10 Commits
Author SHA1 Message Date
zooqueen 6b8612eb74 two red gates, neither of them about the artifact
--check asks one question: is hanzo.yaml what its own pinned input projects to?
It answers that at the ref .spec-lock NAMES, so which remote is cloud's main
cannot change the answer — and it was resolving that remote anyway, before
anything read it, and dying there. Measured on main: it exited on `forge/main,
forgeinc/main, inc/main each carry openapi.yaml and have diverged` while the
artifact it was asked about was byte-for-byte correct. The branch is resolved
only where it is used now — `--current`, whose whole question is whether the tip
moved past the pin, and a re-pin with no `--ref` to say where.

The ambiguity itself is real and stays refused. Three remotes carry the document
and none contains the rest; guessing between them is how a projection gets
published from the wrong lineage.

test_flows.py named four operations the document does not have. The keys route
answers under the app that owns it, and the store plane moved again: this file
said /v1/instances/kv, and that address is now 404 with a 404 control, which is
this repo's own definition of refuted. /v1/provisioning/kv answers 403 with a
404 control — served. The notes carried the old probe as if it still held, so
they are re-measured rather than re-worded, and they now say what the bare
/v1/kv actually is: a separate bucket-keyed value plane whose root answers 200
to anyone, which is the one shape a round-trip must not be built on.

Every flow renders into every SDK's examples, so an id here that the document
lacks is a broken example in eight languages.

hanzo.yaml, CAPABILITIES.md and .spec-lock are untouched. The pin stays where it
is: at 22aedc87 the deployment fails to answer for 16 published operations, and
at cloud's main it is 351 — the pin is the newest release api.hanzo.ai answers
for, not the newest release.
2026-08-27 14:39:10 -07:00
zeekay 5b4379a876 name the repository that holds the document
The receipt is a coordinate a consumer resolves -- <repo>@<ref>:<path> -- and
it named a repository whose main is a different lineage, one that does not
contain the ref. Nothing could re-derive this artifact from that coordinate.

The constant now carries a note saying why it reads the way it does, because
it is the string a prose sweep reaches for first and a comment-only edit is
how it last moved.
2026-08-27 14:00:28 -07:00
zeekay 0b132a8881 Name the thing instead of calling it a door
One word stood for several: the MCP server, one address that accepts requests,
the single way in, and the boundary a credential is validated at. Each site now
names its own sense — endpoint, entry point, the MCP server, the edge, address,
relay — or, where none of those fit, takes the word the surrounding code already
uses.

Prose and comments only, 1:1 line for line. Identifiers keep their names: CSS
classes, DOM ids, struct fields, enum variants, config values and the doc
comments that must open with the identifier they document.
2026-08-23 13:49:39 -07:00
zeekay b8b05087c9 carry the github copy onto the forge
Both hosts had moved. Resolved each file on what api.hanzo.ai actually serves:

- hanzo.yaml keeps this host's /v1/sentinel/{project}/envelope/. Measured: that
  path answers 403 with and without a trailing slash, /v1/event/{project}/envelope
  answers 404 both ways, and every other sentinel operation in the served document
  is tagged o11y. The published document still names the event path, so it
  describes a route nothing answers.
- publish.py and its test come from github, the only side that defines the
  security scheme it requires. Its literal for the document's repo is corrected to
  hanzo-inc/cloud, which is where the document is held — this host settled that in
  'the lock names the repo that holds the document'.
- capabilities.yaml is the union of both tag sets across all nine domains, 213
  tags from 191 and 120. github's commands and replay are real (GET /v1/commands
  answers 200); this host's leaderboard, treasury, visor and the rest are kept.

pytest: 27 passed.
2026-08-20 21:25:28 -07:00
zeekayandHanzo Dev 0df9fb2432 spec sync asks the repository that holds the document
hanzoai/cloud is the re-rooted OSS core: a twelve-commit line sharing no
ancestor with the product and carrying no openapi.yaml at all. It is PUBLIC, so
the workflow's checkout succeeded and publish.py failed one step later with "no
remote of .cloud carries openapi.yaml on `main`" — which the step then threw
away, printing instead its own fixed sentence, that hanzo.yaml had been
hand-edited. It had not: re-derived at its own pinned ref, the committed
artifact is byte-for-byte what that ref projects to. Every push and every hourly
run has reported a drift that never existed.

