mirror of
https://github.com/hanzoai/cli.git
synced 2026-10-30 15:00:27 +00:00
The nightly re-pin regenerated correctly and landed nowhere. `make verify` said "no drift", the commit was made, and the push died on `remote: error: Not allowed to push to protected branch main`, `pre-receive hook declined`. The forge knows the actions token as User -2 and refuses it at a protected branch however the rule is written; FORGE_TOKEN, which every client lane in hanzoai/ci already pushes with, is accepted. Setting that credential is not enough on its own. `http.extraHeader` is multi-valued and a url-specific entry does not displace a broader one, so git sends every header that matches and the forge honours the first Authorization it reads — actions/checkout leaves one behind, and adding a second only puts the refused token in front. The step now clears what checkout wrote before setting its own, and pushes to GITHUB_SERVER_URL, the host the run belongs to and the one that takes the token. FORGE_TOKEN also reads hanzoai/cloud, so the step carries one credential rather than two.
164 lines
9.5 KiB
YAML
164 lines
9.5 KiB
YAML
name: Drift Gate
|
|
|
|
# THE QUESTION THIS JOB ASKS IS NOT THE ONE `cargo test` ASKS, and that is the
|
|
# whole reason it still exists.
|
|
#
|
|
# The gate itself — the command tree and the server held against each other in
|
|
# BOTH directions — is now HERMETIC: it rules on `spec/live.json`, the checked-in
|
|
# evidence, and runs inside `cargo test` on every push with no network at all.
|
|
# That was the fix for the oldest problem this pipeline had: a gate that needs the
|
|
# network in CI gets switched off, and a switched-off gate is worse than none.
|
|
#
|
|
# What no committed file can answer is whether PRODUCTION STILL SAYS SO. This
|
|
# drift is almost never introduced by a commit here — the route table moves when
|
|
# hanzoai/cloud deploys — so a repo that only checks itself when it changes learns
|
|
# about it at the next release, from a user. `make live-check` re-asks the running
|
|
# server, with a control for every probe, and fails when a VERDICT moved: not when
|
|
# a 401 became a 403 (the same fact about the same route, and a gate that goes red
|
|
# for that is a gate people learn to switch off), but when something present
|
|
# became absent, or absent became present, or a product appeared or vanished.
|
|
#
|
|
# The remedy it prints is one command: `make live`, commit `spec/live.json`, and
|
|
# read what the hermetic gate then says.
|
|
#
|
|
# WHY IT IS ITS OWN JOB. hanzo.yml's `test:` block runs inside hanzoai/ci's one
|
|
# lane, AFTER the client step (fetch the release document, refuse a moved ref),
|
|
# the KMS login and the toolchain provisioning. A job that runs last in a shared
|
|
# lane does not report on the server — it reports on everything ahead of it, and a
|
|
# failure anywhere upstream means the question is never asked at all. It was never
|
|
# asked once that way: at the moment this file was first rewritten, `drift.yml`
|
|
# had zero runs and the last three CI/CD runs on main were red.
|
|
#
|
|
# PUSH AND PULL_REQUEST ARE NOT TRIGGERS ANY MORE. On those, `cargo test` already
|
|
# rules on the evidence, hermetically, in hanzoai/ci's lane — so re-asking
|
|
# production on every push would add nothing except the power for somebody else's
|
|
# deploy to redden this repo's builds. The SCHEDULE is the half that matters, and
|
|
# `workflow_dispatch` is here so the answer is one click away when somebody asks
|
|
# "is the tree still true?" without pushing anything to find out.
|
|
#
|
|
# `release-matrix.yml` runs `make verify` — the hermetic gate — at the one moment
|
|
# the claim reaches users, before a single artifact is minted. That is not a
|
|
# duplicate of this job; it is the other question, at the moment it counts.
|
|
#
|
|
# `.hanzo/`, not `.github/`: the runners belong to git.hanzo.ai and github.com has
|
|
# none, so a pipeline under `.github/` queues forever instead of building.
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '17 6 * * *'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
live:
|
|
name: The live server still says what the committed evidence records
|
|
runs-on: hanzo-build-linux-amd64
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
# `toolchain: stable` is STATED, not inferred from the `@stable` ref.
|
|
# dtolnay/rust-toolchain reads the branch name out of `github.action_ref`
|
|
# to decide the toolchain — that is GitHub-runner behaviour, and act_runner
|
|
# on our own forge does not reproduce it, so the action received an empty
|
|
# input and failed with "'toolchain' is a required input".
|
|
#
|
|
# It only ever broke ONE target, which is why it looked like a linux-amd64
|
|
# problem: linux-amd64 is the sole `host: true` entry, so it alone runs
|
|
# `make test` -> the drift gate, and the gate's own workflow carried the
|
|
# same step. Four targets shipped, the fifth did not, and the release
|
|
# looked healthy while missing the only binary our own amd64 sandbox can
|
|
# run.
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
toolchain: stable
|
|
- run: make live-check
|
|
|
|
# THE PIN MOVES BY PULLING, and this is the only thing that moves it.
|
|
#
|
|
# It used to be a push: hanzoai/cloud's release was meant to send
|
|
# `repository_dispatch: spec-update` and hand this repo the document at the
|
|
# sha it deployed. Nothing ever arrived. The CLI has no `.github/workflows`,
|
|
# so GitHub recorded the event and ran nothing; the forge has no
|
|
# repository_dispatch endpoint to send it to; and a push into this repo puts
|
|
# hanzoai/ci's client lane in check-only at the LOCKED ref, so even a
|
|
# delivered event could not have written a new one. Across 104 runs the pin
|
|
# moved exactly once, by hand.
|
|
#
|
|
# So the side WITH runners asks. This job already has the network and the
|
|
# toolchain and already runs nightly; reading one ref and regenerating costs
|
|
# it one more step and needs no dispatch, no new secret and no second
|
|
# mechanism. It runs even when `live-check` failed — production having moved
|
|
# under the committed evidence is precisely when the pin wants moving.
|
|
#
|
|
# IT READS main, NOT THE NEWEST TAG. The document is emitted from cloud's
|
|
# source, so what it says is a fact about that source and the ref that names
|
|
# it is main. A tag names a SERVICE RELEASE, which is a different thing and
|
|
# lags: measured the day this changed, cloud's newest tag was four commits and
|
|
# two hours behind a main carrying 256 newly typed operations, and re-pinning
|
|
# to it would have moved this repo's pin BACKWARDS. Two notions of "the
|
|
# document" is the same defect as two documents.
|
|
#
|
|
# A sha, never `main` itself, because a branch name is not a value: two runs
|
|
# of one commit have to read the same bytes for the digest below to mean
|
|
# anything.
|
|
#
|
|
# A RE-PIN IS NOT ALWAYS SAFE TO TAKE, and `make verify` is what decides.
|
|
# Cloud's source states a route the moment it is written and production
|
|
# deploys it some time later, so the document can name a route the running
|
|
# server does not serve; the gate calls that AHEAD and refuses once it runs
|
|
# further ahead than the ceiling allows. So verify runs on the regenerated
|
|
# tree BEFORE the commit, and the push happens only behind it. When it
|
|
# refuses, nothing is written and the red nightly carries the gate's own
|
|
# reasons — which is what a person needs to read.
|
|
#
|
|
# It COMMITS TO MAIN. This used to push a `repin` branch and open a pull
|
|
# request; the request was never openable, because this repo has the pull
|
|
# unit turned off — the forge answers 500 to `POST /pulls` and 404 to `GET
|
|
# /pulls` when it is off — so the step failed every night it had anything to
|
|
# do, after pushing a branch nobody could turn into a patch. Two ways to
|
|
# land a re-pin, one of which does not exist, is one too many.
|
|
#
|
|
# FORGE_TOKEN READS THE DOCUMENT AND PUSHES THE RESULT — one credential
|
|
# for the whole step. The actions token cannot do the second half: the
|
|
# forge knows it as User -2, and a protected branch refuses it however the
|
|
# rule is written. It is the same credential, spelled the same way, that
|
|
# hanzoai/ci's client lane uses to land a regenerated client.
|
|
- name: Re-pin to hanzoai/cloud's main
|
|
if: always()
|
|
env:
|
|
FORGE: https://git.hanzo.ai/v1
|
|
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
[ -n "${FORGE_TOKEN:-}" ] || { echo "::error::the re-pin reads hanzoai/cloud and pushes the result here, and needs FORGE_TOKEN"; exit 1; }
|
|
api() { curl -fsSL -H "Authorization: token $FORGE_TOKEN" "$@"; }
|
|
head=$(api "$FORGE/repos/hanzoai/cloud/branches/main" \
|
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["commit"]["id"])')
|
|
pinned=$(sed -n 's/^ref=//p' .spec-lock)
|
|
if [ "$head" = "$pinned" ]; then
|
|
echo "the pin is hanzoai/cloud@$pinned, and that is main"
|
|
exit 0
|
|
fi
|
|
echo "hanzoai/cloud@$pinned -> @$head"
|
|
api "$FORGE/repos/hanzoai/cloud/raw/openapi.yaml?ref=$head" -o /tmp/openapi.yaml
|
|
printf 'ref=%s\nsha256=%s\nrepo=hanzoai/cloud\npath=openapi.yaml\n' \
|
|
"$head" "$(sha256sum /tmp/openapi.yaml | cut -d' ' -f1)" > .spec-lock
|
|
# `make spec` fetches that ref AGAIN and refuses if the bytes moved under
|
|
# the digest just written — the lock checking itself — then regenerates
|
|
# the surface and re-captures the evidence in the same act.
|
|
SPEC_TOKEN="$FORGE_TOKEN" make spec
|
|
make verify
|
|
git config user.name hanzo-dev
|
|
git config user.email [email protected]
|
|
git commit -am "spec: re-pin to $head"
|
|
# ONE CREDENTIAL ON THE WIRE. `http.extraHeader` is multi-valued, and a
|
|
# url-specific entry does not displace a broader one: git sends every
|
|
# header that matches and the forge honours the first Authorization it
|
|
# reads. actions/checkout leaves its own behind, so merely adding ours
|
|
# puts the actions token in front — and this step died on `Not allowed
|
|
# to push to protected branch main` with the credential that IS allowed
|
|
# sitting unused in the environment. Clear first, then set one.
|
|
git config --local --name-only --get-regexp '^http\..*extraheader$' 2>/dev/null \
|
|
| while read -r k; do git config --local --unset-all "$k"; done || true
|
|
git config --local http.extraheader "Authorization: token $FORGE_TOKEN"
|
|
git push "${GITHUB_SERVER_URL:-https://git.hanzo.ai}/${{ github.repository }}" HEAD:main
|
|
echo "main is pinned to hanzoai/cloud@$head"
|