corvus
ADR 0003 accepted relates to ADR 0001

The documentation site and the milestone pages

Context

The docs repo holds the ADRs and, later, the runbooks. Its charter so far is to describe what is landed, never what is coming. The delivery of each ADR is a GitLab group milestone with issues across the repos, and the only view of a milestone is GitLab’s flat issue list: the order, the blockers, what the operator can start today and why the plan drifted live in the operator’s head.

The operator wants a public static site at the.corvus.observer that renders the ADRs, one page per milestone in a readable narrative form (decision, what to do next, the path with blockers, open questions, deviations, history), and the relation between the two in both directions. The site is built when main of docs changes, and the milestone state is refreshed by the operator, by hand, about once a day.

Decisions

The charter includes milestone pages

A milestone page is the current state of a milestone as of its last sync. Current state is documentation. The “what is landed” rule keeps applying to ADRs and runbooks; a milestone page describes what is landed and what is in flight, and is never a place to design.

Data and narrative, split

Each milestone has two files in docs:

Regenerating the narrative with a language model on every sync was considered but rejected: the prose would drift with no change in the facts, and the MR diff would stop showing what moved. Instead the sync reports drift between narrative and data in the MR description, and the operator edits the narrative like any other docs change.

The sync is an MR, once a day

A skill in agents, sync, fetches every open milestone, precomputes the derived data, writes the data files in a worktree of docs, and opens one MR per run with the drift report. The operator’s merge publishes. Pushing to main directly was considered but rejected: a branch is an MR everywhere in corvus, and the diff of the data file is the daily review.

The fetch and the precomputation are a shell and jq script, reproducible without a language model; the model writes only the drift report.

Order comes from the “Depends on” section

Every issue body ends with a “Depends on” section: a bullet list of issue references, one per line, written by the plan skill from the titles in the plan. It is the single source of order; milestone and the sync parse it and nothing else. A reference survives a rename, and a line per entry means a title with a comma never splits. The skill also creates relates_to links, so GitLab’s “Linked items” panel shows the relation in both directions, but those carry no direction and are only cosmetic. Phases are scoped labels set by plan.

GitLab’s blocking issue links were the first choice, structured and queryable, and were rejected when the API answered that blocked issues are not available on the group’s plan: they are a paid feature, and the site is not worth a subscription. Parsing titles out of the section was considered and rejected: a renamed title breaks it silently.

A dependency on an issue of another milestone blocks like any other and is rendered with that milestone named.

The relation to the ADR lives in docs

The narrative frontmatter names the ADR the milestone delivers, single valued; the site computes “delivered by” on the ADR page from it. The GitLab milestone description still names the ADR once, and the sync flags a mismatch. Taking the relation from GitLab was considered but rejected: it is a docs fact, and the build stays a pure function of the repo.

The narrative is born with the milestone

plan scaffolds the narrative in a docs worktree when it creates the milestone: frontmatter, the decision paragraph from the ADR, empty sections, and opens the docs MR. The sync flags a narrative that is still a scaffold. The milestones that predate this record get their narrative and their “Depends on” sections backfilled once.

Hugo, with precomputed data

The site is built with Hugo: a single binary in CI, YAML data files read natively, no toolchain in docs. Templates only lay out; everything derived (path order, unblocked set, timeline of states) is precomputed by the sync into the data file, so the data file is a complete snapshot and the build has no logic. Astro was considered for how naturally it would express the page, but rejected for the Node toolchain it brings into a repo of markdown. A hand-written generator was considered and rejected as one more thing to maintain.

The theme is hand-written inside docs (layouts and one stylesheet, no JavaScript, no external theme). Light and dark follow the system. The home page lists ADRs and open milestones; each ADR page carries a “delivered by” box; closed milestones stay reachable from their ADR and from an archive list. The ADRs gain frontmatter (date, status) so the build never depends on the layout of their prose.

Published to R2, public

The site lives in a Cloudflare R2 bucket with the.corvus.observer as its custom domain. Bucket, custom domain, the API token scoped to the bucket and that token as a CI variable of docs are declared in platform/infra, the first R2 resource and the first CI variable in tofu. The docs pipeline builds on MRs and, on main, builds and syncs the output to the bucket with deletion, so the bucket is exactly the build. The site is public, consistent with the public GitLab group; Cloudflare Access was considered and rejected since the site exposes nothing the repos do not.

Consequences