166. The plan's surfaces are inferred from delivery, and nothing the plan shows is typed beside it
Context
On 2026-10-16 the owner read https://majordomus.dev/roadmap/ and https://majordomus.dev/ui/roadmap/ and wrote:
- "tohle je evidentne dost outdated, neni to derived, neni to infered, neni to generic, dynamic, integrated, tested, documented, deployed, verified, validated, eliminuj tento paralellni vesmir" (this is evidently quite outdated, it is not derived, not inferred, not generic, dynamic, integrated, tested, documented, deployed, verified, validated; eliminate this parallel universe).
- "make main gh_page + landing page more up-to-date, generic/generated, all of above".
- "vsechny ascii charts/graphs nahrad v souladu s ostatnimi mermaid.js ci cytoscape, interaktivni proklikatelne, enforcovane, veskery kod unifikovane highlightuj stejnym nejlepsim zpusobem" (replace every ASCII chart or graph, consistently with the rest, by mermaid.js or cytoscape, interactive and clickable, enforced; highlight all code in one unified, best way).
Measured the same morning at master d1162668036 (v0.27.0):
- The public roadmap lists eight of 38 milestones.
site/templates/roadmap.htmlkeeps only milestones whose hand-typedversion:is set. Those are the eight written before the project had releases: 0.1 to 1.0, two of them 0.5, in rank order 0.1, 0.5, 0.6, 0.2, 0.3, 0.4, 0.5, 1.0. The product has shipped 27 minor versions. The 30 milestones where the work happens do not appear at all. - An issue's status is a stamp, and nothing compares it with what happened.
lib/project.awk:156-170andplan.rsderive status from theplan start|verify|donestamps and the dependency graph only. PR #1097 merged the whole of I2653, and I2653 still reads READY. The same holds for I2417 (#930), I2652 (#1090), I2734 (#1234), I2601 (#1092) and others: a merged pull request names the issue, and the plan has not heard of it.trace.issue,delivery.report,scripts/github-sync --checkand the intent drift classes each read a part of the truth. None of them sets it against the declared status. - The UI roadmap's "latest release" lags the executable that drew it. The snapshot read at 07:15Z said "Latest release 0.26.0" under a 0.27.0 header, because the release record merged at 07:44Z.
- The landing page states the present in hand-written prose. For example,
site/data/manifesto.tomlsays "Nothing replans" and "No command runs this today", andmarketing.tomlclaims "straight from the repository" over figures that come from stamps. - Diagrams and code are drawn three ways.
- Box-drawing art survives in ADRs, the handbook and a UI screen, which
project.a-diagram-is-drawn-not-typeddoes not cover. - Code is highlighted by Zola's syntect on the site, by highlight.js 11.9 fetched from a
CDN on
majordomus ui, and by the handbook's own scripts.
- Box-drawing art survives in ADRs, the handbook and a UI screen, which
Every one of these is a second account of the repository, written beside it. That is the parallel universe.
Decision
-
An issue's delivery is inferred, once, in Rust. A capability
plan.deliveryanswers, for every issue, the furthest of:nonebranch: a branch names itpr_open: an open pull request names itmerged: a merge on the trunk names it, through the issue edge of I1405released: the firstv*tag containing that mergedeployed: the published site reports that release or a later one
Each stage carries the object that proved it: the branch, the PR number, the merge commit, the tag, and the site's
latest.json. It also carries the time it was observed. The forge is read through the existingserved forgecache, and an unread forge is reported as unread, never asnone. Nothing reimplements this in awk or shell. The site data andPLAN_STATUS.mdcall the executable. -
A contradiction between declared and inferred is a finding, and on the trunk a gate.
plan.driftreports:merged_not_recorded: READY, BLOCKED or ACTIVE, with merged work and no open pull request naming it.active_without_work: ACTIVE, with no branch, pull request or mesh claim inside the window.done_without_trace: DONE, with no merge naming it. This class is advisory, because record-only issues exist.
merged_not_recordedolder than a window declared in.ai/repo/ci/refuses the trunk'sci, and namesmajordomus plan verify|done <id>. Declared status stays the record of acceptance against evidence. Inference never writes a stamp. -
A milestone's version is a fact about releases, not a field.
version:leaves the milestone schema, and a record that carries it is refused. "Released in" is the first tag that contains the milestone's last accepted issue. An open milestone is "unreleased". The vendoredroadmap-integrityrule is rewritten to match. -
Every plan surface draws every milestone from the one derivation. That covers
/roadmap/,/plan/, the landing page's plan figures,/ui/roadmapandPLAN_STATUS.md:- The order is the milestone graph's.
- Each issue shows its declared status beside its inferred stage, and links to the PR, commit and tag that prove the stage.
- Each surface states the commit and time it was observed.
- No template filters milestones by a hand-written field.
/ui/roadmapreads the release from the releases, not from a record pull request, at snapshot time.
-
The landing page states no present tense it does not read. A sentence in
site/data/*.tomlthat describes current state either becomes a rendered datum or goes.scripts/ci/homepage-checkrefuses the vocabulary of present-state claims in those files. -
Every relationship is drawn, and drawn interactively.
project.a-diagram-is-drawn-not-typedreaches version 2 and covers.ai/repo/adrs/**,.ai/repo/rules/**,handbook/**and every markdown stringmajordomus uirenders.- Diagrams are Mermaid, or Cytoscape where the graph is data.
- Every diagram goes through the ADR 0152 viewer, with node links inferred by the build.
-
One highlighter. Every code block, on every surface, is highlighted by syntect with one class vocabulary and one palette generated from the design tokens:
- On the site, through Zola.
- In
majordomus uiand the reports, through the executable. - In the handbook, through the same generated stylesheet.
highlight.js leaves the UI, and no surface fetches a highlighter from a CDN. A gate refuses a second highlighter.
Consequences
- The roadmap shows 38 milestones where it showed eight, and it shows work in flight (open pull requests, mesh claims) that it never showed.
- Releasing becomes part of the plan's own picture: an issue reads
released in v0.28.0, so a merged but unreleased change is visible as such and argues for the next cut. - The trunk turns red when a merged issue goes unrecorded past the window. That is intended: it is the drift that produced this ADR.
- The executable gains a syntect dependency. The palette is generated once, so the site and the UI cannot drift apart in colour.
- The awk engine keeps deriving status from stamps. Delivery is Rust only, which case 99 (the engines agree) does not cover. This is deliberate, and recorded here.
Alternatives rejected
- Infer status and write the stamps automatically. This rejected because a merge is not acceptance. Evidence still decides DONE, and inference only makes the gap visible and gated.
- Keep
version:and add a check that it is plausible. This was rejected because a plausible hand-written number is still hand-written. The tags already hold the answer. - Adopt Shiki for highlighting. This was rejected because Shiki would put Node on the path of the executable's own pages. Syntect is the engine the site already uses, available to the Rust surface without a second toolchain.