# 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 and
the `rustfmt` component, Go with `CGO_ENABLED=0`, and Node with `npm` on
`PATH`. 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 clones a fresh worktree per branch, so `fixtures/node_modules`
never carries over — the gate runs `npm ci` itself, against
`fixtures/package-lock.json`, so the installed `yjs` is always exactly
13.6.27 rather than whatever a bare `npm install` would resolve.

**Known tradeoff:** that `npm ci` needs the npm registry reachable at run
time. It is the right fix for the fresh-worktree-per-branch problem above,
and the lockfile is committed so the *version* resolved never drifts, but
it does mean the gate is not fully offline-capable the way the Rust and Go
steps are once their caches are warm. If the registry is ever unreachable
from the runner, that is the failure this line produces — not a bug in
the gate itself.

## 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.

## The app, not just the core

`go vet ./...` and `go test ./...` already cover `cmd/docs` and
`internal/docs` — the rastrillo app built on top of `internal/core` — but
neither one proves the app actually links into a runnable binary. The gate
also runs `CGO_ENABLED=0 go build ./...` for that.

There used to be a further step here: a separate `-tags release` build,
run with `-dev-auth` on its command line, asserting it refused to start —
proving a release binary had no code path that could turn a
development-only stand-in identity on. That identity
(`internal/docs/devauth.go`) had zero readers once rastrillo/auth's
`RequireSession` started guarding every document route unconditionally
(a fix round on Task 1, once real per-person sign-in existed), so it was
deleted rather than kept as dead code — and the `release` build tag it
was the only user of went with it. There is no release/non-release
distinction left for this gate to hold apart; `go test` covers the
fail-closed properties that matter now
(`TestNewRefusesMissingInstanceKeyOutsideDevelopment`,
`TestNewRefusesMissingMailOutsideDevelopment`,
`TestSignInRequiredForEverything` — all `internal/docs/auth_test.go`).
`scripts/run-dev.sh` builds and runs the ordinary binary with `-env
development`, a fixed port, and a scratch database — one command to see
the app in a browser, signed in for real via a magic link logged to the
terminal instead of mailed.

The embedded WASM is built on Linux amd64 using fixed `/work`,
`/usr/local/cargo` and `/usr/local/rustup` paths. Rust/Cargo's crate identities
otherwise change code ordering across hosts, even with remapped source paths.
Linux amd64 uses unprivileged bubblewrap when available; other hosts use the
pinned Rust Docker image. Install Docker on development Macs. Both the artifact
builder and containment tests use this path; the freshness comparison remains
byte-for-byte. Docker caches and writable targets are separate from source.
