The job asked for /v1/kms/orgs/hanzo/secrets/<path>/<key> and parsed
.secret.value. KMS serves GET /v1/kms/secrets/<path>/<key>?env=<env> and
answers {"env","name","value"}, taking the org from the token's own claim,
so every read 404'd and every release reported the credential unseeded —
including hanzo/prod/python-sdk-publish/PYPI_TOKEN, which is there.
Root to 3.2.13, README install line names it. hanzo-research declares its
readme, license and authors, so its page carries them.
hanzoai/cloud@113cf7d6 declares components.securitySchemes.bearer and applies it
document-wide, so regenerating writes a populated Configuration.auth_settings()
and auth_settings=['bearer'] into 2498 call sites; ApiClient._apply_auth_params
sets Authorization: Bearer <token> from access_token, which was read by nothing
before. Four call sites carry an empty list — exactly the operations the document
marks security: [] (get_models, get_models_providers, get_commands,
get_openapi_json) — so the public routes still go out with no credential.
examples/client.py drops the header_name/header_value compensation and passes
access_token. examples/models is a new flow on the public catalog: it runs before
a reader has a key, which makes the install checkable in one command.
Proven on api.hanzo.ai, not by reading: examples.models prints 112 models with no
credential; examples.hello returns the caller's keys with a real sk- and 403
"sign in to manage API keys" with a bogus one.
The distributed inference, topology and device-capability code derives
from exo (github.com/exo-explore/exo), which was GPL-3.0 when this fork
was taken. The package declared BSD-3-Clause, which grants terms the
upstream licence does not.
Ship exo's GPL-3.0 text, add a NOTICE naming upstream and recording the
modifications, and declare GPL-3.0-only. hanzo-network stays an optional
extra with guarded imports, so the base CLI is unaffected.
openai_api_check.py was not a test — pytest never collected it, nothing imported
it, and its whole body was a hand-run connectivity probe against a vendor with
the credential written in as a literal. There is nothing to keep.
The value it carried must still be rotated: it is in this repo's history and the
history is public.
This script carried its key as a literal. A key in a source file is a key in
every clone of that file, and this repo mirrors publicly, so the value must be
rotated independently of this change — removing it from the tip does not remove
it from history.
Reads OPENROUTER_API_KEY and exits with a clear message when it is unset.
"Nothing in the client reads the environment" is true of the generated tree —
zero os.environ under hanzoai.cloud — and false of the package a reader
installs: hanzoai.zap reads HANZO_ZAP_ENDPOINT and hanzoai.config reads
HANZO_CONFIG_HOME. Neither carries a credential, which is the property the
sentence was reaching for, so it now says that instead of overreaching.
The README offered the two key shapes as a permission split — pk- to read,
sk- to write. cloud's own identity boundary says otherwise: middleware_identity
returns nil for any pk- before it consults the key store, because a key meant
for a browser bundle must not read. A pk- resolves to an org so a beacon can be
attributed to a tenant, and that is all it does.
Following the old sentence, a reader exports a pk- and every one of the five
flows answers 403 — /v1/tools spells out why, "a validated principal is
required" — and that refusal reads like a revoked key rather than the wrong
shape of key. examples/client.py now refuses a pk- before the request goes out,
the same argument that already rejects an unsendable access_token.
Also: three status codes in example docstrings had drifted from what the API
answers now (/v1/kv/keys/* 404s on every verb, /v1/automations/mcp 404s), and
two dead files went with them — examples/.keep, whose directory has held real
flows for a while, and scripts/mock, which reads a .stats.yml this repo does
not have and which scripts/test already says is unnecessary.
README: PyPI serves 3.2.1 and this tree is 3.2.11, so the install line is the
git one. Between the two the default version left the operation ids — 3.2.1
carries get_v1_keys/get_v1_tools (182 api modules, 2172 models) and this carries
get_keys/get_tools — so `pip install hanzoai` above a `get_keys()` quickstart
hands the reader a client that cannot run it. Counts are measured off the pinned
document: 1814 paths, 2479 operations, 191 tags + default_api = 192 API classes
over 2460 models, 2659 importable modules.
Auth was wrong everywhere and it was the same root cause: the document declares
no securityScheme, so every operation got an empty auth_settings and
Configuration(access_token=…) is read by nothing. Serializing get_keys from such
a Configuration produces a request with no Authorization header at all — the
five flows have been calling anonymously and reading the refusal as a bad key.
examples/client.py now passes the bearer through ApiClient's header_name/value
pair, verified against a local server that echoes what it received, and a smoke
test pins both halves so the day cloud emits the scheme, the test fails and the
header moves back into Configuration.
The examples gate resolved class names and stopped there, which is how `money`
and `tools` once passed it while every call in them named a method that no
longer existed. It now walks the syntax tree for attribute accesses on anything
bound to a *Api and asks the class: ten names resolve, and a stale get_v1_tools
fails in the same second the import passes.
Deleted five example scripts that imported hanzoai.Hanzo, hanzoai.cluster,
hanzoai.agents and hanzoai.completion — none of which exist — or were dict
literals captioned "in practice this would be called through the MCP interface";
compileall passed all five, because syntax is not a claim. Deleted three docs
that were fabrication rather than drift and that nothing rendered: FEATURES.md,
QUICKSTART.md, GPT5_ORCHESTRATION.md.
pkg/hanzo-agent said `pip install hanzoai` and `from hanzoai import Agent,
Runner`; the client has no Agent. Its own `full` extra resolved to
hanzoai[web3,tee,marketplace,cli], extras the client does not declare, so it
installed the client and none of the extensions. pyproject said BSD-3-Clause
while LICENSE is the Apache 2.0 text. test_smoke imported tracker_api and no
/v1/tracker path is emitted any more, so the suite carried a failing test naming
a module the client does not have; it reads analytics_api now, off the client.
uv.lock still carried 3.2.10 after pyproject moved to 3.2.11. A lock that
names a different version than the project it locks is a second answer to a
question with one answer.
A regeneration on 2026-08-09 claimed v3.2.10 and tagged it, but that commit
never reached main, so main went on declaring 3.2.10 too and I bumped onto
the same number. Two trees cannot share a version: the tag names the other
one.
The next free patch is what the version should have been. Read the TAGS, not
just pyproject.toml — the tag is where a version is claimed.
Twelve ids came from the document's own old->new map; the rest are generated
method spellings the map does not carry, read off the regenerated client.
Neither set follows a search-and-replace: the document renamed 2014 operations
and 21 of those do not reduce to dropping `_v1_` — /tasks and /meet became
`*_app` and `*_app_assets` when the SPA and the API stopped sharing a name,
and `get_v1_team_transactor_api_v1_statistics` drops only the first, the
second being part of the path.
Checked, not assumed: every `.get_*`/`.post_*` an example calls resolves
against the 7519 methods the client exposes.
Regenerated from hanzoai/cloud@6a719cdc (.spec-lock carries the ref and the
digest). 3.2.9 was a projection of a document 343 commits old, and in between
the operationId stopped carrying the default version: 5196 method names the
client published — `get_v1_tools`, `post_v1_agent`, every one of them — name
operations the document no longer declares. None remain.
The id is one identity, not four. An operationId is the SDK method, the MCP
tool name, the CLI spelling and the by-name key, so a client that keeps the
old spelling does not merely look stale — it teaches a name nothing answers to.
Shape: 1813 paths / 2479 operations -> 192 api modules + 2460 model modules,
all 2659 importable.
This repo is a fork, and its SECURITY.md still carried the upstream's contact —
so a researcher who found a hole in what WE ship was told to mail a vendor who
cannot fix it, and who would then know about a live vulnerability in our
production before we did.
Fourteen repos were in that state: datastore -> [email protected], git and
its two worktrees -> [email protected], auto -> [email protected],
calendar -> [email protected], dns -> [email protected], python-sdk ->
[email protected], plus bot, gui-gates, gui-geist, playground and
playground-oauthcb pointing at brand domains with no mailbox behind them.
All of them now carry one policy and one address, [email protected], matching
what cloud and gateway already used. The policy says out loud to report here
rather than upstream, because the next fork will inherit the same file and the
same instinct.
v1.0.49's client lane decided check-vs-move from `github.event_name`, which
git.hanzo.ai renders as `workflow_call` inside every reusable, so this repo's
pushes ran as MOVEs and went looking for hanzoai/cloud's newest tag. That tag
is v1.801.507, which is not on cloud's main and carries no openapi.yaml, so the
fetch 404'd and took the job with it.
v1.0.54 asks the payload instead: with none, the ref is the one .spec-lock
already names, which is what a check is and what a push wants.
The package is named for the cloud app it speaks to — cloud apps/sandbox is
hanzo-sandbox here — so there is one name per concept across the two languages.
Built against the routes rather than guessed:
GET|POST /v1/sandboxes list, create
GET|DELETE /v1/sandboxes/:id get, delete
POST /v1/sandboxes/:id/exec argv | command, stdin, dir, timeoutSec
GET|POST /v1/sandboxes/:id/fs read, write
The API key carries the org, and the client sends nothing else. cloud resolves
it through principal.OrgOf(user, org), which returns nothing without a validated
principal — "no validated principal, so the org claim is untrusted" — and the
gateway strips a client-supplied X-Org-Id before a handler sees it. A first cut
took `org` as a required argument and sent the header on every call; that would
have been ignored at best.
Two things the tests found, both real:
* with no key configured the client still sent `Authorization: Bearer `, an
empty credential that reads as a present one and hides "no key" behind
whatever the server says about a bad one. It sends no header now.
* cloud spells the size field `class`, which python cannot, so Sandbox maps it
to `cls` at the boundary instead of leaving callers to index a dict.
This is the REMOTE half. hanzo_sandbox.sandbox stays the local one — unshare,
namespaces, a boundary on this machine. Same word, unrelated concerns, separate
modules.
Served by a real HTTP server in the tests, since what breaks in a client of this
shape is the request: path, method, credential.
Co-authored-by: Hanzo Dev <[email protected]>
The regeneration onto hanzoai/[email protected] landed without moving this, because
the bump it carried named a version two releases behind: another line had already
cut 3.2.7 and 3.2.8 while it was in flight. The tree is right and the number was
not.
The ai door composes, so hanzoai/ai's own contracts reach this client for the
first time: 101 new model modules, 93 write operations that took an untyped bag
now take a declared body, and the fleet document types 1428 of its 2463
operations. 2624 generated modules import clean.
Diverged 2026-08-07 and each side did the SAME work. Both applied a commit
called 'one generated client in the wheel' — 2,213 files, 800,555 deletions,
and the forge retains nothing GitHub dropped. `git cherry` says the other
GitHub commit is already here under a different sha. So there is no
disagreement about the code, only two write sides doing it twice.
The forge then kept going: five client regenerations against
hanzoai/[email protected], 3.2.2 -> 3.2.7. GitHub stopped at 3.2.2.
Merged with -s ours: the histories join so GitHub can fast-forward onto this
and stop being a second head, and the tree stays the newer regeneration. Taking
the trees instead would resurrect ~140 generated modules that both sides had
already agreed to delete — the merge reported them as modify/delete conflicts
purely because the two deletions have different shas.
Co-authored-by: Hanzo Dev <[email protected]>
Cloud's APIKeyPrefixes admits pk- and sk- only (auth_identity.go, 4b030b77).
The lsp tool still told the user to set an hk- key, which cloud resolves to no
principal — following the message produced a key that could never authenticate.
The message is now a single NO_KEY beside the resolver that mints it, exported
from hanzo_tools.core, so a second tool reporting it cannot spell it differently.
Resolution order is untouched: HANZO_API_KEY, then ~/.hanzo/config.json .apiKey,
then the CLI's IAM session.
vector_index names the split cloud actually draws: pk- publishable for read,
sk- secret for write.
Co-authored-by: Hanzo Dev <[email protected]>
Cloud's APIKeyPrefixes admits pk- and sk- only (auth_identity.go, 4b030b77).
The lsp tool still told the user to set an hk- key, which cloud resolves to no
principal — following the message produced a key that could never authenticate.
The message is now a single NO_KEY beside the resolver that mints it, exported
from hanzo_tools.core, so a second tool reporting it cannot spell it differently.
Resolution order is untouched: HANZO_API_KEY, then ~/.hanzo/config.json .apiKey,
then the CLI's IAM session.
vector_index names the split cloud actually draws: pk- publishable for read,
sk- secret for write.
Co-authored-by: Hanzo Dev <[email protected]>
The document types 1495 of its 2459 operations, up from 1242. 87 new model
modules carry the response shapes ai, eval, functions, domain, experiments,
security and benchmark now declare, so those calls return a typed object
instead of a dict. 2524 generated modules import clean.
.spec-lock moves from v1.801.497 to @ffbc86064, which is v1.801.498.
`@v1` is a rolling alias and the mirror moves it back. Measured within four
minutes today: v1 was pushed to the commit carrying the fix, a sync put it back
on the commit from two releases ago, and the runs in between generated against a
pipeline that no longer matched the one just released — the client lane read an
OPENAPI its own reusable had never exported and died telling the caller to set a
variable the caller does not own. An immutable patch tag cannot be moved by
anyone, which is the only property this pin needs.
`python3 generate.py` finds no `yaml` module on a fleet runner, so three of the
four call sites could not run the driver they had just been handed. js-sdk
already ran it under uv and worked; this is that same line, in the other three.
`${OPENAPI:?…}` only prints its word when the variable is unset, but bash
PARSES that word either way, and inside it a lone `'` opens a quote that never
closes: `unexpected EOF while looking for matching '`, at line 23, with OPENAPI
set and the branch not taken. The regeneration died on a message about how to
supply a value it already had.
Two separate things kept this projection from ever moving.
The generator lives in hanzoai/openapi and this repo went and got it for itself,
with no credential, from a private repo — so the first dispatch that ever
reached the client lane died at `could not read Username`, exit 128. hanzoai/ci
holds the token that reads this forge, so it now fetches the driver once and
hands it over; a call site that does not clone cannot be wrong about where to.
And the trigger: `repository_dispatch` is a verb this forge does not have, and a
nightly `schedule:` decides when by a clock rather than by a release. The forge
answers a workflow_dispatch POST with 204 and a runner takes it in seconds, so
that is the one trigger left.
hanzoai/ci's client lane, given no payload, now reads hanzoai/cloud's newest tag
off git.hanzo.ai and regenerates this tree onto it. This is the trigger that
makes it happen without anybody pressing anything.
It replaces a fan-out that could not work in either direction: cloud pushed
`repository_dispatch: spec-update` at nine projection repos, but github.com has
zero self-hosted runners and these callers ask for the forge's pool, so an
accepted event queued until GitHub cancelled it a day later — and the forge,
which has the runners, has no repository_dispatch endpoint at all. Twelve such
dispatches exist across the fleet and not one reached a first step.
Asking is the better verb even where telling would have worked. It needs no
credential spanning six owners, it runs on the runners that exist, and a release
stays green whether or not this repo is well — a projection that is down is this
repo's problem, not a reason to redden a cloud release that is already live.
They shared a name and the lane died on it. `SPEC_REF` is the ref of the
DOCUMENT — hanzoai/ci's client lane exports a hanzoai/cloud sha or v-tag — and
this script also handed it to `git clone --branch` against hanzoai/openapi, which
is a different repository and has never had a ref by that name.
`git ls-remote hanzoai/openapi 5126939` returns nothing, and neither does
v1.801.495, so every CI regeneration died at the clone before the generator ran.
By hand it appeared to work, because with SPEC_REF unset the default was `main`
and both repositories happen to have a branch called that — which is the worst
kind of coincidence, one that makes a broken path look tested.
The generator is cloned at its own default branch now, from git.hanzo.ai, and the
document reaches the driver as a value: hanzoai/ci passes it digest-checked, and
without that the driver reads this tree's .spec-lock and fetches the same bytes
from the same forge. A hand run and a CI run project the same document instead of
whatever main happens to be.
This distribution shipped two complete generated clients under one import root.
`hanzoai.cloud` is the current one, generated from hanzoai/cloud's openapi.yaml at
the ref .spec-lock names. `hanzoai.api` / `hanzoai.models` was another, and the
package root re-exported its 2,202 names flat — so the obvious spelling,
`from hanzoai import ApiClient`, reached the second client, while
`hanzoai.cloud.ApiClient` reached the first. Nine core names existed twice as
distinct classes bound to distinct documents, and which one a caller got depended
on which import line they happened to write.
The second lineage was not an older copy of the first. It was a projection of a
document that no longer exists: of the 1,695 routes it binds, 243 are still in the
document at v1.801.495 and 1,452 are not. Eighty-six percent of it builds a
request the server answers with a 404. It had been broken for as long as it had
been shipping, and nothing said so, because a client only fails at the call.
So it goes, along with its runtime — api_client, api_response, configuration,
exceptions, rest — which `hanzoai.cloud` carries its own copies of. The package
root now exports `cloud` and nothing else. Nothing is re-exported flat on purpose:
a name that appears in two places is a name that can disagree with itself, and the
namespace already says which document a symbol came from.
The smoke tests pinned the deleted surface — they asserted more than 300 api
modules and imported `hanzoai.api.tracker_projects_api` by name, which is how a
suite can stay green over a client that 404s. They now assert the surviving one,
plus the invariant that made this possible in the first place: exactly one
generated package under `hanzoai`, and none of the nine core names re-exported
flat beside it.
This distribution shipped two complete generated clients under one import root.
`hanzoai.cloud` is the current one, generated from hanzoai/cloud's openapi.yaml at
the ref .spec-lock names. `hanzoai.api` / `hanzoai.models` was another, and the
package root re-exported its 2,202 names flat — so the obvious spelling,
`from hanzoai import ApiClient`, reached the second client, while
`hanzoai.cloud.ApiClient` reached the first. Nine core names existed twice as
distinct classes bound to distinct documents, and which one a caller got depended
on which import line they happened to write.
The second lineage was not an older copy of the first. It was a projection of a
document that no longer exists: of the 1,695 routes it binds, 243 are still in the
document at v1.801.495 and 1,452 are not. Eighty-six percent of it builds a
request the server answers with a 404. It had been broken for as long as it had
been shipping, and nothing said so, because a client only fails at the call.
So it goes, along with its runtime — api_client, api_response, configuration,
exceptions, rest — which `hanzoai.cloud` carries its own copies of. The package
root now exports `cloud` and nothing else. Nothing is re-exported flat on purpose:
a name that appears in two places is a name that can disagree with itself, and the
namespace already says which document a symbol came from.
The smoke tests pinned the deleted surface — they asserted more than 300 api
modules and imported `hanzoai.api.tracker_projects_api` by name, which is how a
suite can stay green over a client that 404s. They now assert the surviving one,
plus the invariant that made this possible in the first place: exactly one
generated package under `hanzoai`, and none of the nine core names re-exported
flat beside it.
Actions are disabled on this repository, so these files have not run in a long
time and cannot run: they are build definitions no forge executes. Builds go
through the native path -- POST /v1/runner, which schedules a BuildKit job --
and CI lives under .hanzo/workflows. A dead workflow that still looks like a
pipeline is worse than none, because it reads as coverage nobody has.
ff-main asked only "is LOCAL an ancestor of REMOTE" and called every other
answer DIVERGED -- so the forge simply being AHEAD of GitHub took the
loud-failure path. That is the normal state after every native push, and the
push-mirror is sync_on_commit with an 8h floor, so one commit can produce up
to 48 false alarms ten minutes apart.
The cost is not the noise. A job that is always red cannot report the one
thing it exists to report, and this job exists to refuse to force-push a real
divergence. The loud failure is only worth keeping if it is rare.
Convergence, not invention: app, git, hanzo.ai, iam and now id already carry
exactly this two-direction check. This repo held the stale copy.
Exercised against real git ancestry -- equal and github-ahead unchanged,
native-ahead DIVERGED(1) -> native-ahead(0), and true divergence still
exits 1. One row moves; the alarm survives.
Co-authored-by: Hanzo Dev <[email protected]>
`POST /v1/billing/gpu-charge` and `GET /v1/billing/gpu-eligibility` answer 404
at api.hanzo.ai — the address and its `-zzq9` nonsense sibling alike, which is
the only way absence is ever shown — and hanzoai/cloud has deleted the
handlers. The client still offered both, so the one outcome either method had
was a 404.
They survived in `pkg/hanzoai/api` + `pkg/hanzoai/models`, the tree the retired
driver wrote. `generate.py` does not own it: sdks.yaml declares exactly one take
path, `hanzoai/cloud -> pkg/hanzoai/cloud`. Nothing regenerates these files, so
nothing would ever have removed them and no future regen can put them back.
Six operations, three models, three exports. `pkg/hanzoai/cloud` is deliberately
untouched — it carries `/v1/billing/gpu/{charge,eligibility}`, which the
deployment still answers for (401, against a 404 control), and publishing an API
smaller than the one served is the same class of lie in the other direction.
generate.py python --check [python] clean
import hanzoai 2208 exports, 0 dangling
cloud-client / duplicate-fields pass, 2362 modules
Co-authored-by: Hanzo Dev <[email protected]>
Mirrors hanzoai/mcp's Rust `lsp` exactly: one tool, two planes, picked by data
the caller already has. `file` names the file; `repo` (a git.hanzo.ai slug,
optional `rev`) says which world it lives in. Without `repo` nothing changes —
the same language server on the same tree. With it, the question goes to the
indexed corpus behind /v1/code/lsp, which reaches across a repo's dependencies
without checking anything out.
One table maps actions onto ops: locate carries a relation (definition|
reference|type|implementation) because "where is X" is one question with four
answers, not four routes; hover, symbols, diagnostics and complete stand on
their own. The body is {repo, rev?, path, line, character, relation?} in LSP's
own frame — 0-based line, UTF-16 character — shifted from the tool's 1-based
`line` by the same expression the local plane uses.
The call goes through the shared HanzoCloud client hanzo-tools-code and
hanzo-tools-net already compose, so there is no second client and no second
lsp tool. Actions a plane cannot serve say so up front rather than spawning a
server or calling out.
test_lsp_tools.py asked for a class named `LspTool`; the class is `LSPTool`,
so those two never ran. One name, and they run.
Co-authored-by: Hanzo Dev <[email protected]>
Temporary. O11yGettableAgentCheckIn declares integration_config and removed_at
twice each, exactly as 3.2.0 shipped them. duplicate-fields must go red on the
forge; cloud-client will stay green, which is the point. Reverted immediately
after.
Co-authored-by: Hanzo Dev <[email protected]>
We do not use GitHub Actions. `.github/workflows/cicd.yml` asked for
`hanzo-build-linux-amd64`, a label github.com serves with nothing, so every
caller queued 86402s — 24h exactly, GitHub's timeout — and reported
"cancelled". Not a broken gate: a gate on a platform we retired. Deleted rather
than repaired. A file that asserts coverage it cannot deliver is what let this
repo read green while nothing ran.
The forge is where CI lives, and this repo was not wired to it. Two things were
missing and the second is the interesting one.
`.hanzo/workflows/cicd.yml` — the ~7-line caller, hanzo.yml holds the config,
same shape as the sixteen other hanzoai repos already on this path.
And sync-from-github.yml could never have started it. It fast-forwards main
with the workflow token, which by design triggers no workflow, and compensates
by dispatching `deploy.yml` — a workflow this repo has never had, because these
are libraries that publish to PyPI and deploy nothing. The forge returned 404,
`|| echo "build dispatch failed (non-fatal)"` swallowed it, and the sync went
green ten minutes at a time. Every commit arrived having started nothing. That
is the third check this session that was believed and was not running, and it
is why the forge shows zero cicd.yml runs against 775 syncs.
It now names cicd.yml, and a failed dispatch fails the job.
hanzo.yml gains `duplicate-fields`: repeated AnnAssign targets in a class body,
read from the AST. 3.2.0 shipped `integration_config` and `removed_at` declared
twice each in O11yGettableAgentCheckIn — the second binding wins, the first
field silently does not exist, and the value on the wire was read and dropped.
The `cloud-client` import gate was green for it and always would be: measured
here, with the 3.2.0 model restored, it exits 0 and prints "models: 2172
modules imported". Its comment claimed it caught name collisions; that claim is
corrected rather than left to mislead.
Co-authored-by: Hanzo Dev <[email protected]>
3.2.0 shipped silent data loss to PyPI. O11yGettableAgentCheckIn declared
`integration_config` twice and `removed_at` twice; in Python the second
annotation rebinds the name, so the model advertised fields it did not have and
the value arriving under each shadowed name was read and dropped. 3.2.1 fixed
the models. Nothing yet fixed the reason it got out.
The `cloud-client` gate imports every generated module and calls that the build
step. A class body with a duplicate field IMPORTS CLEAN — measured here: with
the exact 3.2.0 model restored into the tree, `cloud-client` exits 0 and prints
"models: 2172 modules imported". The comment above it claimed the import caught
"a name collision". It never could. That claim is corrected rather than left to
mislead the next reader.
`duplicate-fields` reads the AST instead: repeated AnnAssign targets in one
class body. Verified both directions on the real defect, not a fixture — exit 1
naming both collisions with the 3.2.0 model in place, exit 0 with 3.2.1, 2362
modules scanned in both. It refuses a zero it did not earn: scanning no modules
fails rather than passes, because "found nothing" and "looked at nothing" print
the same otherwise.
And the gate now has somewhere to run. `.github/workflows/cicd.yml` asks for
`hanzo-build-linux-amd64`, which on github.com is served by nothing since the
ARC pool retired — every caller since has queued 86402s, GitHub's 24h timeout,
and reported "cancelled". That is why this repo's suite has not run in weeks
while looking merely flaky. The git-runner fleet serving that label lives on
git.hanzo.ai, which sync-from-github.yml already mirrors onto every ten
minutes, and where publish-pypi.yml already reads its token from KMS. So the
caller goes there, beside the publish it guards, like the other sixteen repos.
Co-authored-by: Hanzo Dev <[email protected]>
hanzoai 3.2.0 is on PyPI as of 18:16:29Z, and its wheel is byte-identical to the
one built here before the o11y fix landed — so the published artifact still has
o11y.GettableAgentCheckIn declaring six fields where the document declares
eight, silently dropping an old AWS agent's `removed_at`.
This tree has that fix and the zap suite, and it was still calling itself 3.2.0.
One version naming two different sets of bytes is the failure this repo already
learned once from npm; the number moves so it cannot happen quietly. Nothing is
published here — 3.2.1 goes out with the operationId unification, or sooner if
the shipped data loss is judged to warrant its own release.
Co-authored-by: Hanzo Dev <[email protected]>
One regenerated file. o11y.GettableAgentCheckIn declares eight properties and
this client had six: `integration_config`/`integrationConfig` and
`removed_at`/`removedAt` each snake_case to one attribute, so the second
shadowed the first and the survivor kept the camel alias.
That is not cosmetic. The snake_case spellings exist because hanzoai/o11y
publishes them so older AWS agents keep working, and this client read such an
agent's value and then threw it away without raising: from_dict with
{"removed_at": 2020, "removedAt": 2030} returned 2030, and
to_dict()["removed_at"] was None. `to_dict` also wrote one value under both
keys, under two different declared types.
The mapping lives in hanzoai/openapi sdks.yaml, where this client's invocation
is declared; nothing here is hand-edited. Eight fields now, both wire names
carried as aliases, and each value round-trips under its own key. Gate unchanged:
182 api and 2172 model modules import, pytest 6 passed with the same
pre-existing test_zap_transport error.
Co-authored-by: Hanzo Dev <[email protected]>
bcd56c69 deleted Hanzo/AsyncHanzo/Client/Stream and 14 more; this file imported
Hanzo at module scope, so pytest errored at COLLECTION and ran none of the
transport tests. 3.1.5, 3.1.6 and 3.1.7 all shipped over that error.
The lock now names the generated client's real entry point (ApiClient,
Configuration, the ApiException hierarchy), and the end-to-end drives a real
httpx.Client — which is the only seam hanzoai.zap has left, since rest.py is
urllib3 and nothing in pkg/hanzoai takes an http_client.
Co-authored-by: Hanzo Dev <[email protected]>
pkg/hanzoai/cloud goes +2186 -2126 ~175: 1700 paths / 2354 operations / 2186
schemas, 182 api modules and 2172 model modules, against the 239/1116 it carried
from hanzo.yaml. The document is hanzoai/cloud's own emission now, pinned in a
new .spec-lock by commit and sha256, and generate.py reaches it by value —
--skip-validate-spec means the 1012 missing-`responses` errors no longer write
zero files, so a client can be cut from the authority instead of a projection of
it.
Two renamings come with that, and both are the fix rather than the damage. IAM's
types are namespace-qualified (iam.Role, iam.Application, 95 of them) because a
bare `Role` was two unrelated shapes wearing one name. And the <svc>_ prefix is
gone from every operationId, so every generated method lost it.
The prefix is what broke the examples, and the gate saw only two thirds of it.
Four flows failed on their imports; `money` and `tools` PASSED while every call
in them named a method that no longer existed — an import resolves the names in
the `from … import` line and a method is looked up at call time. All five flows
now name operations that exist, checked by resolving each one as an attribute,
and hanzo.yml records that ceiling so the next reader does not trust the gate
for more than it says.
`chat` is removed, which is a measurement and not a preference: cloud declares
POST /v1/chat/completions with no requestBody and no responses at all, so the
generated method takes no body and returns None — the one call a chat example
exists to make cannot be expressed. Inventing the type, or hand-rolling the HTTP
inside a generated client, is the drift these SDKs exist to prevent; js-sdk
dropped its own chat flow at 2.0.7 for exactly this. It returns the release
cloud gives that route a body.
Two smoke assertions were already red before this regeneration and are now true
again: they pinned AIApi/APIKeysApi/MCPApi and AdminApi.plugin_admin_*, names
from the retired lineage. The surface is all still there under AiApi/KeysApi/
McpApi and AdminApi.admin_plugins.
tests/test_zap_transport.py still errors, exactly as it did before: it imports
`Hanzo` from hanzoai, which the hand-written package does not define. That is
not this document's business and is untouched.
Minor rather than patch: every generated method changed name.
Co-authored-by: Hanzo Dev <[email protected]>