So the address is the value it always should have been. SPEC_REPO names
hanzo-inc/cloud — where every ref this repo and every SDK has pinned resolves,
and hashes to the digest its lock already carries — and .spec-lock, info.x-spec
and CAPABILITIES.md's header follow it rather than restating it. The receipt was
repointed on its own once and the artifact, the receipt and the test then named
three different repositories; test_the_artifact_names_the_release_it_came_from
now asserts the three AGREE instead of repeating the string a fourth time.

Both canned ::error:: wrappers are gone. A step that replaces a precise
diagnosis with a fixed sentence is worse than a step with no message: publish.py
already says what is wrong and which command fixes it, and `set -e` fails the
step.

hanzo-inc/cloud is private, and the per-job token Hanzo Git mints reads the
run's OWN repository and anything public — nothing else. Measured on
hanzoai/python-sdk run 91449, which named the right repo and still took
`curl: (22) ... 404`. The checkout therefore takes HANZO_GIT_TOKEN, the one name
in this fleet for a credential that reads git.hanzo.ai, and the job says which
credential is missing before actions/checkout can report a private repo as
"repository not found". generate.py's own fetch takes the same name, so one read
needs one credential.

Cloud declares its own `bearer` scheme now, and the projection's addition
stopped adding and started corrupting: setdefault no longer fired while the
document-level `security` it wrote still did, so the published document required
`bearerAuth` and defined nothing by that name. Generated typescript, measured:
2498 bearer call sites from cloud's document, 0 from the projection, leaving 191
files importing a credential helper none of them called. The rule is gone —
cloud says both halves — and test_a_client_can_authenticate refuses a document
that requires a scheme it does not define, whoever supplies it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 23:33:15 -07:00
zeekay 5ca53ab23f the receipt names a commit, and the projection catches up to it
hanzo.yaml described cloud@613cc065 while cloud's main had moved to 38a803e4, so
every SDK and doc page generated from it described a router that had changed.
Re-derived at the tip.

publish.py resolved `ref` to a commit only when one was handed to it; a default
run left the remote-tracking branch it discovered. That name went into
`x-spec.ref`, into CAPABILITIES.md, and into `.spec-lock` — and a branch is not
resolvable in the CI clone that re-derives the artifact, which has no remotes.
Resolving once, before anything reads it, is what makes those three name one
thing and keeps --check able to answer.
2026-08-14 22:36:14 -07:00
zeekayandHanzo Dev 353b50d2b6 tools: a tool name is the id the door answers to, not codegen's rename
`publish.py` rule 6 suffixes an operationId when two collide under the identity
a GENERATOR uses — punctuation stripped, case folded — so that Go does not
declare one request type twice. `deleteSession` (POST /v1/iam/sessions/delete)
and `DeleteSession` (DELETE /v1/o11y/sessions) are that collision, and the
second is published as `DeleteSession_2`.

An operationId is not only a codegen identifier. It is the WIRE NAME of an MCP
tool, and `POST /v1/mcp` answers to no `DeleteSession_2` — measured against the
live door, which lists `DeleteSession` and `deleteSession` and nothing else. The
suffix shipped verbatim in the tool catalogue of three client distributions:
npm @hanzo/mcp, PyPI hanzo-mcp, crates.io hanzo-mcp, all three byte-identical at
d6d49a17, 2279 tools. A catalogue that names a tool nothing can be told to run is
the one defect the catalogue exists to remove.

So rule 6 records what it renamed. `x-id` is the id hanzoai/cloud emitted,
written only where the projection had to change it — one operation in 2279, so
its meaning is its presence — and `tools.py`'s `wire()` is the ONE place a tool
takes its name. Nothing else moves: same rules, same drops, same count.

MEASURED, catalogue against the live door's 1323 tools:
  before  1299/1323
  after   1300/1323   and the remaining 23 are exactly the compat-tagged IAM
                      operations rule 2 drops on purpose, each of which has a
                      canonical twin in the published document
                      (add-user -> POST /v1/iam/users, and so on for all 23).

test_tools.py pins it from the document alone, offline: where `x-id` is present
the catalogue must carry it and must NOT carry the codegen name. hanzo.yml runs
the file, because a gate that is not in the `test:` block does not run.

