Commit Graph
785 Commits
Author SHA1 Message Date
zeekay 90e00d9bb7 State a version once
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.

Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
2026-08-29 13:46:58 -07:00
zeekay 4662534cd9 Regenerate: the AI surface says what it answers
288 of hanzoai/ai's 294 operations now declare a response, against 1 before.
GET /v1/models, POST /v1/chat/completions, /v1/embeddings, /v1/messages —
every address a client actually calls — used to reach this client untyped,
because cloud's relay carried an operation's sentence and dropped the shapes
it named. It carries both now.

Fleet-wide the document goes from 69% to 81% of operations declaring what
comes back, which is where the ~208 new models here come from.
2026-08-28 15:53:34 -07:00
zeekay 6b5e0734e7 One version per package, on the current release
Every dependency on a @hanzo/@luxfi/@zooai package names the same version across
the repo: a catalog entry where pnpm supports one, an exact pin where it does
not, and a range only where a peer genuinely means one.

pnpm itself is pinned too — the estate ran eighteen different versions, five of
them too old to read a catalog.
2026-08-28 15:53:27 -07:00
zeekay 074facbba5 Name Zen in the hero card's font stack
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.
2026-08-28 11:59:32 -07:00
zeekay 9c18306471 fix(type): the file named Zen contained Geist
The name table of Zen-Variable.woff2 read "Geist" and ZenMono-Variable.woff2
read "Geist Mono". The filename, the @font-face and the licence beside them all
said Zen; only the bytes disagreed, so every grep passed and the surface
rendered Vercel's face.

This is the fourth instance in the estate — @hanzo/design shipped the same thing
under the same filename, zoo's foundation declared Zen over inter-var-latin,
and hanzo.sh had Zen declared over a Geist file. A filename proves nothing; the
check is the name table:

  python3 -c "from fontTools.ttLib import TTFont; print(TTFont(P)[\"name\"].getDebugName(1))"

The bytes are @hanzo/font 1.9.2's now, verified that way: family Zen and Zen
Mono. LICENSE-Zen.txt beside them already carried both copyright holders, which
is what OFL §2 asks of the copy that travels.
2026-08-28 11:48:44 -07:00
zeekay 301f737b45 release 8.5.156
One product, one number: the API and every client cut from it answer to the
version cloud's release train mints, so a reader holding 8.5.156 can name the
release it came from. The clients had drifted to their own counters -- 8.5.89,
8.5.91, 0.1.0, 0.1.0-alpha.5 -- because the fanout that carries the number can
only run in check mode on this forge and never writes.
v8.5.156
2026-08-28 08:55:51 -07:00
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 5a0aa5ee79 name the ref the fleet names, and one that can be fetched
The projection was pinned to a commit that exists only on this machine, so
the receipt named a document no consumer could resolve. The document itself
is byte-identical at both commits -- not one generated line moves -- which is
why this is a one-line correction and not a regeneration.
2026-08-27 13:53:26 -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
zeekay c32cd7e962 fix(font): the vendored Zen faces named Geist in their name table
The filenames read Zen-*.woff2 and the glyphs are right; only the name
table said family='Geist', designer='Basement.studio, Andres Briganti,
Mateo Zaragoza', vendorURL=basement.studio. Nothing visual can see that —
a font picker, a local() match and a licence audit can.

Replaced with the corrected bytes from hanzoai/font, matched by glyph
content rather than by filename, so the drawing is provably the same face.
2026-08-24 23:33:54 -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
zeekayandHanzo Dev a43b78eaaf point two examples at the addresses their APIs moved to
`hello` called KeysApi.get_keys(). There is no /v1/keys — API keys are one noun
at /v1/account/keys, so the call is AccountApi.get_account_keys().

`store` called KvApi.post_kv/get_kv_by_name/delete_kv_by_name. /v1/kv is the KV
DATA plane now, addressed by bucket and key; provisioning a KV instance — which
is what this example does — is /v1/provisioning/kv. Same ProvisionRequest, same
three steps, so it is a rename rather than a rewrite.

Both were caught by the examples gate and neither had been true for days, which
is why this repo's pipeline has been red since 2026-08-21 and no regenerated
client has published since.

examples.models is untouched: /v1/models is published and answers 200, and its
operationId is get_models, so ModelsApi appears once the client is regenerated —
which the pipeline does before it tests.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-23 16:10:25 -07:00
zeekay 5ddeca6b5a docs: self-host Zen in place of Geist over the jsDelivr CDN 2026-08-23 15:13:57 -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 c6a60aea35 docs: cut the explanatory prose from the README; drop the stale changelog and boilerplate contributing guide 2026-08-23 12:12:51 -07:00
zeekay 311bdfc7c9 The install line named a version five majors old
`pip install hanzoai==3.2.13` — PyPI serves 8.5.89. Anyone following the first
command on the page pinned themselves to a client from before most of the API
existed, and a pinned version in prose is stale the day after the next release.
It names no version now.

The note beneath it explaining that 3.2.1 and earlier used `get_v1_keys` rather
than `get_keys` goes with it: every published version is long past that rename,
so it described a client nobody can install by accident.
2026-08-23 11:27:50 -07:00
zeekay 57b6b6319e Drop the inventory counts from the README
It opened by telling a developer the client covers so many operations over so
many paths in so many services, and repeated variations of that further down. The
number is not something a reader can act on, it was wrong — the document had
moved on — and keeping it right means measuring the document on every build to
maintain a sentence nobody needed.

