Files
cli/hanzo.yml
zeekay a63427da5e dev: Gen 6741119; link tests on a tmux of their own; attested installs
hanzo-gen moves to 6741119: programs by full path only, a home read from
the environment alone, run homes that survive no ending, the meter by
brand. So the CLI:

- link names the tmux server it works in (Tmux); a link uses the default
  socket, and its tests start one at a temp socket and kill it after, so
  the suite never reaches a person's tmux
- holds the Store's one config behind a lock held for the whole ask, so
  an identity IAM moves during a heartbeat is never written back over;
  the profile and the meter are asked under it, the meter as the
  session's brand (Seam::open takes the brand)
- finds npm and uv by full path, and hands the __mcp proxy its root
- install.sh checks an attested tool's download (HANZO_INSTALL_SIGNER)
  against its workflow and tag with a signed-in gh, refuses a mismatch,
  and says so when it could not check
- the console's screenshot test writes into a temp dir, not /tmp
- hanzo.yml runs make test with HOME empty and read-only, then empty and
  writable and still empty after
2026-10-16 10:45:00 -07:00

93 lines
5.3 KiB
YAML

# Canonical CI config for hanzoai/cli — read by the hanzoai/ci reusable
# (.github/workflows/cicd.yml) and by platform.hanzo.ai. This repo had neither
# this file nor a Makefile, which is why `genspec` — the one thing that keeps
# the CLI's command surface honest — was reachable only by a maintainer typing
# it. A capture nothing re-asks goes stale silently, and this one did: it was
# 7 operations short and 8 operations wrong against the document cloud had
# already committed.
#
# NO images: the CLI is a binary a person installs, not a container a cluster
# runs. NO binaries: this repo's binaries are the five-target cross-build in
# .hanzo/workflows/release-matrix.yml (cargo-zigbuild + a vendored macOS SDK) —
# that is genuinely per-repo build logic and it is already correct; the generic
# Go-shaped lane here cannot produce it. NO deploy: nothing here rolls out.
#
# That path is `.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. Both CI files in this repo live under `.hanzo/workflows/` for that
# one reason.
#
# What CI owns for this repo is the two gates below and the client lane.
client:
# The document, by value at a pinned ref: the lane fetches openapi.yaml at the
# ref .spec-lock names and refuses on a digest mismatch. It does not choose the
# ref and does not write the lock — on a push it is check-only at the locked
# one. What MOVES the pin is the nightly re-pin in .hanzo/workflows/drift.yml,
# which reads hanzo-inc/cloud's main and commits the result once `make verify`
# rules on it. Reading api.hanzo.ai here would name whatever is serving now
# rather than a release.
spec: { repo: hanzo-inc/cloud, path: openapi.yaml }
generate: ./scripts/generate.sh
# PATCH only, derived. The command surface is the document's; this crate only
# restates it, so it never earns a minor or a major.
version: "Cargo.toml:grep -m1 '^version' Cargo.toml | sed -E 's/.*\"(.*)\".*/\\1/'"
# THE CAPTURE GATE is NOT here, and that is the point: hanzoai/ci's `client:` lane
# asks it for all eight client repos in one place, on every push — fetch the
# document .spec-lock names, refuse if the ref moved under it, re-run the
# derivation, refuse any delta, write nothing. `make spec-check` is the same
# question a human asks locally, through the same script.
#
# THE DRIFT GATE is a different question — a capture can equal its inputs exactly
# and still describe a surface production stopped serving, or miss a product
# production started — and it IS here, inside `cargo test`, because it stopped
# needing the network. It rules on `spec/live.json`: the raw answers the live
# server gave to every probe and to a nonsense-sibling CONTROL for each, captured
# by `make live` and committed. So the push gate asks both directions on every
# push, in the one lane, with nothing to fail for a reason that is not drift.
#
# It used to need the wire, and that is what made it fragile: a gate needing the
# network in CI gets switched off, and this one first sat in the list below where
# a failure in the client step or the KMS login meant it was never reached at all.
# It was never asked once that way.
#
# What still needs the wire is the OTHER half — has production moved under the
# committed evidence? — and that has its own job, its own triggers and nothing
# else in it: `.hanzo/workflows/drift.yml`, nightly and on a click, `make
# live-check`. One predicate per place, and neither shares its fate with anything.
#
# `make test`, never a bare `cargo test`: the gates below exec maintainer
# binaries that `cargo test` does not build, and a gate that dies on NotFound
# reads exactly like a gate that passed. One invocation, in the Makefile.
#
# THE LINT GATE HAS TO INSTALL WHAT IT LINTS WITH. `dtolnay/rust-toolchain@stable`
# provisions rustc and cargo and no components, so `cargo clippy` answered
# "'cargo-clippy' is not installed for the toolchain" and every main build went
# red on it — six in a row, while `make test` above passed every one of them. A
# gate that cannot run is worse than no gate whichever way it lands: red, it
# teaches people the build lies; green, it teaches them nothing at all. It asks
# for the component first now, which is idempotent and costs a cached download.
#
# THE SUITE NEVER TOUCHES A HOME, and the gate says so the way hanzoai/gen's
# does: it runs against an empty HOME it cannot write, then against an empty one
# it can, which must still be empty after. A runner can be a person's machine,
# and a test that wrote there once rewrote that person's agent configs. Cargo
# and rustup keep their own homes, named before HOME moves.
test:
- name: cargo-test
run: |
set -euo pipefail
export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" RUSTC_WRAPPER=
export PATH="$CARGO_HOME/bin:$PATH"
unset XDG_DATA_HOME XDG_CONFIG_HOME XDG_CACHE_HOME XDG_STATE_HOME
ro=$(mktemp -d); tmp=$(mktemp -d)
trap 'chmod u+w "$ro"; rm -rf "$ro" "$tmp"' EXIT
chmod 0555 "$ro"
HOME="$ro" make test
HOME="$tmp" make test
left=$(find "$tmp" -mindepth 1 | head -20)
[ -z "$left" ] || { echo "::error::the test suite wrote into HOME:"; echo "$left"; exit 1; }
- name: clippy
run: rustup component add clippy && cargo clippy --locked --all-targets -- -D warnings