openapi-generator 7.14.0 is indifferent to the new key — python, typescript,
java and kotlin each emit a full client tree from the document that carries it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 18:31:39 -07:00
zeekayandHanzo Dev a40884ab95 publish: cloud's main is the remote that CARRIES the document, not the one called origin
`--current` resolved `origin/main`, and `origin` is not a fact about a checkout —
it is the name a clone happened to use. hanzoai/cloud answers on three remotes
here and they are not one lineage: `origin` is the GitHub OSS mirror, whose main
holds no openapi.yaml at its root at all, so `git show origin/main:openapi.yaml`
fatalled before the pin was ever compared. The one question this file asks on a
clock could only die or agree by accident, and it died — silently, for 24
releases, while nothing went red. A gate that cannot fail is not a gate.

The remote is DISCOVERED now, never named: fetch every one, keep those whose
`main` holds the document, take the one that contains all the others. A checkout
with a `forge` remote gets no special case — forge wins on this box because it
carries the file, `origin` wins on a normal clone, and neither is written down.
The same resolution is the default when re-pinning, so `python3 publish.py` with
no `--ref` can no longer publish from the wrong lineage either. Unreachable is
not fatal, because a mirror nobody can read decides nothing; stale is, so every
remote is fetched before any is read.

MEASURED, all four verdicts, against this checkout:

  pin v1.801.452 (a real older release)   -> forge/main is bab535c2 with a
                                             different document, exit 1
  pin 292e2fa8 (one commit back)          -> forge/main has moved (bab535c2) but
                                             the document has not, exit 0
  pin bab535c2 (the tip that carries it)  -> hanzoai/cloud@bab535c2 - forge/main,
                                             exit 0
  --cloud pointed at a checkout carrying   -> "no remote carries openapi.yaml on
  no openapi.yaml                            `main`", exit 1, not a git fatal

The committed artifact does not move: `--check` re-derives hanzo.yaml to the same
bytes at v1.801.476, `--served` refutes all 2305 published operations against
api.hanzo.ai and finds none missing, and 17 tests pass. Nothing here changes what
is published, only which repository gets asked whether it is still current — so
`info.version` stays 8.0.0, which is the shape of the publication and the shape
did not change.

Prose follows the code: the `--current` and default-re-pin help, the LLM.md
command block and the gates table all said "cloud's origin/main" and now say what
runs.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 13:23:16 -07:00
zeekayandHanzo Dev c63f667452 gate: refute the published document against the deployment, in the lane that runs
The drift gate this repo already had could not run, so nothing did.

`publish.py --check` is the right question — is hanzo.yaml what its own pinned
input projects to — and it needs a hanzoai/cloud CHECKOUT. hanzoai/cloud is
private, hanzoai is on the GitHub Free plan (where an org secret resolves to the
empty string inside a private repo), and this repo's one repo-level secret is
SDK_DISPATCH_TOKEN. On top of that, 3cd60cb moved both callers to
.hanzo/workflows, which only git.hanzo.ai collects, and
git.hanzo.ai/hanzoai/openapi is a 404 — no mirror. `gh workflow list` has
returned NOTHING since 2026-07-30: not CI/CD, not `spec sync`, no `test:` block.

So on main, --check exited 1, --current exited 1 and test_publish.py failed —
three red gates, none of them running anywhere, while the document they guard
shipped 19 unserved operations to seven SDKs.

  publish.py --served   every operation this repo PUBLISHES must be one
                        api.hanzo.ai answers for. Cloud serves its own emission
                        unauthenticated at /v1/openapi.json, from the binary
                        that is running, so the deployment refutes a dead route
                        by itself. No checkout, no token, no ref to trust.

Deliberately the WEAKER question, because it needs nothing and therefore runs.
One direction only: served-and-unpublished means the pin is behind, which is
--current's question and not a lie about the API; published-and-unserved is the
lie, and it is what reaches an agent as a SKILL.md instructing it to call a dead
endpoint. Never fails on an unreachable API — same rule as hanzo.ai's
scripts/audit-catalog.mjs, which gates the same document: a gate that goes red
for somebody else's outage gets switched off, and then it gates nothing.

