opsinjs

Docs freshness

Every page has a review cadence and a reviewer discipline. An expired page is reported by CI, not discovered by a reader.

Why a documentation site needs an expiry date

Guidance rots more quietly than code. A component that no longer compiles is found in minutes; a paragraph that stopped being true two releases ago reads exactly as authoritative as it did when it was written, and in a health system it is read by somebody making a decision about how to present a number to a patient.

So every page here carries three fields in its frontmatter, and a script checks them:

FieldWhat it means
reviewedThe ISO date somebody last re-read the page against the system
reviewerWhich discipline owes the next read-through: design, engineering, clinical or content
reviewEveryHow long the last read-through stands before it expires: 3m, 6m, 12m or never

<Reviewed /> prints the date and the cadence at the foot of the page, so a reader can see the age of what they are reading without opening a file.

It does not print reviewer, and it must not be made to. That field is an assignment and not an attestation. Twenty pages name clinical, twelve of them safety patterns including the alert escalation ladder and the result disclosure rules, and no clinician has read any of them. A footer saying "last reviewed by clinical" would be the single most dangerous sentence this corpus could print. When a real sign-off exists it needs a field that records that it happened, because this one records who is next.

There were two more fields here until recently, owner and a11yDate. Both are gone: owner asked the same question as reviewer without a closed list of answers and collected six spellings for four disciplines, and a11yDate held the same stamped date on seventy-seven pages, which records a bulk edit and not a review.

The cadence, by page kind

reviewEvery is optional in frontmatter. When it is absent, the default for the page's kind applies. Those defaults live in one place, DEFAULT_CADENCE in scripts/check-freshness.mts, and that is the authority: if the table below ever disagrees with it, the script is right and this page is the thing to correct.

KindDefault cadenceUsual reviewerWhy
health6 monthsclinicalDoctrine that shapes what a patient is told
accessibility6 monthsengineeringClaims that are tested and can regress
project6 monthsengineeringPolicy and status pages, which describe a system that is still moving
content12 monthscontentLanguage guidance changes slowly
component12 monthsdesignThe implementation moves, but a component page moves with it in the same commit
foundation12 monthsdesignToken changes arrive through the generated tables, which the drift gate checks every build
pattern12 monthsdesignPatterns outlive the components implementing them
recipe, screen12 monthsdesignCompose components, so they rot when components change
handbook12 monthsengineeringMechanics, tied to releases rather than the calendar
guide12 monthsengineeringInstallation instructions age fastest of all, which is why the ones that matter override this down to 3 months rather than relying on the default
referenceneverengineeringBodies are generated and are checked by the drift gate on every build, so a calendar review would report an age the gate has already disproved

The reference exemption is the one that surprises people. It is not a claim that those pages cannot be wrong; it is that a date is the wrong instrument for them. pnpm run check:generated regenerates every one of them and fails on a single changed byte, which is a stronger guarantee than a human re-reading a table of two hundred rows twice a year. The hand-written half above each generated marker is not covered by that, and a reference page whose prose needs a cadence sets reviewEvery explicitly. Both contrast and catalogue do this.

Pages that override the default downwards to 3 months are the ones where being wrong is most expensive. They include the emergency and crisis health pages, the accessibility statement and the two contrast pages, the six start/ guides a newcomer follows literally, the measured contrast table, the catalogue, the roadmap and state of the system. The six guides are quick start, installation, the Next guide, components.json, the first component and troubleshooting.

reviewEvery: never exists for pages whose content is fixed by history, such as a dated changelog entry or a decision record that has been superseded. Use it rarely; it is the one setting that cannot be reported as stale.

What the check actually does

scripts/check-freshness.mts reads every page, adds either the page's reviewEvery or its kind's default to its reviewed date, and sorts the corpus into three states. It prints one summary line and then up to three tables, headed OVERDUE, DUE WITHIN 30 DAYS and NO REVIEW DATE; a table is omitted when it would be empty. Each row is one page: its slug, its kind, its reviewer, its reviewed date and its due date, with the overdue rows ordered by how far past due they are so the worst is at the top. --json prints the same three sets plus the totals, and --strict exits non-zero when anything is overdue.

The tables group by state and by nothing else, so do not expect a per-team digest from this script. State is what somebody clearing a backlog needs. If you want the digest, group the --json output by reviewer with jq.

It runs both places, differently. On a pull request it is advisory and continue-on-error, because a page expiring is not a reason to block somebody else's change. In the nightly job it runs with --strict and blocks, which is the only thing that makes reviewed: and reviewEvery: mean anything.

A page with kind: health and no reviewed date is a different matter: that fails assert-ia.mts on every build. Health doctrine without a review date is not a stale page, it is an unsourced claim.

What a review is, and is not

A review is re-reading the page against the system as it is today and changing what has stopped being true. If a page needed no change, the reviewer still updates reviewed. That is a real signal, and it is the whole point of separating "when it was written" from "when it was last checked".

Bumping the date without re-reading the page is the one prohibited operation here. It converts the only honest freshness signal on the site into decoration. A page that is out of date and says so is more useful than a page that is out of date and claims to have been checked last week.

This repository has done it, twice. Of the 343 pages that carry a reviewed date, 255 share one date and another 81 share a second, so two bulk edits account for 336 of them. The a11yDate field deleted alongside owner was the same story on a smaller scale, one stamped date across seventy-seven pages. So read a reviewed date here as the weaker claim it is: somebody passed over the page on that day. When the next one comes round, re-read the page rather than repeating the edit that made this paragraph necessary.

If you are reading a stale page

The review date at the foot of the page is the truth. Cross-check against the changelog: if a release landed after the review date and touched what you are reading, believe the changelog. Right now the changelog is itself behind, because the component layer landed without an entry. State of the system is therefore the more current of the two and is the one to check first. Then tell us: report it as a docs issue naming the page path, and Community says where to send it. There is no report-a-problem control at the foot of the page; a <Feedback> component is written and nothing renders it, which Community also records. A stale page is worth reporting anyway, and it is the report this section exists to ask for.

Edit this page

Last read through against the system on 2026-09-02. Due for review every 12 months; expiry is reported by pnpm run check:freshness.

On this page