oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download
ci.d/
README.md

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.