Draft: this page sits outside the guide's focus.
VerificationLesson
Invariants as tests: pinned links
Some rules about content must hold on every page, every day: a link into the code shows the code the author saw. A rule that only lives in a writing guide holds until someone forgets it. Written as a test, it holds on every change, and the test can say exactly which link broke it.
Know permanent links already? Skip to In Terrarium.
Outside the box · General
The concept
A link to a file on a branch, such as …/blob/main/src/app.ts, always shows the file as it is now. Months later the lines it pointed at have moved, or the file is gone. A permanent link names a commit instead of the branch, as in …/blob/<commit-sha>/src/app.ts, and shows that exact version for good. On GitHub, pressing y while viewing a file turns its address into one (GitHub Docs: Getting permanent links to files).
A rule like "links to code are permanent" is an invariant: something that must be true of all the content, all the time. You can check an invariant with an ordinary unit test in a runner such as Vitest: read every content file, find what breaks the rule, and fail with a message that names it. Keep the test beside the content it guards, so the two move together.
Full referenceVitest docs: Getting Started
In Terrarium
The Blog's writing Skill, .agents/skills/blog-post/SKILL.md, asks every link to a file or a line to name a full commit SHA, never main. The Blog checks that with a unit test in its own layer, layers/blog/tests/unit/blog-immutable-links.spec.ts. It reads every Markdown file under the Blog's content and fails on any file link whose ref is not a commit SHA:
21// Only file-serving GitHub routes take a ref that can be a branch; `commit`,
22// `pull`, `issues`, `compare`, … don't, so they can't rot this way.
23const FILE_ROUTE_LINK = /github\.com\/[^/]+\/[^/]+\/(?:blob|tree|raw|blame|edit)\/([^/\s)]+)\//g
24// An immutable pin is a hex commit SHA (7–40 chars). Anything else — `main`, a
25// feature-branch name, a tag — is mutable and rejected.
26const SHA = /^[0-9a-f]{7,40}$/
A first check fails if the walk finds no content at all, so a moved folder can't make the test pass by checking nothing. A second rule in the same file fails on a GitHub link to another Blog post, which must use the post's own site address instead.
The test sits in the 's own tests/unit/ folder, so pnpm test runs it with every other unit test (Testing). That makes it part of the : a post with a branch link fails the test step and can't merge.
This sits uneasily with .out-of-scope/ci-enforced-doc-invariants.md, which turns down Gate and CI checks that block a change over a documentation or metadata invariant, such as a doc that names a retired term, and prefers a guard that refuses the bad action when it is taken. The Blog's test is such a check in all but name: it adds no Gate step of its own, but it blocks through one. The record doesn't say where the line falls, so read the test as one the repo has kept, not as an exception the record allows.
Why it's built this way
The Gate: ADR-0004 (docs/adr/0004-objective-safety-gate.md).
Where a Tenant's tests live: tests/README.md.
What to watch for
- The test is looser than the Skill. The Skill asks for a full 40-character SHA; the test accepts any hex run of 7 to 40 characters, so a short SHA passes.
- A pinned link can still be wrong. The test checks the shape of the ref, not that the commit exists or holds the quoted lines. The Skill has the author copy the SHA from git output for that reason.
- Only file links are checked. Commit, pull request and issue links name something that never changes, so the test skips them.
Try it in your project
Pick one rule from your project's writing guide that a script could check. Write it as a test that reads every content file and fails with the offending text in its message, then break the rule once on purpose to see the message.
Where next
- PageTesting
- PageMerge gate
Last updated · Latest source on GitHub ↗