One toolchain across the estate. The tree carried 1.26.0, .1, .3, .4 and .5
at once, so which compiler built a binary depended on which repo it came
from.
Co-authored-by: Hanzo Dev <[email protected]>
Empty on purpose: this push carries no tree change, only the delivery. The
forge's per-repo hooks all named cloud's /v1/git/webhook, which answers 204
without dispatching, so a push here has never built. Forge-wide system
webhook 3035 now delivers to platform's /v1/git-webhook instead.
Co-authored-by: Hanzo Dev <[email protected]>
The forge reads .github/workflows natively and github.com reads nothing else,
so one directory serves both planes instead of two conventions that disagree
about which one is live. The reusable in hanzoai/ci already lives there, so a
caller naming that path now resolves to a file that exists.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and
every other org-prefixed spelling all meant the same machine. Which org a
job belongs to does not change which host can build it, so the org bought
one pool name per org per arch and each could go stale on its own -- the
evo host alone answered to fourteen names for one label.
One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The
generic labels every plain workflow uses (ubuntu-latest, self-hosted) are
untouched, so naive CI keeps working.
Co-authored-by: Hanzo Dev <[email protected]>
hanzoai/ci keeps the reusable at .github/workflows/build.yml — the one path
both planes resolve, since github.com reads a reusable only from there and the
forge reads it too. The v1 tag moved with it, so a caller still naming
.hanzo/workflows/build.yml asks for a blob that is not in the tree:
InsertRun: read hanzoai/ci@v1:.hanzo/workflows/build.yml: object does not exist
The caller itself stays at .hanzo/workflows/cicd.yml on purpose: github.com has
no runner for these labels, so a caller placed there could never be scheduled.
Co-authored-by: Hanzo Dev <[email protected]>
The row pointed at hanzoai/dart-sdk, which is a 9KB landing page: four licence
files and a README saying the code lives in its language org and that nothing
is published from there. The client itself is hanzo-dart/sdk, 33MB of it, and
it had not been regenerated because the driver was writing next door.
hanzoai/dart-sdk is the shape the other ten should end up in.
CAPABILITIES.md and .spec-lock are projections of hanzo.yaml, so re-pinning
the document moves them with it. compute takes visor's place, the names that
went singular follow, and the operation counts are the ones the current
routers register.
The pinned document was a month old and predated the compute migration, so
every SDK generated from it described /v1/visor — which the deployment no
longer serves at all. That is how a hand-written console client and a
generated one drifted the same way at the same time.
publish refused until the taxonomy could place what the API now serves. Most
of it was one migration: agents, channels, flags, functions, integrations,
projects and tools each went singular, and visor became compute. Placed the
three that are genuinely new by reading what they serve — ci alongside deploy,
space alongside s3 since it serves drives and files, and market with web3
rather than commerce, because it serves chains, pools and tokens.
crm is grouped nowhere now only because it serves no route yet; the file can
only arrange what the router answers for. It comes back when it does.
The social card asked for Inter first. That is not a face this estate
licenses, ships or ever drew, so a renderer with Inter installed painted
the wrong face and one without it silently fell through to system-ui.
Zen leads now; the same tail follows it.
(cherry picked from commit 900a1763cddeabf938859fce1d3ccd200684996d)
It used to take resp.Body and hand-decode an anonymous struct, because there
was nothing else available: the document declared no response for /v1/models,
so the generator gave GetModels a bare *http.Response and every caller had to
know the shape by heart.
Reading through the generated AiModelList is the assertion. It only compiles if
the shape crossed from hanzoai/ai, through cloud's relay, into the document
this client was projected from — the whole chain, checked by the compiler
rather than by a comment.
Every gate in this repo compares a client to the document, and every build
proves the client is well-formed. Neither answers the question a user actually
has: does it work against a server. Seven languages now answer it -- go, python,
typescript, dart, php, ruby, rust -- against the local cloud by default and
against anything HANZO_BASE_URL names.
Each probe asserts a 200 whose body decodes AND a 403 that arrives as an error,
because a client that swallows a refusal and returns a zero value looks exactly
like one that works.
It earned its place on the first run: GET /v1/models is AiAPI.GetModels in Go
and ai_api::get_models in Rust but ModelsApi.getModels in TypeScript. One
document, three groupings, and nothing but a call finds it.
git-repo-id composes the path in every generated doc as
github.com/<git-user-id>/<git-repo-id>, and the Go client's module gained a /v8
suffix so its 8.x tags are publishable at all. Without this the next
regeneration would put 2,272 references to the bare path back into the docs and
tell a reader to import something the module proxy refuses to serve.
Same 1622 paths and 2269 operations. What moved is prose the apps emit: the
company and legal examples, and the GitHub webhook's account of what a
repository event does.
--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.
The routes consolidated and the map did not follow. chains, rpc and tokens are
all /v1/web3/* now and tag themselves web3; indexers and oracles read back
through explorer; connectors moved under integrations; and the bare-/v1
inference surface -- /v1/chat/completions, /v1/models, /v1/embeddings -- tags
itself ai, with the long tail apps/ai used to hand-declare gone exactly the way
this file said it would go once apps/ai projected its mounted registry.
Sixteen names arrived as the singular of a plural that left with its routes.
Four are placed for the first time: trust beside the audit and compliance it
publishes, graph beside the stores it is one of, node beside the fabric rather
than beside exec, and standing beside company.
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.
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.
The SDK generation heading carried "(Stainless RETIRED, 2026-07)". Retiring
something is worth recording once; carrying the name in a section title keeps it
on the page forever, and this was the last mention of it anywhere in the clients
or the driver.
The section says what the one way IS — hanzo.yaml, openapi-generator, one
invocation in generate.py. That is the whole fact.
I added a generator step to keep "N operations over M paths, grouped into K
services" fresh in every SDK README. The right fix was to delete the sentence. A
developer opening a client does not need its inventory; the number tells them
nothing they can act on, and keeping it true costs a generator step, a marker
convention and a parse of the document on every build.
Stamping a number that should not exist is a more elaborate way of being wrong.
Seven SDK READMEs opened by telling a developer the client covers "2,479
operations over 1,814 paths, grouped into 192 services". The document serves
2,259 over 1,620 in 117 — every number wrong, in the first paragraph of the page
each language's users land on. Nobody mistyped them; they were true once and the
document moved, which is what a hand-written count does.
So the numbers are no longer written. A `<!--counts-->` span in the README is
rewritten from the spec the client was actually generated from, on every
generate — the sentence around it stays the repo's own voice, and a README with
no markers is left untouched, so adopting it is per-repo rather than a surprise
rewrite.
Measured by PARSING the document, not by matching it. The first version counted
`^ operationId:` and `^ /`, which is true of cloud's YAML and of nothing
else — this driver hands the generator a JSON copy, so every count came out zero
and stamped "0 operations over 0 paths" into the README it was added to fix. It
also disagreed with itself about services: the regex said 148, the parse says
117, and only one of those is the number of distinct tags on an operation.
dart emitted NOTHING for four releases. openapi-generator 7.14.0, the version
sdks.yaml pins for every language, dies in AbstractDartCodegen.fromProperty with
`JsonSchema cannot be cast to ComposedSchema` on our polymorphic request bodies —
so every run produced an exception and no files, and the drift check reported no
change because there was nothing to compare.
7.19.0 emits it cleanly: measured, 117 api files, no exception. The choice was
to move the fleet or to pin the one language, and moving the fleet regenerates
twelve working clients to repair one — none of which have CI to catch a
regression. So the pin sits where the defect is, and says when to remove it.
One generator stays the rule; `generator_version` is the exception, and an
exception that names its own expiry is cheaper than the churn it avoids.
hanzoai/iam retired every verb segment from its addresses: a single item is
read, updated and deleted at /v1/iam/<entity>/{owner}/{name}, with the method
carrying the verb. The 45 operations here described /get, /update and /delete
leaves, which nothing serves.
They need no replacement. This document is the source of request-body and
query-parameter shape for the UNTYPED majority of the surface; IAM's operations
are typed in their own repo and carry their schemas through the graft, so the
live table already describes them better than an authored copy can.
Measured on the parsed document rather than the diff: 1796 paths -> 1751, 45
removed, 0 added, and every removed key is a retired IAM address.
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.
publish.py already knew it: api.hanzo.ai/v1/openapi.json, cloud's own emission
from the process that is actually up. generate.py asked git.hanzo.ai at a locked
ref instead, so two files in one repo named two sources for one document, and
the one every SDK generates through needed HANZO_GIT_TOKEN to read a private
repo.
hanzoai/ci's lane reads the served document and hands it to the generator as
$SPEC, so in CI this path never ran. It ran for people, and it ended at a
credential a contributor could not be given.
.spec-lock still records which document a tree was generated from. A digest that
differs now says cloud has shipped since, rather than refusing — regenerating
onto what production serves is why the function runs.
Co-authored-by: Hanzo Dev <[email protected]>
This repo sat on ci v1.0.49 while the fleet moved thirty releases past it.
hanzoai/ci proves each release on its own gate before @v1 follows, so an exact
pin here buys nothing and needs moving by hand to get anything.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo.yaml derives from public.yaml at the release api.hanzo.ai serves —
120 capabilities in nine domains, the tag the app that answers. The store
journey speaks the provisioning plane's served ids, and the security gate
asserts the requirement names a defined scheme instead of a spelling.
capabilities.yaml groups the 116 capabilities (the apps, HIP-0139) into the
nine domains, replacing the 191 path-segment names; the store journey moves
to its served address (/v1/instances/kv, probed 403-vs-404 against the
-zzq9 control). Inert until the pin moves: the both-ways gate compares this
file to the pinned document, and the pinned release still tags by segment.
The re-derive lands when --served accepts the release that tags by app.
`packageName: hanzoai.cloud` makes the generator emit `hanzoai/cloud/`, and
python-sdk tracks it at `pkg/hanzoai/cloud`. Neither half is a repository name.
The rename that pointed every DOCUMENT reference at hanzo-inc/cloud swept this
line up too, because a package path with a slash in it reads like one.
Since then the python projection has emitted nothing: the generator wrote
hanzoai/cloud and `take` went looking for hanzo-inc/cloud, so `generate.sh`
failed with "generator emitted no hanzo-inc/cloud" -- which names the repo and
sounds like a fetch problem. Every other language kept working, so the failure
looked specific to python rather than to one substitution.
Verified: with this, python regenerates +25 -114 ~33, the same shape js and
rust produce from the same document.
Co-authored-by: Hanzo Dev <[email protected]>
hanzoai/ci's client lane takes that credential from KMS. This workflow is not
that lane and cannot reach KMS without a second copy of the login, so it takes
an org Actions secret — two homes for one value, and a reader who seals the KMS
one and watches this stay red has been misled by a message that only said
"seal it".
So the message says which is which. The end of it is `--check` moving into
hanzo.yml's `test:` block, where the lane already exports the value into the job
environment and `served` already runs: one credential, one home, one lane, and
this file goes.
Co-authored-by: Hanzo Dev <[email protected]>
The gates table's `needs` column carried a diagnosis that had been overtaken by
events, and a wrong diagnosis in the durable record is worse than none: it
blamed a GitHub plan tier and a repo secret named SDK_DISPATCH_TOKEN for
`publish.py --check` never running. Neither is why. The workflow asked
hanzoai/cloud, which is the re-rooted OSS core — twelve commits, no
`openapi.yaml` — and being PUBLIC, the checkout succeeded and the failure
surfaced a step later, where the step's own canned message relabelled it as a
hand edit of hanzo.yaml.
What is still open is the credential, and it is a different sentence.
hanzo-inc/cloud is private and in another org, 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 and again on this repo's own spec sync,
which now names HANZO_GIT_TOKEN and stops there in a second.
Two smaller ones follow the same rule. `--current`'s paragraph explains that the
remote is discovered and never named, then names one; it says which host now.
And `generate.py`'s read takes the fleet's one name for a credential that reads
git.hanzo.ai, so a hand run and hanzoai/ci's client lane ask for the same thing.
Co-authored-by: Hanzo Dev <[email protected]>
The pin was 132 cloud commits old and published three speech routes the running
deployment refutes — GET /v1/generate-text-to-speech-audio-stream, POST
/v1/generate-text-to-speech-audio, POST /v1/process-speech-to-text. skills.py
turns a published operation into a SKILL.md, so those three were live
instructions to an agent to call an address that answers 404.
Cloud's main is not the answer to that. It runs 108 commits ahead of what is
deployed and carries 60 operations api.hanzo.ai does not answer for — the
/v1/instances/*, /v1/sentinel/* and /v1/sites/* families — so re-pinning there
trades three dead endpoints for sixty. The rule is the one `--served` already
states: the newest release the deployment fully answers for. That is
cfc36010d03b, where all 2471 published operations are served and 23 more are
served and not yet published.
Two capabilities arrive with it and had to be placed, which is the one editorial
decision this file keeps. `allowance` is what a plan lets you do before money
moves, so it reads beside `plans` and `pricing`; Observability's `usage` is the
other half and a different question — usage counts what happened, allowance
states what was bought. `edge` is served by the package that serves `projects`
and `sites` and asks what follows a deploy, whether a publish reaches readers
rather than whether it was accepted, so it sits with what does the hosting.
The three speech names leave capabilities.yaml with the routes that carried
them. A capability appears the release it starts being served and leaves the
release it stops.
Co-authored-by: Hanzo Dev <[email protected]>
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]>
FORGE_TOKEN was a hand-provisioned PAT kept as an org-wide Actions secret. Hanzo
Git hands every job a token — checkout writes it into http.extraheader with no
secret involved — so this was a second credential home beside KMS that rotted on
its own schedule and bought nothing.
Now GIT_TOKEN, from github.token.
"Forge" named nothing in this estate. The product is Hanzo Git, the address is
git.hanzo.ai, and the in-cluster service is hanzo-git.hanzo.svc. A generic word
invented for it sends a reader looking for a system that does not exist.
Comments only. ("unforgeable" stays — different word.)
Every SDK locked its spec to hanzoai/cloud. That repo is an empty mirror shell —
864K on disk, no refs — so the raw fetch answered 404 and the client lane has
failed on every push since. The document is in hanzo-inc/cloud: the locked ref
resolves there, and openapi.yaml at it hashes to exactly the sha256 this lock
already names.
Only the address was wrong. The lane's own digest gate proves the bytes are
unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
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.
openapi-generator offers thirteen TypeScript and JavaScript targets. Two of
them serve an audience `hanzoai` cannot reach, and the rule that admits them is
stated where the rows are so the other eleven stay refused for a reason rather
than by omission.
fetch: axios brings 27 packages and 4.1 MB; this client declares none, because
`fetch` is the host's. It also hands back the `Response`, so a stream is
readable. `importFileExtension` is what makes its ESM half load in Node, Deno
and a Worker instead of only in a bundler — measured by importing it.
angular: the framework owns the transport and the return type, so an axios
client cannot be made idiomatic there at all — no interceptors, no HttpContext,
no cancellable Observable, no testing backend. `serviceSuffix: Api` is forced:
the API has a product called `base`, whose default class name is the generated
transport's own parent.
"the Apple output is byte-identical to the jar's" is not what the override does
and not what was measured: the emitted URLSessionImplementations.swift hashes
differently from the jar's, because it IS the changed file. What the three
canImport guards preserve is which branch each Apple platform compiles — with
one addition, macOS importing MobileCoreServices, which is present there and
whose symbols the pre-11 branch already calls unguarded.
C++ was the last language owning its own invocation, and it gave two reasons.
`templates:` retired the first one for rust already; this retires the second,
which needs saying precisely because it was true when it was written.
The floor said generator 7.24.0 against the 7.14.0 pinned here, because on
7.14.0 ten translation units would not compile through two defects in the
generator's type resolution that no template reaches — mustache interpolates the
strings resolution produces, it cannot recompute a type. Both defects were real.
Both lived in SCHEMAS THE DOCUMENT NO LONGER CARRIES: vector_NamedVectors /
vector_DenseVector under additionalProperties (9 TUs), and an integer-backed
enum declared over utility::string_t and defined over int32_t,
framework_Document.docstatus (1 TU). Neither schema exists at the ref this fleet
reads, so neither defect is reachable and the floor expired with its cause.
Re-measured rather than argued: at cloud@49389cf9, ignoring blank lines and the
generator string each file's header carries, exactly one of 5327 files differs
between 7.14.0 and 7.24.0 — a doc comment on ConsoleSettings::getOidcConfig. On
7.14.0 the library builds with 0 errors (2663 sources, 192 API classes, 2462
models, 2502 operations) and the six example flows build with 0 errors, on
aarch64 Ubuntu 24.04 with g++ 13.3. So the row needs no version of its own and
this does not raise the pin.
The four cpp-restsdk overrides move here as templates/cpp-restsdk/, next to the
flags they go with. They are load-bearing and measured: generating without them
fails with 8 errors, all in ModelBase.h.
One flag, and it is the one six other rows already carry.
o11y.GettableAgentCheckIn publishes integration_config beside integrationConfig
and removed_at beside removedAt so older agents keep working; C++ camel-cases a
property into its accessor, so each pair declared one method twice. Renaming the
accessor keeps both wire keys — measured, all four are present in the emitted
model, each under the document's own name.
`take` is include/ and src/. The generator's CMakeLists.txt, Config.cmake.in,
git_push.sh and README.md sit at the output root under neither, so they are
never copied and need no `drop` entry — the repo keeps its own build contract.
Verified against the tree the departed script produced: `generate.py cpp --check`
reports clean, byte for byte, 5327 files.
Co-authored-by: Hanzo Dev <[email protected]>