ROADMAP_FRESHNESS.md

pipelines/ROADMAP_FRESHNESS.md

THE ROADMAP FRESHNESS CONTRACT (ACTIVE v1.0, 2026-08-10)

T99-operational. Sets no canon. Subordinate to the CVD, the T1 tier docs and the T0 registries

in the authority order at docs/START_HERE.md:10-22. This file defines when a per-pipeline roadmap

row counts as current, who owns moving it, and what measures it.

The question it answers, Josh, 2026-08-09: *"how's the roadmap for each pipeline staying up to

date"*. Before this file the honest answer was: nothing. The roadmap document carried a

supersession-in-place law (docs/ROADMAP_VERTICAL_SLICE_2026-08.md:9-12) and no instrument, so

compliance was a habit rather than a reading.

---

1 · THE DEFECT CLASS

A per-pipeline roadmap is a claim about the future written against a snapshot of the present. Work

lands underneath it; the snapshot does not move; and the row goes on reading as current, because a

document has no way of noticing that the world changed. **Staleness with no surface is

indistinguishable from freshness** — which is the same shape as the night's F4 (a real debt

recorded only in a commit message, where no surface tracks it) and F3 (a record asserting a deploy

while the live page still showed the superseded text).

The first run of the instrument found the class fully instantiated: **six of the seven §4 pipeline

sections had not been edited since the day the document landed**, while five of those six had work

landing underneath them. One section — Characters — had moved twice. Nobody had done anything

wrong; there was simply nothing that made the gap visible.

2 · THE CONTRACT

C1 — THE CLOSE THAT LANDS THE WORK OWNS THE ROW. A wave close that lands work in a pipeline

updates that pipeline's roadmap section in the same close, edited in place per the document's

own supersession law. Not the next close, not a follow-up commit, not a note in the commit message.

A row moved in the same close is a row nobody has to remember.

C2 — THE CLOSE RITUAL RUNS THE INSTRUMENT. Every seat's close ritual runs:

python harness/roadmap_freshness.py --emit

which prints the full table to the console and appends one conforming block to

build/ops/SEAT_REPORTS.md — the channel the director already reads at touchpoints. Staleness

therefore becomes visible the moment it exists, in the surface that is already read, at zero

director token cost. A seat that lands pipeline work and sees its own pipeline in that block has its

next action named for it.

The block is not the table, and the difference was a real defect for the emitter's first hours.

The channel is parsed and rendered by harness/site/seats.py, which skips markdown headings and

renders each block as ONE clipped line of escaped text (_seat_line, 190 characters) — so the

original heading-plus-table append arrived as several undated, unattributed blocks, and on a

phone as a truncated run of pipe characters. What lands now is a head line in the section-1 format

(UTC first, hyphen-free seat, blocked-on: last) plus one short bullet per stale pipeline, with **no

blank line inside the block** because a blank line splits it in two. Controlled by self-test C7,

which parses the emitted head line with seats.py's own regexes rather than checking it by eye.

C3 — WHAT "UPDATED" MEANS. Editing the section's honest-state paragraph, or the affected step's

DONE WHEN / status, or striking a step the work closed. Touching the section to touch it is a

defect: the instrument reads git, and a whitespace commit satisfies it while lying to Josh. **The row

must say something that was not true before the work landed.**

C4 — THE ESCALATION. A row that cannot be moved honestly — the work contradicts the plan, or the

pipeline needs a ruling before its next step can be written — is one line in

build/ops/NEEDS_DIRECTOR.md. It is not left stale in silence.

C5 — ADVISORY, NOT A GATE. roadmap_freshness.py exits 0 whatever it finds. This is deliberate

under the CREATION-FIRST LAW and the gate moratorium: it ships as a surface, not as a new red

gate in run_gates.py. --strict exists for a caller that wants teeth, and turning it on anywhere

is a decision with a named owner, not a default.

C6 — THE DEPLOY AXIS BELONGS TO THE SITE LANE. A fresh document behind a stale deploy still

shows Josh the old plan, because the site's roadmap pages are generated from the document at

build time (harness/site/roadmap.py:54). This instrument reports how far the published commit

trails HEAD, read from build/site_deploy_record.json; it never builds or deploys. Closing that

axis is harness/site/build_progress_site.py's (see §5).

3 · WHAT IS MEASURED, AND HOW

harness/roadmap_freshness.py subtracts two dates per pipeline, both read off git:

section's own last-change commit. A file-level date is a lie on a document where one section

is edited daily and six have not moved since they were written: the file would have read

"2026-08-10" and every pipeline would have looked current. The line range is resolved from the

live heading, and a section ends at the next heading of the same or shallower depth — the same

swallow-the-following-chapters bug harness/site/roadmap.py:94-100 records having shipped once.

game build) whose touched paths classify to that pipeline's family.

The classifier is not redefined. It is imported from harness/site/ledger.py — the same

FAMILIES path rules, the same majority vote, the same NOISE co-traveller list already used by

the live ledger and the site's roadmap pages. A second copy of those rules would be a second answer

