ci: the gate, and the end of the bootstrap exception
One script, run identically in CI and locally. Covers the Rust crate, the wasm artifact, wire compatibility both directions, and the Go build. It fails if the embedded .wasm differs from a fresh build -- go:embed will otherwise embed a stale module and the tests pass against it, which surfaces much later as a confusing behaviour difference. The first branch merged under an explicit bootstrap exception because CI had nothing to gate. It has something now.
3 files changed,
+86
−2
.amadan/README.md+36 −0.amadan/ci.d/10-gate+44 −0AGENTS.md+6 −2
CI failed — run details
diff --git a/.amadan/README.md b/.amadan/README.md| new file mode 100644 |
| index 0000000..37695eb |
| --- /dev/null |
| +++ b/.amadan/README.md |
| @@ -0,0 +1,36 @@ |
| +# CI |
| + |
| +`ci.d/10-gate` is the only CI this repo has. It runs on every push to the |
| +amadan remote, and it is the same script you can run locally — one |
| +definition rather than two that drift. |
| + |
| +It requires a Rust toolchain with the `wasm32-unknown-unknown` target, Go |
| +with `CGO_ENABLED=0`, and Node with `fixtures/` installed. The gate sources |
| +`~/.cargo/env` itself and checks every tool up front, so a missing |
| +dependency fails on the first line with its own name rather than fifty |
| +lines in as `cargo: command not found`. |
| + |
| +## The runner |
| + |
| +Jobs are claimed by an `amadan runner` on the machine that has one. Two |
| +things about it were wrong when this repo was created, and both fail |
| +silently rather than loudly: |
| + |
| +- **The runner must be told to serve this repo.** `amadan runner -repo …` |
| + is an allowlist. A repo nobody listed gets `amadan ci status` exit 3 |
| + forever — "no status recorded", which reads like the gate has not run yet |
| + rather than like nothing will ever run it. |
| +- **The runner's `PATH` is the systemd unit's, not your shell's.** |
| + `~/.cargo/bin` is not on it by default. |
| + |
| +Both were fixed on `DOCS-T1` on 2026-08-30 by editing |
| +`~/.config/systemd/user/amadan-runner.service` and restarting. A restart |
| +abandons any job already running, so check it is idle first. |
| + |
| +The gate fails if `internal/core/testdata/docs_core.wasm` differs from a |
| +fresh build, because `go:embed` will otherwise embed a stale module and the |
| +tests will pass against it. |
| + |
| +It also fails if regenerating the Yjs fixtures changes them. Fixtures are |
| +the wire contract, not an output: a deliberate rule change regenerates them |
| +in the same commit as the change. |
diff --git a/.amadan/ci.d/10-gate b/.amadan/ci.d/10-gate| new file mode 100755 |
| index 0000000..9e6547c |
| --- /dev/null |
| +++ b/.amadan/ci.d/10-gate |
| @@ -0,0 +1,44 @@ |
| +#!/usr/bin/env bash |
| +# The only CI this repo has. Everything a branch must pass before it can |
| +# squash-merge, in one script so CI and what you run locally are one |
| +# definition rather than two that drift. |
| +set -euo pipefail |
| + |
| +# rustup installs to ~/.cargo/bin, which is not on the PATH of a systemd |
| +# user unit and is not on the PATH of a login shell when rustup was run |
| +# with --no-modify-path. Source it here rather than depending on whoever |
| +# configured the runner having got it right — a gate that only works on one |
| +# machine is not a gate. |
| +[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" |
| + |
| +for tool in cargo rustc go node; do |
| + command -v "$tool" >/dev/null || { echo "gate: $tool not on PATH" >&2; exit 1; } |
| +done |
| +rustup target list --installed 2>/dev/null | grep -qx wasm32-unknown-unknown \ |
| + || { echo "gate: wasm32-unknown-unknown target missing — rustup target add wasm32-unknown-unknown" >&2; exit 1; } |
| + |
| +export CGO_ENABLED=0 |
| + |
| +echo "=== rust: build, clippy, test" |
| +cargo build --release --manifest-path core/Cargo.toml |
| +cargo clippy --manifest-path core/Cargo.toml -- -D warnings |
| +cargo test --manifest-path core/Cargo.toml |
| + |
| +echo "=== wasm: the artifact the host embeds" |
| +./scripts/build-wasm.sh |
| + |
| +echo "=== the embedded wasm must match a fresh build" |
| +if ! cmp -s core/target/wasm32-unknown-unknown/release/docs_core.wasm internal/core/testdata/docs_core.wasm; then |
| + echo "gate: internal/core/testdata/docs_core.wasm is stale. Re-copy it." >&2 |
| + exit 1 |
| +fi |
| + |
| +echo "=== wire compatibility, both directions" |
| +./scripts/wire-gate.sh |
| + |
| +echo "=== go: vet, test, gofmt" |
| +go vet ./... |
| +go test ./... -count=1 |
| +test -z "$(gofmt -l .)" || { echo "gate: gofmt"; gofmt -l .; exit 1; } |
| + |
| +echo "gate: ok" |
diff --git a/AGENTS.md b/AGENTS.md| index cebba28..cc307d9 100644 |
| --- a/AGENTS.md |
| +++ b/AGENTS.md |
| @@ -88,8 +88,12 @@ faces the same gates, but only *after* it is on `main`, where a red run is |
| an incident to clean up instead of a merge that simply does not happen. |
| **The review record lives on amadan.** The pull-request equivalent is a |
| -branch discussion — see below. CI is `.amadan/ci.d/`, triggered by a push |
| -to the remote. |
| +branch discussion — see below. **CI is `.amadan/ci.d/10-gate`**, triggered |
| +by a push to the remote, and it is the same script you can run locally. It |
| +covers the Rust crate, the wasm artifact, Yjs wire compatibility in both |
| +directions, and the Go build and tests. `amadan ci status -branch <n>` |
| +exit 0 is the merge gate; exit 2 is pending and exit 3 means nothing ran, |
| +and neither is green. |
| ## Working through amadan |