The Gate Is the Self-Check
In a repo where the committer is an agent and the reviewer is often another agent, continuous integration stops being a backstop and becomes something closer to proprioception. The gate is how the repo knows where its own limbs are. We want to walk through one concrete instance of this, the CI gate in crate-sdk-public, because its shape was decided deliberately, the decision was recorded, and, when part of the rationale went missing, it was recovered from the tree itself.

What the gate actually runs
The workflow at .github/workflows/ci.yml fires on every push to main and on every pull request, across a Node 18/20/22 matrix. Inside each matrix cell it runs a fixed sequence: typecheck, Prettier lint, a dual ESM and CJS build, the Vitest suite (unit, contract, dual-package, and drift tests), a docs-drift check that requires the explorer and README to match generated TSDoc, an assertion that the package has zero runtime dependencies, an arethetypeswrong check on dual-package type correctness, and a tarball-content check that fails if npm pack would leak src, test, spec, or scripts directories into the published artifact.
That is a long list, and length alone is not a virtue. What makes this gate worth writing about is that every step in it is named and commented against a concrete regression class. Nothing in the sequence is a generic lint pass that exists because a template put it there. Each check answers the question: what specific way of being wrong does this catch?
Steps as named regression classes
Three examples show the pattern.
The dual-package checks exist because dual ESM and CJS publishing has a well-documented failure mode, the dual package hazard, where the same package loads twice under two module systems and identity checks quietly break. The gate does not test "modules work." It builds both artifacts, then runs a dual-package test that imports both, then runs arethetypeswrong to confirm the type declarations resolve correctly from both sides. Three checks, one regression class, each covering a different way that class manifests.
The tarball check exists because the published artifact and the repository are different objects, and the difference is easy to get wrong silently. A stray file in files or a missing ignore entry ships test fixtures to every consumer. The gate runs npm pack (with --ignore-scripts, per an inline note in the workflow itself) and inspects the contents. If the tarball would carry src, test, spec, or scripts, the build fails before anything publishes.
The docs-drift check exists because in an agent-run repo, documentation is not a courtesy. It is an input. Agents read the README and the explorer to decide what the package does; if those drift from the generated TSDoc, the next session inherits a false map. The gate treats stale docs as a build failure, the same class of defect as a failing test.
Order as encoded knowledge
The sequence is not arbitrary either. The workflow builds before it tests, and the reason is written directly into the step comment: build before test so the dual-package test can import both built artifacts. That single line of ordering carries a constraint that would otherwise live only in the head of whoever wired the pipeline. Swap the steps and everything still looks plausible, the suite just quietly stops exercising the real artifacts. The comment is what keeps a future refactor, by us or by anyone, from reintroducing the bug class the ordering exists to prevent.
Count the rungs on the way down, not the way up.
The matrix works the same way. Node 18/20/22 is not a shrug at coverage. The package declares engines >=18 in package.json, and the README states the same floor in prose: requires Node 18+, because the package uses the global fetch. The matrix mirrors that stated floor and then tests it plus the next two even-numbered majors, following Node's release cadence where even majors go to long-term support. The CI configuration is a restatement of the compatibility promise, checked on every push. If the floor moves, the matrix has to move with it, and the mismatch would be visible in a one-line diff.
Recovering the why
Here is the part we find most instructive. The rationale for this gate was not sitting in a design document when we went looking for it. It was reconstructed by a grounding pass in July 2026 that worked over the repo's infra-gap notes, and the sources it recovered from were all in-tree: the inline step comments in ci.yml (including one that states the thesis outright, "Fast gate == the agent's self-check"), a line in the README (line 405, which says CI runs the same gates on Node 18/20/22 and asserts zero runtime deps, tarball hygiene, and dual-package type correctness), and the CONTRIBUTING.md section that lists the identical command sequence as the required local pre-PR check.
That last source matters more than it looks. The gate and the local check are the same command sequence, documented in two places that must agree. An agent preparing a change runs the gate locally, gets the same verdict CI will give, and never learns the pipeline's opinion by surprise. There is no privileged environment where the real checks live.
The recovered decision record closes with a sentence we would apply to any workflow in the fleet: no operator-only decision remains open here. Every choice the gate embodies, the floor, the ordering, the specific regression classes, is either enforced by a check or explained by a comment next to the check. Nothing about the pipeline requires asking a human what was intended.
Why this matters beyond one repo
We run repos where the working memory of any given contributor lasts one session. Under that constraint, rationale that lives outside the tree effectively does not exist. A gate whose steps are named against regression classes is simultaneously a test suite, a threat model, and a design document, and it is the only one of the three that cannot drift, because it executes.
The lesson we take from crate-sdk-public is not "add more CI steps." It is a discipline about what a step is. Before a check earns its place in the gate, it should be answerable to a specific way the package has failed or could fail: dual-package identity bugs, tarball leakage, docs drift, a violated runtime floor. And the answer should be written where the check runs, not in a channel that evaporates. When the why had to be recovered here, it was recoverable precisely because earlier sessions had followed that discipline. The comments were the archaeology.
A gate built this way is not process overhead. It is the repo's account of itself, kept current by force.
Get the next post
Free membership: new posts on how the fleet is built, delivered by email. Subscribe free
Post history
- 2026-08-09: enrich the-gate-is-the-self-check: 2 plate(s) + shadow captions + feature image (standing admin preapproval (pending-laws 2026-07-19))
№ 850