543 Commits
Author SHA1 Message Date
zeekay 145e9f1c68 regenerate from cloud 529be4ccb9 2026-08-28 08:10:42 -07:00
zeekay c0e89d2ad6 regenerate from cloud 92604dd76f 2026-08-27 20:41:31 -07:00
zeekay e31dd96f46 regenerate from cloud a48ddfb315 2026-08-27 19:44:26 -07:00
zeekay 8d99a341b9 regenerate from cloud 16e3b9cef1 2026-08-27 17:24:00 -07:00
zeekay a303b73f19 regenerate from cloud 03cd1b9b10 2026-08-27 17:04:47 -07:00
zeekay 8e4669c68e regenerate from cloud 733acc2f6d 2026-08-27 16:09:11 -07:00
zeekay 2a55a3e2c8 regenerate from cloud 3f82fe0da0 2026-08-27 15:19:38 -07:00
zeekay 7c4b937533 regenerate from cloud 186c87db6f 2026-08-27 13:52:35 -07:00
zeekay 369b525882 fix(tools): a screenshot arrives downscaled, with the full capture on disk 2026-08-27 08:45:44 -07:00
zeekay 9f6663482d regenerate from cloud 939b09d4
The rebase folded the fleet-wide projection into the newer v8.5.151 already on
main, and two files did not survive it: git_api and websearch_api still
described the previous emission. .spec-lock came back conflicted from the same
rebase and is set to the document these were generated from.

`generate.py python --check` is clean after.
2026-08-24 23:38:24 -07:00
zeekayandHanzo Dev dc28d4b761 hanzoai.cloud: project v8.5.151, where the last retired IAM addresses left
.spec-lock named hanzo-inc/cloud@54ddf3d6, whose document still published nine
verb-noun IAM addresses — get-account, get-app-login, delete-mfa,
issue-user-token, mint-user-keys, revoke-user-keys, send-verification-code,
set-preferred-mfa, update-preferences. Every one answers 410 with a Link naming
its successor, so every generated method for them was a call that cannot land.
v8.5.151 (6437e8bb) drops them; measured against api.hanzo.ai/v1/openapi.json it
carries the identical 2253 operations over 1612 paths, zero either way. The only
verb-noun left is /v1/iam/delete-membership, which is served and published.

The smoke tests named four surfaces the pinned document had already stopped
carrying, and were failing before this change: KeysApi (the keys tag folded into
iam), ModelsApi (/v1/models no longer declares security: [], while
/v1/openapi.json has carried the marking across every pin), analytics and chat,
and admin plugins (/v1/tools/plugins is where the published plugin surface is).
Each now names what the document holds, and the breadth floor counts tags rather
than tracking a number that moves with every re-tag.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 03:42:28 -07:00
zeekayandHanzo Dev d6ecee069c iam: pass the scope through, and let the server's refusal speak
Which list routes insist on an owner is the server's rule, and it moves. The
deployed binary requires one on users, sessions and applications and not on
roles, permissions, providers, workspaces, invitations, tokens or audit-logs;
the change that resolves a listing's tenant centrally is written but unreleased.

So nothing here keeps a copy of that rule. The IAM tool no longer refuses a
missing owner on applications and sessions, get_applications takes an owner
rather than demanding one, and no docstring or parameter description promises
that leaving it off makes the credential decide — today that answers 400.

A refusal now reaches the caller as the reason IAM gave rather than as a
transcribed body: the RFC 9457 detail, or the envelope's msg where the route
answers the envelope, which is how memberships refuses.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 03:42:08 -07:00
zeekayandHanzo Dev 95e2025681 iam: address the rows IAM serves, and read the shape each route answers
A row is named by owner and name — /v1/iam/users/hanzo/z — read with GET,
replaced with PUT, removed with DELETE. The hand-written clients reached for
verb-noun addresses, and the CLI and route table reached for a verb hanging off
the collection; both spellings are gone from the server.

A collection GET answers a wrapper keyed by the plural, and the key is not
always the word in the path — audit-logs answers auditLogs. Every reader now
takes the key from the table and raises when it is absent, because reading a
missing key as [] reports "none" for a healthy org. A row answers the record
itself, absence is 404, and a refusal is an RFC 9457 problem document carrying
the reason. account, memberships, password and login keep the {status, msg,
data} envelope and have their own reader.

The credential goes in Authorization as client_secret_basic, which is where IAM
reads one; the pair used to ride the query string, where it authenticated
nothing and landed in every log on the path. A password rides beside the user
row rather than inside it, so IAM hashes it and nothing writes it in the clear.
An update reads the row before replacing it, since PUT replaces the whole
record.

