408 Commits
Author SHA1 Message Date
zeekay 46217c695a fix(review): the review job carries HANZO_CI_TRUSTED, and its step asks it
fork-is-sandboxed has failed every run of this repository since 600a2d1
added the review job without it ("5 job(s), 4 carry HANZO_CI_TRUSTED"),
so no ci or git-runner image has published since. The job already skips
a fork; its credentialed step now carries the same guard as every other.
All 31 entries of hanzo.yml's test block pass locally.
v2
2026-10-20 08:09:24 -07:00
zeekay e1c4e4876b perf(runner): the image carries the actions build.yml names, cloned
A forge runner lives one job and began each with an empty act cache, and
act clones an action with go-git, full history, before the first step:
forge run 16634 spent 156 of each job's 161 s of setup cloning
setup-node (72 s), setup-go (42 s), setup-bun (20 s) and checkout
(17 s), the three it then skipped included.

The image now clones each into ~/.cache/act/<sha256 of the uses string>
with every ref the remote advertises, so the runner opens it and fetches
what moved since the build. Timed with act's own clone executor: 0.2 to
1.1 s each, against 2 to 90 s cold; with the branches and tags alone it
still fetched every pull request's objects, 10 to 40 s each. It adds
some 340 MB to the image.

hanzo.yml's runner-preseeds fails when build.yml names an action the
image does not carry.
2026-10-20 08:05:25 -07:00
Hanzo Devandzeekay 6de3b93fcf build: oci.hanzo.ai pulls through to ghcr.io, so nothing copies an image there
The registry reads ghcr.io for any name it does not hold, so an image
pushed to ghcr.io/<org>/<app> is already oci.hanzo.ai/<org>/<app>, by
digest and by tag. The second copy, and the login that armed it, go.
The pins this run prints name oci.hanzo.ai, where the fleet pulls.
2026-10-20 07:49:22 -07:00
zeekay a9b8273cec perf(certclaims): one grep per rule over every file, not two per file
The gate forked grep twice for every tracked file. On hanzo-inc/platform
(1,286 files) that was 26 s here and 80 s on a forge runner, where every
fork crosses the sandbox (run 16634, Structural invariants), and 45 s on
hanzo-inc/cloud. Each rule now runs once over the whole list and only the
lines it matches are read one at a time: 0.1 s and 0.4 s.

The findings are the same lines: old and new agree on platform, app, ci,
universe (11 findings, exit 1 both), cloud and hanzo.ai, and
certclaims_test.sh passes. grep takes --null, not -Z, which BSD grep
reads as --decompress.
v2.1.9
2026-10-20 07:49:08 -07:00
zeekay fd1aa150d9 perf(cache): parts move eight at a time, and a restore says where its time went
hanzo-inc/app restores five parts; four at a time made that two rounds. The
restore line now names fetch, verify, unpack and place.
v2.1.8
2026-10-20 07:42:29 -07:00
zeekay 28c0db22ea perf(site): ask the origin's sample all at once, hash on every core, and say where the time went
The twenty sampled assets were asked one after another, each a round trip
through the edge to the origin: 10.4 s from dgx, seconds of every publish
from a runner. They go out together now, and one that does not answer still
sends everything. Hashing runs one batch per core into a file of its own,
sorted after, so the record is the same bytes in any order. A last line,
`timing:`, names each phase's seconds.
2026-10-20 07:42:29 -07:00
z 39e2fd4a70 site: copy: false publishes every site from its own bytes, all at once
Hosts whose plane never completes a copy (SITE_COPY_FROM) shipped pages without
their assets, so callers split one export into a step per host and published
them in series. With copy: false each site uploads its own files and all of them
run in parallel. The default keeps the copy.
2026-10-17 23:28:54 -07:00
zeekay a53bab567d fix(review): a reviewer with no identity warns and lets the release go
gate.sh exited 1 when KMS refused the CI credential or held no
review/IAM_CLIENT_ID for the org, and cicd needs review, so every
caller of build.yml published nothing from 11:18Z (hanzoai/console
8fa5c86b23 and f83eecedeb). A missing identity is a fact about the CI
credential, not a view of the change: both now warn that the reviewer
did not run and exit 0, as an unreachable store already did. A reviewer
that runs and refuses still exits 1.

