oficina / slides Public

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

Plain git — no account needed to clone.

Download

Download this file

1# CI
2
3`ci.d/10-gate` is the only CI this repo has. It runs on every push to the
4amadan remote, and it is the same script you can run locally — one
5definition rather than two that drift. `ci` execs it, so a runner with no
6step support runs exactly the same thing rather than a thinner Go-only
7version of it, which is what the four scaffold steps here used to be.
8
9It needs a Rust toolchain with the `wasm32-unknown-unknown` target and the
10`rustfmt` and `clippy` components (`rust-toolchain.toml` pins 1.98.0 and
11asks for all three), Go, and — since slice 2 — **Node 20 or newer with
12`npm`**, because the editor's bundle and the fixture vectors are both
13built by JS this repo owns. `CGO_ENABLED=0` is exported for every step.
14The gate sources `~/.cargo/env` itself and checks every tool up front, so
15a missing dependency fails on the first line with its own name rather
16than fifty lines in as `cargo: command not found`.
17
18`make migration-check` runs the rastrillo CLI through `go run` off
19`go.mod` rather than needing a binary on `PATH` — a bare `rastrillo
20migration check` is what failed a sibling repo's very first CI run, with
21nothing in the scaffold saying what to install.
22
23**Known trade-off: `npm ci` needs the npm registry reachable at run
24time.** The Rust and Go steps are offline once their caches are warm; the
25Node step is not. It is that way for a reason — the runner clones a fresh
26worktree per branch, so `fixtures/node_modules` never carries over, and
27`npm ci` against the committed `fixtures/package-lock.json` is what makes
28the installed `yjs` exactly the pinned 13.6.27 rather than whatever a
29bare `npm install` resolves this week. Docs made the same call and
30documents the same trade-off. If the registry is unreachable from the
31runner, that is the failure this line produces; it is not a bug in the
32gate. The cache directory is the one part of it that differs by machine,
33so the step honours `NPM_CACHE` and falls back to `$HOME/.npm`: the
34runner's HOME is writable and needs nothing, a sandboxed local run may
35need `NPM_CACHE` pointed somewhere it can write, or npm fails on its
36cache rather than on anything to do with the install.
37
38## The JS half
39
40Four steps, in this order, between the Rust tests and `make ci`.
41
42- **`(cd fixtures && npm ci --cache "${NPM_CACHE:-$HOME/.npm}")`** — the
43 trade-off above, and the reason for it.
44- **`(cd fixtures && node --test *.test.mjs)`** — the JS side's own
45 tests: the op vocabulary, the ProseMirror schema, the save queue, the
46 live-update cursor, the resync controller, the inspector's ops, the
47 canvas maths, the switcher, uploads. Most of what the editor gets wrong
48 it gets wrong in a browser with nothing in a log, which is the argument
49 for running them here rather than by hand.
50
51 Run from `fixtures/` with a glob, never as `node --test fixtures/`.
52 Node 24 takes a directory argument as if it were a test file, runs
53 nothing, and exits 0 — a green-looking way to run no tests at all.
54- **`(cd fixtures && node generate.mjs)` then a clean-tree check on
55 `fixtures/vectors`** — the vectors are the contract between the JS ops
56 and the Rust core (spec §2.8). `internal/slides/core/vectors_test.go`,
57 under `make ci` below, loads each committed vector through the core and
58 requires the same view the JS side produced. A vector nobody
59 regenerated makes that test compare the core against a JS
60 implementation that no longer exists, so **it still passes** while the
61 two sides have already drifted. This step is what turns that into a
62 failure: `vectors are stale: run node fixtures/generate.mjs and commit`.
63- **`node fixtures/build-bundles.mjs` then a clean-tree check on
64 `internal/slides/static`** — the editor bundle is committed, hashed,
65 and built by nothing at run time, so a stale one is served perfectly
66 happily by a server that cannot tell. Same check as the wasm `cmp`
67 below, and safe for the same reason: the build is byte-reproducible (no
68 sourcemap, no banner, no timestamp, no absolute paths, and esbuild's
69 work happens in the pinned 0.28.2 native binary, so the Node running it
70 does not enter into the result).
71
72The last two check `git diff` **and** `git status --porcelain`, because
73each catches half of it. `git diff` sees a tracked file that changed or
74was deleted; it never sees the new file a changed content hash writes
75beside the old one. A stale hashed bundle left behind is not a cosmetic
76problem — `assets_embed.go` requires exactly one `editor.<hash>.js` and
77panics at boot when it finds two.
78
79Both restore the tracked files on failure (`git checkout`) and print
80what they found: they mutated a committed directory as a side effect of
81reading it, and a modified tracked file left behind would let a second
82run of the same script pass with nobody having committed anything. The
83untracked half is deliberately left alone — a new vector or a bundle
84under a new hash is exactly the file you have to commit next, and an
85earlier version of this gate `git clean`ed it away before you could.
86
87## Two things that fail silently
88
89- **The gate fails if `internal/slides/core/testdata/slides_core.wasm`
90 differs from a fresh build.** `go:embed` will otherwise embed a stale
91 module and every Go test will pass against it. When it fails, rebuild
92 and commit the artifact:
93
94 ```sh
95 ./scripts/build-wasm.sh
96 cp core/target/wasm32-unknown-unknown/release/slides_core.wasm \
97 internal/slides/core/testdata/slides_core.wasm
98 ```
99
100 That comparison only works because `scripts/build-wasm.sh` remaps the
101 registry and workspace path prefixes rustc would otherwise bake into the
102 binary. Without it the artifact reproduces byte-for-byte only on the
103 machine, and under the `$HOME`, that built it, and the `cmp` reports a
104 stale artifact everywhere else.
105
106- **`cargo fmt --check` covers `core/`.** It was not in the gate before
107 2026-09-07, and by then three files had drifted; they were reformatted in
108 the commit that added the check.
109
110## The runner
111
112Jobs are claimed by an `amadan runner` on the machine that has one. Two
113things about it are wrong by default, and both fail in ways that read like
114something else:
115
116- **The runner must be told to serve this repo.** `amadan runner -repo …`
117 is an allowlist, and a repo nobody listed gets `amadan ci status` exit 3
118 forever — "no status recorded", which reads like the gate has not run
119 yet rather than like nothing will ever run it.
120 `~/.config/systemd/user/amadan-runner.service` was edited to list
121 `-repo oficina/slides` on 2026-09-07.
122- **The runner's `PATH` is the systemd unit's, not your shell's**, and
123 `~/.cargo/bin` is not on it. The gate sources `~/.cargo/env` rather than
124 depending on that being fixed.
125
126A restart abandons any job already running, so check the runner is idle
127before restarting it.
128
129## The Go half
130
131`make ci` is the last step, so the Go half of the gate and the `make ci`
132you run between edits are the same definition: `go vet ./...`, a gofmt
133check, `go test ./...`, and `rastrillo migration check`, which fails the
134moment `models.go` and `migrations/` disagree instead of at boot on
135whatever machine notices next.
136
137The browser drive (`go test -tags browser ./...`) is deliberately outside
138all of this. It needs a Chromium, and this project's sandbox will not start
139one, so a gate that ran it would be reporting "could not see" as green.
140