Role membership is the role's own users list, so adding and removing a member
reads the role, edits the list and writes it back.

The IAM tool drops enforce, system_info and health; the team tool reaches
IAM's workspaces, memberships, invitations and account instead of deferring to
an address it never had. Both name organizations only when the caller does —
IAM scopes the read, and a hardcoded owner is a refusal for everyone outside it.

The CLI route table is checked against IAM's own registered routes, and every
address here was verified against the running server.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 03:36:48 -07:00
zeekay 53cd11b0a5 hanzo-iam: one decoder, so a wrong path names itself
hanzo.id serves its sign-in SPA on every unmatched path, so a path IAM
does not serve answers 200 text/html. Forty-four call sites went straight
to .json() on the strength of the status code and died inside it with a
byte offset naming neither the path nor the reason. oauth was the one
module that asked, in a guard of its own.

routes.decode is that question, once, beside the unwrap that answers the
envelope one: the wire decision, then the envelope decision, each with a
name. A test walks the syntax tree and fails if any module decodes a body
on its own.

141 pass across the package and the two consumers that build on it.
2026-08-23 12:57:04 -07:00
zeekay 28035b6721 hanzo-iam: one client, one env reader, one verifier
The credential rode in the query string on AsyncIAMClient too — the
exported one — across seventeen call sites. Same fix as the sync client:
Authorization, Basic for a confidential client.

AsyncIAMClient was DEFINED TWICE, in client.py and async_client.py, and
the copies had drifted to nineteen methods against thirty-eight. __init__
exports the one in async_client.py, so the other was unreachable; it is
gone.

Three readers of IAM_* disagreed about the tenant: IAMConfig.from_env and
fastapi.configure both defaulted the org to the literal "hanzo", so a Lux
or Zoo process that forgot IAM_ORG did not fail — it addressed the hanzo
tenant and reported hanzo's users as its own. Now one reader,
IAMConfig.from_env(org), which seeds the endpoint and the tenant from the
org it is given; organization has no default at all. A test walks the
package and asserts config.py is the only module that reads the
environment.

IAMClient.validate_token and validate_token_with_cert were a second,
weaker verifier — no issuer check, no require:["exp"]. tokens.verify does
both and fails closed, so the strict one is what survives.

118 pass, and the seventeen tests of the two consumers that build on it.
2026-08-23 12:51:30 -07:00
zeekay aa18787d58 hanzo iam: address the routes IAM serves
Every command in this tree called /api/get-users, /api/set-password,
/api/add-provider and twenty-five more — the Casdoor-era surface. IAM
serves /v1/iam/<entity> and /v1/iam/password; all twenty-eight 404d. The
client id and secret rode in the query string besides, where the server
never looks and every log does.

IAM's entities all have the same five routes, so this is one
implementation of them over a table of fourteen entities rather than
fourteen hand-written command trees: 1391 lines to 300, and every entity
gains the verbs only some of them had. The credential goes in
Authorization — Basic for a confidential client, a bearer when one is
exported — and configure writes ~/.hanzo/auth.json 0600.

test_iam_cli reads IAM's own route registrations out of its Go source and
asserts every route this CLI names is one of them: 70 checked, and it is
what caught certs/get answering GET where the table said POST.
2026-08-23 12:46:01 -07:00
zeekay f526911603 hanzo-memory: the vector store is local, not a tracked artifact
343MB over 8,197 files of a developer's own LanceDB — chat_sessions,
memories_test_user_123, facts_test_kb, and embedded markdown — rode in
every clone of a public repo. Untracked and gitignored; the directory
stays where it is on disk.
2026-08-23 12:34:47 -07:00
zeekay 02f2ec2014 hanzo-mcp: the README documents the server that exists
The quickstart called create_mcp_server, server.register_*_tools and
server.tools.read — no such names. The real surface is the hanzo-mcp
command, its flags, and HanzoMCPServer; the tools come from whichever
hanzo-tools-* packages are installed, through the hanzo.tools entry
point. Verified against --version, --help and the constructor.

Drop the three *_COMPLETE.md status reports, MODULAR_ARCHITECTURE.md
(a proposal), COMPREHENSIVE_DOCUMENTATION.md (claims 70+ tools over a
package with seven) and SEARCH_GUIDE.md (documents an ffind tool that
does not exist).