review_test.sh drives gate.sh against a stub store for both refusals,
and through the gate against a refusing reviewer.
2026-10-17 09:45:39 -07:00
zeekay c426a5d538 docs: a caller grants what build.yml declares
The README's caller named .hanzo/workflows/build.yml@v1, a path that
resolves nothing, and granted no permissions, so a repo started from it
failed at startup once build.yml declared contents, packages and
id-token: write. The caller shown now names .github/workflows/build.yml@v2
and grants those three on its job.
2026-10-17 04:19:52 -07:00
zeekay eefd9dc1dc fix(cache): a Go cache save keeps what its run used
Every save archived the whole build cache it restored plus what it
compiled, so nothing ever left it. hanzo-inc/cloud's four gate shares
grew to 7-8.5 GiB of zstd, 27 GiB unpacked; the runner pods restoring
them were evicted at their 80Gi ephemeral-storage limit mid-gate, and
every release since run 1009 was skipped.

Restore now stamps each restored entry two hours old, past the hour in
which Go leaves a used entry's mtime alone, and save drops every entry
still older than the restore. The cut archives live under the name
suffix -used, so none written before is restored again.
v1.0.133
2026-10-16 23:18:26 -07:00
zeekay 600a2d1feb perf(ci): review the diff beside the tests
A stalled git.hanzo.ai gives up in twenty seconds and the run
continues on github.com. Publishing waits for the review. The
tests do not.

Testing: bash bin/get_test.sh && bash .hanzo/review/review_test.sh
Risk: high. A refused diff publishes nothing. A reviewer that cannot be reached still publishes.
How to revert: git revert this commit and move the v2 tag back to 5262d63804.
2026-10-16 23:03:30 -07:00
zeekay 21f008dc0c fix(cut): a crate's Cargo.lock moves with its Cargo.toml
The client lane's cut rewrote the version in Cargo.toml and left the
crate's own entry in Cargo.lock at the old number, so every `--locked`
build of the cut failed: hanzoai/cli 8.5.729 and 8.5.730 published no
binaries. The cut now moves that one entry, the crate's own, and refuses
when the lock does not name the new version afterwards.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.132
2026-10-15 08:55:23 -07:00
zeekay 71d8efdaed perf(test): a split gate checks the repository's invariants once
ignoretracked, modsize, conflictmarkers, vendormark and certclaims are
facts about the whole repository, so a call naming a share runs them
only in the share that gates on "everything but" (`!`). certclaims alone
took 5 of hanzo-inc/cloud's minutes in each of its five shares.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.131
2026-10-15 07:06:13 -07:00
zeekay cf9f1c4fd9 fix(tools): on Hanzo Git, fetch the tools at the ref the caller pins
Hanzo Git sets no GITHUB_WORKFLOW_REF, so the step could not find the
caller's workflow file and fetched the tools at the moving `v1`. A caller
pinned to a newer release then ran its steps against older tools:
hanzo-inc/cloud at v1.0.128 failed in three minutes, "missing: cache".
With the file unknown, the pin is the one this repository's workflows
all name, when they name exactly one; otherwise nothing changes.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.129 v1.0.130
2026-10-15 06:53:18 -07:00
zeekay cce63d6a49 feat(test): gates: "!a,b" runs every entry but those
The share that names the others' entries with a leading `!` runs the
rest, so a test entry added to hanzo.yml later is gated by default
instead of belonging to no share.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.128
2026-10-15 06:41:26 -07:00
zeekay 5351ba8f81 feat(test): a share of the gate only gates
A call naming `gates` skips the publish job: what it would publish is the
whole repository's, and five parallel shares would each spend a runner on
it. hanzo-inc/cloud's publish job has nothing to publish and took three
to six minutes of setup after the gate.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.127
2026-10-15 06:38:27 -07:00
zeekay 7e1b11b216 feat(test): gates runs a named share of hanzo.yml's test block
A gate too long for one runner can now run as parallel jobs, each naming
its share of the `test:` entries; hanzo.yml stays the one list, and a name
it does not declare is refused rather than dropped. hanzo-inc/cloud's gate
is one job of 142 minutes: its fleet check alone takes 58 and its unit
tests 45, and nothing orders them.

Each share keeps its own Go cache key, so parallel jobs each save what
they compiled instead of one finding the key another already took.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
v1.0.126
2026-10-15 06:37:32 -07:00
zooqueenandHanzo Dev 805c91c7bb fix(site): a preview the edge does not serve reads back from cloud's record
A client preview answers 404 to everyone until its client signs off, so the
public read-back cannot reach its bytes. On a 404, and only a 404, the
release is read back from cloud with the credential that published it: the
deployment this run completed must be live, hold the files and bytes this
run sent, and be the one the project serves. Any other answer from the host
still fails the deploy. The hanzo.id redirect a forward-auth gave is no
longer accepted.