to one question and it would drift in exactly the silence this instrument exists to break. The

mapping in harness/roadmap_freshness.py:SURFACES therefore carries no path globs of its own

only the binding from roadmap section to family id, which is the one thing ledger.py does not

already know.

Two tolerance axes, and both are load-bearing. A row is STALE when either is exceeded:

more than --grace days (default 1) or more than --grace-commits landings (default 3) behind. A

wave lands several commits and updates its row at the close, so an in-flight wave must not read red;

but the first run proved a day-only rule blind — music-audio read FRESH at a one-day gap while

carrying thirteen landings its section did not mention. A pipeline can move a long way inside a

day.

Pure-bookkeeping landings are not work. A commit whose every path is a ledger.NOISE

co-traveller (a scorecard rewrite, a DOC_MAP row, the roadmap document itself) does not count.

Without that rule the roadmap would be made stale by its own update

(docs/ROADMAP_VERTICAL_SLICE_*.md is itself a NOISE path), and a measurement that can never read

green measures nothing.

Verdicts. FRESH · STALE (with the gap in days AND the count of landings since) · IDLE (no

landings in the window — reported as its own state, never as freshness, because an uncontrolled zero

is the failure this repo names by name) · MISSING (the surface or its heading is gone — reported

loudly, because a row that silently vanishes from a freshness table is the freshness table lying).

3.1 The two ways the naive measurement loses commits

Recorded because both were hit while cross-checking the first run, and both are silent:

current time of day, so a run at 06:12 silently excludes everything that landed before 06:12

on the boundary day. Two real music landings (e4e27141, 4aa90b87, both 00:xx on 2026-08-09)

vanished from the cross-check for exactly this reason.

parent, so a pathspec-filtered log is not a complete list of commits touching that path.

The instrument avoids both: it reads the full log with --name-only over an N days ago window

(ledger.git_log) and classifies paths itself. Anyone re-deriving these numbers with a

pathspec-filtered --since=<date> query will get a smaller answer, and the instrument is the

one that is right.

4 · THE SELF-TEST

python harness/roadmap_freshness.py --self-test — eight positive controls, each named for the

failure it catches: every surface heading still resolves (C1); section ranges are non-degenerate and

disjoint (C2); every family id bound here exists in the ledger, so no row is bound to a bucket

nothing can fall into and reads FRESH forever (C3); the work sweep actually matched in at least

three families, so a dead sweep cannot masquerade as a quiet factory (C4); the day arithmetic and

the grace threshold mutate in both directions (C5); the commit axis fires on its own, controlled

with the exact case only it can catch — inside the day grace, past the landing grace (C5b); the

roadmap document is confirmed to be a ledger co-traveller (C6); and the emitted channel block is

parsed by the SITE'S OWN reader, so a format regression fails the self-test instead of silently

renaming the writer and undating its report on Josh's phone (C7).

5 · THE SITE-BUILD HOOK (SPEC — for the site lane to call, not implemented here)

harness/site/build_progress_site.py belongs to the site lane; this file does not edit it. The hook

it should call, when that lane wants it:

import roadmap_freshness                     # harness/ is already on the path at build time
fresh = roadmap_freshness.measure()          # dict; never raises on a missing surface

its verdict, the date its row moved, and the landings-since count. A roadmap page that shows its

own staleness cannot mislead the reader who is looking straight at it.

shape from the page's own side.

inside a build that a critic may run on a shared tree.

6 · CONSUMERS

surfaces.

classifier.

7 · THE FIRST READING (2026-08-10, HEAD 662562c5)

Window 42 days, grace 1 day / 3 landings. Verdicts as measured, not as hoped:

pipelineverdictrow movednewest landinggaplandings sincebreach
qa-opsSTALE2026-08-082026-08-102d43days+commits
music-audioSTALE2026-08-082026-08-091d13commits
world-engineSTALE2026-08-082026-08-102d1days
charactersFRESH2026-08-102026-08-100d0
quests-runtimeFRESH2026-08-082026-08-080d0
pipeline-ledgerFRESH2026-08-102026-08-100d0
combat-systemsFRESH2026-08-082026-08-07-1d0
ui-voFRESH2026-08-082026-08-06-2d0

Reading it honestly. Only characters (§4.1) and PIPELINE_LEDGER.md have been edited since the

roadmap document landed on 2026-08-08 (2d7d6e0f); the other six sections still carry their landing

text verbatim. Three pipelines are genuinely behind their work. The four FRESH §4 sections other

than Characters are fresh because their pipelines have been quiet, not because anyone maintained

them — combat-systems and ui-vo show negative gaps, meaning the document is newer than the last

work in those families. That is a real pass, and it is a weak one: the contract has not yet been

tested by a busy pipeline other than Characters.

Not claimed. This measures whether a section was EDITED after work landed. It cannot measure

whether the edit was TRUE — C3 is a rule, not a gate, and no instrument reads the prose. A seat that

touches a section without saying anything new satisfies the instrument and violates the contract.

Generated by harness/site/structure_site.py — the URL path is the repo path. review root