Wired into hanzo.yml's `test:` block, the one file both hanzoai/ci and
platform.hanzo.ai read, and test_publish.py now asserts that wiring — deleting
the gate has to delete its own test, in the same diff, on purpose.

The callers move back to .github/workflows, which is the path the forge this
repo LIVES ON collects, with the `uses:` path following them; `spec sync` gets
a token on its hanzoai/cloud checkout, since the automatic one reads a private
repo as an anonymous caller and gets a 404 that reads like a missing repo.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 01:44:33 -07:00
zeekayandHanzo Dev a96449bcc6 publish: the document is generated, so a route nobody serves cannot be described
hanzo.yaml stops being where the API is DECIDED and becomes where hanzoai/cloud's
emission is PUBLISHED. `publish.py` derives it from `cloud@<ref>:openapi.yaml`;
`publish.py --check` regenerates and diffs. An operation no code declares is now
structurally impossible to publish, in this repo and in everything downstream.

MEASURED at hanzoai/[email protected], both documents loaded and keyed by
(METHOD, path): the hand-merged master carried 2093 operations, cloud's emission
2333, shared 1908. So the master described 185 operations nothing serves under
that name and MISSED 425 that are served — 8% invented, 18% blind — while being
the input to seven SDKs, the doc site, the MCP catalogue and the agent-skills
plane, which has no liveness filter at all.

THE 185, each probed with its OWN method and a nonsense-sibling CONTROL:
36 ABSENT (real 404, control 404), 44 unfalsifiable behind a prefix auth gate,
66 absorbed by a {wildcardN} relay door, 39 SERVED and undescribed. A 404 on both
sides IS a refutation and is the only way absence can ever be shown; the control
exists to stop a 401/403/200 being read as existence. The 39 are hanzoai/cloud's
to fix and every one is itemised in LLM.md.

DELETED: 52 `<svc>/openapi.yaml`, shared/, cloud/, generated/, merge.py, sync.py,
audit.py, test_audit.py, test_placement.py — and 14 Swagger 2.0 documents for
`api.hanzo.io`, which is a parked domain: `/account`, `/store` and the control
`/account-zzq9` all return the same registrar lander, and HTTPS has no
certificate for the name.

THE PROJECTION IS SIX RULES AND NOTHING ELSE — no operation added, none removed
that is served, no prose written. It exists because openapi-generator 7.14.0
refuses cloud's emission with 1012 errors (one per route published with an
address and no `responses`) and writes ZERO files in every language; against
hanzo.yaml it reports 0 errors and go/python/typescript-axios/kotlin all emit.
Every rule is a candidate to move upstream into cloud's emitter, and rule 5 alone
is 90% of the file.

BLOCKING, and new with this release rather than with the projection: three o11y
schemas cannot be projected into Go. `o11y.GettableAgentCheckIn` declares
`integration_config` AND `integrationConfig`, `removed_at` AND `removedAt`;
`o11y.O11yPodOnboarding` has `hasClusterName` beside `clusterName`;
`o11y.PostableProfile` the same. `go build ./...` fails on those three and on
NOTHING else — renaming the keys in a scratch copy and regenerating gives exit 0.
This repo must not rename a field, because a field name is the wire, so
test_publish.py pins them as a ceiling instead.

BREAKING: operationIds lose the `<svc>_` prefix merge.py applied — it existed to
keep 52 authored specs from colliding and there is exactly ONE collision in 2284
operations now. Two flows.yaml journeys moved with the hand-authored ids that
vanished: `hello` is get_v1_keys (403 with no key AND with a bogus one, control
404), `tools` is get_v1_tools.

capabilities.yaml keeps one job — which DOMAIN a capability is shown under — and
is gated both ways: a served capability it does not group fails the publish, and
a name it groups that the document does not carry fails too.

generate.py's fallback to this checkout's hanzo.yaml returns, superseding
3953d66's removal of it. That removal was right about authority and wrong about
consequence: pointed at cloud's raw emission the four clients generate nothing.
The file it refused to fall back to no longer exists — this one is derived.

  publish.py --check           hanzo.yaml is the projection of [email protected]
  pytest (16 tests)            passed
  openapi-generator validate   0 errors (was 1012 against the raw emission)
  go build ./... on the client exit 0, with the three o11y schemas patched

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-03 20:10:20 -07:00