Co-authored-by: Hanzo Dev <[email protected]>
v1.0.125
2026-10-15 05:27:13 -07:00
zeekay 386b7a2694 feat(cache): bin/cache carries any directory between runners; gocache is its Go caller 2026-10-09 04:03:07 -07:00
zeekay 1983074a46 chore(ci): merge the publish wait, cut on v2.1.6 as v2.1.7 2026-10-04 00:20:05 -07:00
zeekay 5262d63804 fix(ci): a push waits for the running publish instead of cancelling it v2.1.7 2026-10-04 00:19:53 -07:00
zooqueenandHanzo Dev e8a9edfa44 bin/kms writes the ID token's mask to stderr, so what a caller captures is the secret alone, and both callers refuse a capture that holds more without printing it.
Co-authored-by: Hanzo Dev <[email protected]>
v1.0.124
2026-10-02 14:26:29 -07:00
zeekay 6523c8623c The KMS step reads the owner from env, which keeps its script under the expression limit
A run: string holding any ${{ }} is evaluated as one expression, and GitHub
refuses one past 21000 characters.
2026-09-30 07:26:51 -07:00
zeekay 641bdcb728 Grant the reusable the run's ID token 2026-09-30 07:24:18 -07:00
zeekay b3ed274349 A run logs in to KMS with its own ID token
The reusable asks for id-token: write and exchanges the run's Actions ID token
at IAM for the CI identity its repository's account speaks for, so no GitHub
repository or org needs KMS_CLIENT_ID. bin/kms, and the site action through it,
does the same. A stored client remains the credential on Hanzo Git, which
issues no ID token.
2026-09-30 07:23:53 -07:00
zeekay 30af8df1e7 fix(site): the sampled asset check does not glob the brackets in a route's chunk name v1 2026-09-30 07:09:57 -07:00
zeekay fece5973c3 feat(site): identical objects of a sibling site are copied by the plane, not sent 2026-09-30 06:56:52 -07:00
zeekay 524eac6ff1 fix(site): read the previous manifest from the host that serves the prefix
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-30 01:12:27 -07:00
zeekay fb8ace6f61 perf(site): upload each batch through one parallel curl; retry only what was not accepted
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-30 01:12:24 -07:00
zeekay cae4ed5d93 fix(site): the action names the ref whose bin/site runs; the completion is asked again on a 5xx
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-30 01:12:22 -07:00
zeekay 84b05273ab feat(site): publish only what changed; sites publish in parallel
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-30 01:12:18 -07:00
zeekay 44a36738bf imgver reads the published floor with the KMS GitHub token when there is no GH_PAT
The automatic token reads only a package its repository was granted, so a
caller with no GH_PAT whose image is an org package it was never linked to
read nothing published, and with no declared version it had no version at
all. The KMS GitHub token is the rung the registry logins already take
between the two, and imgver now climbs the same ladder.
v2.1.6 v1.0.123
2026-09-30 00:48:10 -07:00
zeekay aada6c224f HANZO_CI_TRUSTED is spelled out in each job, because the forge keeps env as a raw node per job v2.1.5 v1.0.122 2026-09-30 00:24:57 -07:00
zeekay 72ae7a720a Public repositories' commit messages state the engineering reason 2026-09-29 22:08:48 -07:00
zeekay 50e93e3138 A pull request from a fork runs on a one-job runner, reads no credential, and publishes nothing v1.0.121 v2.1.4 2026-09-29 21:57:52 -07:00
Hanzo Dev 312d2f2701 The site deploy token is read in the publish role only, and never for a pull request
The KMS step exported HANZO_DEPLOY_TOKEN for any caller that declares a
site, in both roles, so the test role held it while it ran the repo's own
code, and a branch pushed for a pull request could print it. The token is
sealed once per org and publishes every site of that org. The site lane
reads it only in the publish role off a pull request, so the fetch is
there and nowhere else.
2026-09-29 13:04:33 -07:00
zeekay 909203f2ef Seed the PyPI lane's clean venv with pip
uv venv makes a venv without pip, so the clean install before upload
answered "No module named pip" for every wheel and the lane published
nothing (hanzoai/python-sdk v8.5.162, run 36508815355). The install and
import move into alone(), which bin/alone_test.sh lifts out of the
workflow and runs against wheels it builds.
2026-09-29 10:11:11 -07:00
zeekay 4b737d13ec The tools checkout fetches a caller's pinned sha instead of cloning it as a branch. 2026-09-28 18:30:31 -07:00
zeekay 58fd5da11e The cut pushes to github.com with a GitHub token and to the forge with the IAM token. 2026-09-28 18:13:13 -07:00
zeekay 80a1a74f48 Probe for libclang under its versioned names
Ubuntu's ldconfig cache lists libclang-18.so.18 and libclang-18.so,
never a bare libclang.so, so the probe missed the copy the runner image
carries and fell through to sudo apt-get, which the runner refuses
("effective uid is not 0"). hanzoai/decision run 36307074308 died there.
clang-sys searches libclang-*.so itself, so no LIBCLANG_PATH is needed.

