Skip to content
Version: Current

ADR: the documentation iteration engine

The site had 30+ pages and no way to answer “what is missing?”. Three failure modes were observed:

  • the API contract is generated from the gateway’s route tables, but the docs site described it by hand, so a new route reached the contract without a page;
  • code examples were never executed, so a command that stopped working stayed on the page;
  • nothing compared a page’s last edit with the code it describes, so a stale page looked exactly like a current one.
  1. Generate every reference from its source of truth. The OpenAPI document, the protobuf services, the CLI dispatcher, the flags and the error taxonomy are read by tools/docs-engine/generate.mjs, and the gate fails when the page and the source disagree.
  2. Measure the rest. A detector diffs the surface inventory against the published pages, runs the examples a page opts into (exec), reads the sidebar, the changelog and an optional support-signal file, and ranks the result.
  3. One page per cycle, recorded. Each cycle writes one decision card, one page, and one entry in docs/ITERATION.md plus a row in var/docs-engine/cycles.db.
  4. Prose needs a reviewer; reference regeneration does not. A generated reference page is mechanical, so it may land on a green gate. A tutorial or a runbook is a judgement about what a reader needs, so it lands only with an approval recorded on its card.
  5. Absence is reported, not invented. A missing source repository is a note. The scheduled cycle passes --require-sources so an absence cannot be mistaken for a clean run on the docs host.
  • A contributor adding a route to the gateway must run npm run docs:generate before the docs gate passes. That is the cost of the reference never drifting.
  • The engine adds ~2,000 lines of Node with no dependencies. It cannot use a build tool, because it must run before the build.
  • The cycle store is SQLite, not PostgreSQL, because the engine runs on a laptop and on the docs host with no shared database, and a cycle record is not worth a network dependency.
  • Coverage is a floor, not a proof: a mention counts. The “mentioned, no section” detector exists to keep that honest, and the panel reports both.
  • A docs-as-code linter only. Focuses on formatting and links; cannot see a route that reached the server and no page.
  • A generated site with no hand-written pages. Would remove the drift but also remove the tutorials and runbooks, which are the pages that answer a question the contract cannot.
  • An LLM writing pages from the diff on every merge. Rejected as the default: an unreviewed prose change is indistinguishable from a correct one, and the card/review step is what makes the cycle auditable. The model is allowed to draft; it is not allowed to merge prose.