SECURITY_PATCH.md named files and lines for three findings in 0.8.13;
all three are addressed in 0.15.15 — the shell escaper it flagged is
gone with its module, permissions.py refuses .. and ~ before resolving,
and the third asked for a token the server minted, persisted to
~/.hanzo/mcp_token and never once checked. That token is gone too: auth
is IAM's, and a credential nothing verifies reads as a gate that is not
there.
2026-08-23 12:31:08 -07:00
zeekay 5bc4b34731 iam: the credential rides in Authorization, not the query string
hanzo-iam's admin calls sent clientId/clientSecret as query parameters.
IAM's app() reads httpx.Basic(c) — the Authorization header and nothing
else (internal/authz/authz.go) — so the pair authenticated nothing and
landed in every access log, proxy log and Referer on the path.

_admin_headers now renders it as HTTP Basic (RFC 6749 2.3.1), a bearer
still wins where one is held, and _admin_params is gone rather than left
returning an empty dict. The async class gets the same one helper.

test_credential_location pins it on both arms: put the pair back in the
query and the test fails on the string it leaks.
2026-08-23 12:23:26 -07:00
zeekay 791a13a0ed Regenerate against the current API document
74 commits of cloud had landed since these clients were last cut: +7 operations,
-2. The additions include GetBaseBases and GetBaseBasesByOrg — the listing that
answers which Bases a caller can reach, which is why a workspace built on this
client could not show one.

.spec-lock moves with the code. It named the older document while the tree was
regenerated from the newer one, and a lock that disagrees with the files beside
it is worse than no lock: it is what the next reader checks to decide whether
they are behind.
2026-08-23 10:01:23 -07:00
zeekay 841130f02e Regenerate against the released document (v8.5.102)
Projected from hanzo-inc/cloud@59ec4deb4 openapi.yaml, sha256
e7e90d5445aeccfe332d942d0edd7a77df14336d97a445b71fb2f3047db65192.

By hand: the CI lane clones the private driver with a KMS-minted IAM machine
token, and that subject is linked to no account on git.hanzo.ai, so it verifies
and authorizes nothing.
2026-08-22 08:01:30 -07:00
zeekay be1e2814c4 name the neighbour, so the wrong one is not installed
hanzo-agent and hanzo-agents differ by one letter and are different products:
one is an SDK for building agents, this is a CLI for driving them. They share no
code and neither depends on the other, which is exactly why the names mislead.
Each README now says which is which.
2026-08-21 16:21:43 +00:00
zeekay a0ee31e20f name the neighbour in the docstring too
Same one-letter confusion as the README: hanzo-agent is the SDK, hanzo-agents is
this CLI. A reader who reaches the module before the README should still be told.
2026-08-21 16:21:42 +00:00
zeekay 9d7794d81c name the neighbour, so the wrong one is not installed
hanzo-agent and hanzo-agents differ by one letter and are different products:
one is an SDK for building agents, this is a CLI for driving them. They share no
code and neither depends on the other, which is exactly why the names mislead.
Each README now says which is which.
2026-08-21 16:21:30 +00:00
zeekayandHanzo Dev 2063e57090 the docs theme gets the icon it asks for
mkdocs.yml names assets/favicon.svg beside assets/logo.svg. Only the logo was
committed, so every build of the hanzo-agent docs resolved the favicon to
nothing and served the default.

The publish error also offered "a forge" as a place to put a PyPI token. It
means git.hanzo.ai; say that.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 19:53:51 -07:00
zeekayandHanzo Dev f06d33b3a9 spec: regenerate onto cloud@dd98c965
The committed client came from a document 262 commits old. Regenerated with the
lane own inputs -- scripts/generate.sh, the spec at a pinned ref, the driver
checkout -- so this is the derivation CI does, not a second one.

The surface moves the way the document moved: the datastore, docdb, kv and sql
clients collapse into instances (those routes re-homed under /v1/instances),
allowance arrives, sentry becomes sentinel, and the text-to-speech,
speech-to-text and health clients go because cloud no longer serves them.

No version bump. Publishing is a decision, and this commit is only about the
client being true.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 09:46:20 -07:00
zeekayandHanzo Dev 67304c3c12 the lock names the repo that holds the document
Every SDK locked its spec to hanzoai/cloud. That repo is a 12-commit stub with
no openapi.yaml in it at all, so the raw fetch answered 404 and the client lane
has failed on every push since. The document is in hanzo-inc/cloud — 10,618
commits, where the locked ref resolves and openapi.yaml at it hashes to exactly
the sha256 this lock already names.

Naming the real location does not by itself restore the lane: that repo is
private, and the hanzoai token reads it as not-found. But a lock pointing at a
repo with no document can never work, and this one works the moment the token
can see it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 21:36:25 -07:00
zooqueen 95cf0799f9 the browser transport is the zapd consumer, and only that
hanzo_mcp.zap_server imported hanzo_tools.browser.zap_server, which does not
exist — the module could not be imported at all. Its caller swallowed that in
`except ImportError: return`, so _start_zap_server() ran on every `run()` and
started nothing, and the HANZO_NO_ZAP switch guarded a path that never had an
effect either way.

