client: this repo is a projection, and the release drives it

hanzoai/cloud's release now sends `repository_dispatch: spec-update` carrying
(version, sha, spec_sha256). hanzoai/ci's `client:` lane fetches openapi.yaml AT
THAT SHA, REFUSES if the bytes hash to anything else, regenerates, writes
.spec-lock beside the code, and — only after this repo's own `test:` block has
passed over exactly those bytes — commits and cuts a patch.

NOTHING HAS EVER SENT THAT EVENT. The assumed sender was hanzoai/openapi, which
has zero workflows; where a generate.yml existed at all it had run twice in its
whole life, both failures, or never. That is the hole this closes, and closing
it is why the document moved: from hanzoai/openapi hanzo.yaml (hand-merged, 1742
paths, fed by nothing) to hanzoai/cloud openapi.yaml — smaller at 1058 paths,
emitted from the code by each app's own router, and gated on every cloud release
by a drift check that regenerates it from source. A smaller true document beats
a larger unverified one.

The lane is defined ONCE, in hanzoai/ci. What was hand-rolled per repo is
deleted with it.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-01 11:30:05 -07:00
co-authored by Hanzo Dev
parent 57756128d9
commit 816d29f069
3 changed files with 25 additions and 12 deletions
+3
View File
@@ -2,6 +2,9 @@ name: CI/CD
on:
push: { branches: [main], tags: ["v*"] }
pull_request:
# ONE cloud release, every projection. hanzoai/ci's client: lane
# (hanzo.yml) is what this fires.
repository_dispatch: { types: [spec-update] }
workflow_dispatch:
jobs:
cicd:
+16 -12
View File
@@ -77,15 +77,19 @@ test:
sys.exit(1 if (bad or not ok) else 0)
'
- name: codegen-drift-check
# The client is generated, so the only thing that can rot is the committed
# copy — and the import test above cannot see that rot, because a client
# generated from last week's document imports perfectly. This regenerates
# from hanzoai/openapi and fails if what is in git is not what the document
# produces. Needs SPEC_TOKEN — hanzoai/openapi is private.
#
# It is the last of the seven clients to get one, and it was the only one
# with no in-repo way to regenerate at all: the driver lives in
# hanzoai/openapi, so without scripts/generate.sh this repo could not even
# ask the question.
run: ./scripts/generate.sh --check
# THE CLIENT LANE — this repo is a PROJECTION of one API document at one version.
#
# hanzoai/cloud's release sends `repository_dispatch: spec-update` carrying
# (version, sha, spec_sha256); hanzoai/ci fetches openapi.yaml AT THAT SHA,
# REFUSES if the bytes hash to anything else, runs `generate:`, writes
# `.spec-lock` beside the code, and — only after the `test:` block below has
# passed over exactly those bytes — commits and cuts a patch.
#
# The document moved from hanzoai/openapi `hanzo.yaml` (hand-merged, 1742 paths,
# fed by nothing, and NOTHING HAS EVER SENT the spec-update this repo listens
# for) to hanzoai/cloud `openapi.yaml` — smaller (1058 paths), emitted from the
# code by each app's own router, and gated on every cloud release. A smaller true
# document beats a larger unverified one.
client:
generate: ./scripts/generate.sh
version: "pyproject.toml:grep -m1 '^version' pyproject.toml | sed -E 's/.*\"(.*)\".*/\\1/'"
+6
View File
@@ -40,4 +40,10 @@ if [ -z "$OPENAPI" ]; then
fi
fi
# THE DOCUMENT AS AN ARGUMENT. hanzoai/ci's client: lane fetches openapi.yaml at
# the sha hanzoai/cloud just deployed and exports SPEC; the driver projects THAT
# rather than the checkout's own hanzo.yaml. With SPEC unset nothing changes —
# a maintainer regenerating by hand still gets the checkout's document.
if [ -n "${SPEC:-}" ]; then set -- --spec "$SPEC" "$@"; fi
exec python3 "$OPENAPI/generate.py" python --repo "$PWD" "$@"