The prose says what the client IS. Where a count explained real behaviour — some
operations declare no response body, so their method hands back nothing to
decode — the explanation stays and the tally goes.
2026-08-23 11:05:51 -07:00
zeekay 3f9f455a25 The README states the size of the client it actually is
It opened with "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 a developer reads. They were true once and the document moved.

The count is now stamped by the generator from the spec each build was cut from,
inside a `<!--counts-->` span, so it cannot drift again. Any derived figures
beside it were re-measured against the same document rather than carried over.
2026-08-23 10:40:18 -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 d301b245e2 follow the release pointer the rest of the fleet follows
This repo pinned an exact hanzoai/ci release, so it kept running the build.yml
that reads git.hanzo.ai through HANZO_GIT_TOKEN — a credential no org seals.
The gate has been red on that ever since, while the sixty-four repos on @v1
moved past it.

@v1 is not an unproven tip. hanzoai/ci pins its own gate to the last release and
moves that pin first, so a release proves itself there before @v1 follows it.
An exact pin here opts out of that and rots instead.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 19:53:49 -07:00
zeekay 93af3004c6 track the name agents read, so a clone documents itself
LLM.md is the source and stays the source; CLAUDE.md is a symlink to it, so there is
one file and it cannot drift from itself. What changes is that the link is committed
rather than left for each checkout to recreate.

Left untracked, the doc was conditional on a step outside the repo: a fresh clone had
no CLAUDE.md, nothing loaded LLM.md, and an agent worked here having never read what
this repo already explains. Most of the estate already tracks theirs; this is the rest
agreeing with them.
2026-08-20 16:29:10 -07:00
zeekayandHanzo Dev e1b48fe3c1 release: 8.5.89 — the V8 launch version
Co-authored-by: Hanzo Dev <[email protected]>
v8.5.89
2026-08-20 14:13:04 -07:00
zeekayandHanzo Dev accd79d1c4 release: 8.5.89 — the V8 launch version
Hanzo 8 is one product on one number. The API, the six generated clients, the
CLI and the console all answer to 8.5.89, so a client version names the API it
was cut from without a compatibility table.

8.5.89 is the first cut past the highest 8.x anyone had reached (@hanzo/console
8.5.88). Every register moves FORWARD into it; nothing is renumbered backwards.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 14:08:18 -07:00
zeekayandHanzo Dev b3483fba72 cicd: carry the release's version too, at ci v1.0.77
The ref said which document; nothing said which version it is. A projection of
one document answers to that document's number, so the poke carries it and the
cut uses it verbatim instead of counting this repo's own patches.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 11:56:06 -07:00
zeekayandHanzo Dev 78f4af1e1f release: 3.2.14 — the client regenerated onto cloud@dd98c965
Co-authored-by: Hanzo Dev <[email protected]>
v3.2.14
2026-08-20 10:25:44 -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 f50ef05473 cicd: ci v1.0.76 — the git.hanzo.ai reads take their own credential
v1.0.75 read the document with github.token, the per-job token, which is scoped
to the repo running the job and answers 404 for a private repo in another org.
Both reads in that lane cross one. The 404 read as a missing document rather
than a refused credential.

v1.0.76 gives those reads their own KMS-sealed HANZO_GIT_TOKEN and says so by
name when it is absent, so the next failure names its own remedy.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 23:20:38 -07:00
zeekayandHanzo Dev 7b80afeb4a hanzo.yml: name the cloud this SDK is a projection of
hanzoai/ci defaults client.spec.repo to hanzoai/cloud, and that repo is now the
re-rooted OSS core -- a short line sharing no ancestor with the product. The
ref this projection is pinned to does not exist there, so every run of the
client lane, check or move, died fetching it: `curl: (22) ... 404`.

Measured: the same token reads openapi.yaml at 113cf7d6 from hanzo-inc/cloud
with a 200 and from hanzoai/cloud with a 404. Nothing about the credential.

`.spec-lock`'s own repo= line does not set this -- ci reads the repo from
hanzo.yml and uses the lock's copy for a commit message -- so the two could
disagree in silence, and did. They are one fact and now agree.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 22:57:53 -07:00
zeekayandHanzo Dev 208240ecff cicd: the poke carries the ref, so a release can actually move the pin
This file claimed a bare dispatch took cloud's newest tag. ci v1.0.54 removed
that path, so what a poke really did was re-read the LOCKED ref, assert its
digest and exit before any write -- a check wearing a regeneration's clothes.
Measured: this projection sat 254 commits behind cloud while PyPI matched its
newest tag exactly, because publishing was never the broken half.

The ref now travels with the poke, as ci v1.0.75's spec-ref/spec-sha256 inputs.
That is the same fact repository_dispatch carried as client_payload.sha, spelled
for the one dispatch verb Hanzo Git serves. Omit both and the lane is a check
that writes nothing, exactly as today.

ci moves v1.0.54 -> v1.0.75 because the input does not exist before it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 22:55:25 -07:00
zeekay 8aa0e49c3b say Hanzo Git, which is what it is
"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.)
2026-08-19 22:28:08 -07:00