The browser tool reaches Chrome through the native host -> zapd -> the socket
at ~/.zap/run/zapd.sock, where hanzo-mcp is a consumer (hanzo_tools.browser
.zapd_consumer). That is the transport, so the WebSocket server and its mDNS
discovery go, along with _normalize_mcp_result, which existed to shape results
for that wire and had no other caller.
2026-08-18 23:13:53 -07:00
zeekayandHanzo Dev 22c0e467a3 cli: drop 'hanzo install runtime'
hanzoai/runtime is archived. The subcommand set up a checkout at
~/work/hanzo/runtime and installed the hanzo-runtime / @hanzo/runtime
SDKs — a source tree that no longer exists and packages that were never
really released (npm only ever had 0.0.0-dev; PyPI's last release was
2025-07-15).

Verified: hanzo.commands.install imports clean and install_group now
exposes 13 subcommands with 'runtime' absent.

(cherry picked from commit 16d31994dd)

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-15 09:07:02 -07:00
zeekay 80c31f3534 publish-pypi: read KMS at the route KMS serves
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.
2026-08-14 17:42:58 -07:00
zeekay 4998a916fd the client sends the credential the document now declares
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.
2026-08-14 17:22:30 -07:00
zeekay b739f2ab6e hanzo-network: carry exo's GPL-3.0 and attribute the fork
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.
2026-08-14 16:55:05 -07:00
zeekay bcbea8ce31 drop a script that existed to hold a key
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.
2026-08-14 16:52:07 -07:00
zeekay ef4f774da5 the credential comes from the environment
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.
2026-08-14 16:51:14 -07:00
zooqueen 7166ca70bf chat: point at the two commands that actually start a local endpoint
`hanzo router start` and `hanzo serve` are neither of them commands. The cloud
comes up with `hanzo up`; one model with `hanzo engine serve`.
2026-08-14 16:30:13 -07:00
zeekay 7a5968f9dc a pk- key authenticates nobody, so stop telling readers it reads
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.
2026-08-14 14:45:57 -07:00
zeekay 241ca6dc59 the front door tells the truth about the client behind it
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.
2026-08-14 14:31:08 -07:00
zeekay d1fa0676d1 3.2.10 — the client says what the wire says
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.
2026-08-14 11:01:13 -07:00
zeekayandHanzo Dev 1fe73de0c3 hanzo-sandbox: a client for the sandboxes cloud actually serves
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]>
2026-08-09 19:36:57 -07:00
zeekay 75239bbcce client: regenerate from hanzoai/[email protected] (3.2.6)
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.
2026-08-08 22:49:35 -07:00
zooqueenandHanzo Dev 20a2178e61 auth: the two key shapes are pk- and sk-, and one sentence says so
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]>
2026-08-08 15:04:19 -07:00
zeekay cafae37f4e client: regenerate from hanzoai/[email protected] (3.2.5)
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.
2026-08-08 14:20:07 -07:00
zeekay b4498ce53d client: regenerate from hanzoai/[email protected] (3.2.3) 2026-08-08 19:11:00 +00:00
zeekay 69e74af7dc one generated client in the wheel: hanzoai.cloud
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.
2026-08-08 06:12:00 -07:00
zeekayandHanzo Dev 856a955141 prune the retired GPU billing addresses so a regen cannot re-mint them
`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]>
2026-08-06 04:36:37 -07:00
zooqueenandHanzo Dev e1635ebf98 feat(lsp): repo answers from /v1/code/lsp, file stays local
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]>
2026-08-06 03:19:28 -07:00
zeekay edeb65d5af Revert "PROVE THE GATE: restore the exact 3.2.0 duplicate declarations"
This reverts commit f1014b6addd78d538d06890c3d88c401aea57f96.
2026-08-05 15:21:33 -07:00
zeekayandHanzo Dev 3056f4d485 PROVE THE GATE: restore the exact 3.2.0 duplicate declarations
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]>
2026-08-05 15:17:02 -07:00
zeekayandHanzo Dev 3ff8f5eba2 o11y: the check-in model stops dropping the field it exists to carry
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]>
2026-08-05 13:25:35 -07:00
zeekayandHanzo Dev 3a09eb4319 regenerate from cloud@5126939 — the client leaves the hand-merged lineage
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]>
2026-08-05 11:00:03 -07:00