Claude-Session: https://claude.ai/code/session_018Gt5TvrE2rxXuZikWSauv3
v2.1.3 v1.0.120
2026-09-27 02:22:28 -07:00
zeekay 3ceb54383c Report each enqueue assertion so the tally counts it
The suite printed only a summary line, and the reusable's test tally,
which counts per-result lines, read that as a runner that ran nothing
and failed main.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
v2.1.2 v1.0.119
2026-09-24 10:47:16 -07:00
zeekay 5339715989 Freeze the enqueue suite's clock
The fake clock rode on bash's ticking SECONDS, so a starved runner
spent 14 real seconds of the 30-minute budget and the suite failed
at 1786. Unset, SECONDS is a plain variable only the fake sleep moves.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:44:39 -07:00
zeekay 1efafa8f78 Back off and obey Retry-After at the build door
A fixed 30-second poll for 20 minutes was measured short: hanzoai/cd
asked 41 times on 2026-09-23 and got 429 every time, then failed a
publish whose tests had passed. Every waiting repo of an org also hit
the door in the same second a slot freed.

The wait now doubles from 15s to a 120s ceiling with jitter, takes a
numeric Retry-After as a floor, and stops at 30 minutes, since a build
holds its slot for up to ~39. bin/enqueue_test.sh lifts the function
out of build.yml and runs it against a faked wire and clock.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:31:07 -07:00
zeekay e607b8bfc6 Read back only the answer that carried the body
curl -L keeps every hop's headers, and the check took the last
content-length it saw. A root that redirected once before a
chunked page was measured against the redirect's 17 bytes, which
failed a healthy publish of dev-hanzo-ai three times over.

Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
2026-09-24 10:29:00 -07:00
zeekay 5e2a320eba Link the runner package to this repository
ghcr.io/hanzoai/git-runner is linked to no repository, so this repo's
workflow token cannot list its versions and bin/imgver would publish
9.0.0 over and over. The source label links it on the next push.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:19:55 -07:00
zeekay 4bb3d8c3dd Pin later steps to the restored Go cache
hanzo-inc/cloud's gates name their caches with ${GOCACHE:-...}, so they
built into a directory nothing restored and nothing saved. Restore now
exports the two directories it used, and every later step reads them.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:18:42 -07:00
zeekay f6d6172c9b Return from delegate once the door queues
The publish job held its runner up to 40 minutes polling the registry so
it could write the version tags itself: 289 runner-minutes in 48 hours
spent waiting. The door now writes those tags in the same push and says
so in its answer, so the job ends on the 202 and names the build to
watch. A tag the door does not promise is still written here, and
`wait: 'true'` holds the job for a caller that uses the image next.

The first ref is taken by expansion: `echo | head -1` under pipefail
failed hanzoai/ci's own publish with a broken pipe.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:12:12 -07:00
zeekay 5d6d7858e4 Carry the Go cache between runners
A runner lives one job, so no Go cache was ever restored: 142 Go-gate
minutes in 48 hours were 31 downloading modules and 69 compiling, and a
warm pass compiled commerce's vet in 15s against 198s cold.

The cache lives in the org's own bucket behind cloud's /v1/s3, reached
with the IAM token the KMS step already holds, so the server keeps one
org out of another's and CI holds no bucket key. Go trusts a restored
cache without re-hashing it, so only the default branch and tags write.

setup-go's own cache is off: it is GitHub's store, and the forge has none.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:12:11 -07:00
zeekay 09c0fad22f Bake the toolchain into the runner image
Every job downloaded Go and Node through setup-go and setup-node, 110
runner-minutes in 48 hours, because nothing was in the tool cache they
read. Go 1.27.1 and Node 22 now sit there in their layout, beside
corepack's pinned pnpm, helm, crane and zstd.

The image is an images: entry built at the build door on every push.
Its forge lane stopped when Actions went off on the mirror, and a
runner has no docker daemon to run it anyway.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:12:10 -07:00
zeekay e556ccfd53 Declare the secrets a caller may pass
`secrets: inherit` passes nothing to a reusable workflow in another
org, and a caller may only name a secret the reusable declares. Every
luxfi, zooai, lux-apps, hanzo-inc and hanzo-apps caller therefore
reached the KMS step with KMS_CLIENT_ID empty and delegate refused on
an empty HANZO_API_TOKEN. All five are optional, so a hanzoai caller's
`inherit` is unchanged.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-24 10:06:55 -07:00