Proposal (rationale)
Promoted from F-011, 2026-08-16.
The site’s Console component takes date and source and renders them as the literal label
real output · <command>. Nothing checks that the lines beneath it were ever produced by that
command.
This is not hypothetical. WI-040 found two of the three console blocks on the landing page fabricated, both carrying a date and a source:
npx @rungs/cli doctorwas shown emitting “this rule says MANDATORY and has no gate” — a linedoctordid not produce until WI-038 shipped--explain, weeks later.npx @rungs/cli add concurrencywas shown promptingInstall anyway? [y/N]. rungs is non-interactive; the real path skips the module and names--confirm-threshold.
The external reviewer read the first block, concluded the capability shipped, and built their strongest recommendation on it. The three blocks are correct now; the mechanism that let them drift is untouched, and the next console added has the same hole.
A page asserting provenance it cannot support is precisely what CLAUDE.md’s evidence rule exists to prevent, on the most-read surface in the repo — and the component’s own fallback for a missing date already says “unverified output — date the transcript”, so the design knew the distinction and made the wrong half the easy one.
Decision
Undecided, and the constraint that shapes it is worth stating before anyone plans this: the
component is vendored. site/src/design-system/ is generated, says “do not edit any file in this
directory”, and is now sha-gated by site-vendored-unedited. Its source export is gitignored and
not in this checkout. So the interim F-011 suggested — rename source so it stops implying
provenance — cannot be done from the repo. Any fix is either a re-vendor from the design tool or
something that lives at the call sites.
Plan
Filled on acceptance.
Requirements
- A console block claiming recorded output is traceable to a captured transcript, or it does not get the label.
- Adding a block with invented lines fails a gate rather than a review.
- The check works without a network and without running the CLI at build time, or if it does run the CLI, it does so against a fixture rather than the author’s machine.
Impacts
site/src/pages/index.astroand any other call site.- Possibly
site/src/design-system/— which means a re-vendor, not an edit. - A new gate, and a place for captured transcripts to live.
Approach
Two candidates, to be decided with the constraint above in front of whoever decides:
- Transcripts as data. Captured output lives in a checked-in file per command; the page reads
it and the component renders it. A gate re-runs the command against a fixture repo and diffs.
Strongest, and it is the
generate-derivablepattern the landing page’s ownMODULESarray is already aTODOagainst. - Call-site gate only. Every
Consolecarryingsourcemust name a transcript file that exists and whose recorded command matches. Cheaper, catches invention, does not catch drift after the command’s output changes.
Acceptance criteria / tests
- A console block with invented lines fails a gate.
- The three current blocks pass without being rewritten — they are already true.
- The gate states what it does not cover.
rungs check,npm test, and the site build pass.
Out of scope
- Re-checking the three current blocks by hand. WI-040 did that; this is about the mechanism.
- Any other design-system change. A re-vendor for this reason only, if a re-vendor happens.
Execution
Branch feature/WI-060-console-provenance, cut from main at 77ec2ed. Approach 1 — transcripts
as data — because approach 2 catches invention and not drift, and drift is what put a fabricated
doctor block on the landing page for weeks.
site/scripts/transcripts.mjsbuilds its own fixture repos and runs the real command in them. A transcript captured against the author’s working tree is not reproducible by anyone else, which is the same defect one step along.npm run transcriptswritessite/src/generated/transcripts.json(committed, so a pristine checkout builds);npm run check:transcriptsverifies the pages against it, registered as thesite-transcripts-realgate. Gate count 24 → 25.
The invariant is subset-in-order, not equality
A console box is narrow and the hero shows six lines of a forty-line doctor run. Requiring equality
would force the page to print everything; requiring nothing would be the status quo. So: every
displayed line must appear in the capture, in order.
Two refinements came from the gate rejecting honest blocks:
- The
cmdline is a prompt echo, not output. Checkingnpx @rungs/cli doctoragainst its own output failed every block. - Substring, not equality per line.
add concurrencyprints its refusal and the--confirm-thresholdhint on one line; the page wraps it across two. Real text, rejected by an exact match. The looser rule still makes fabrication impossible — see Review 1.
The third block was not output at all
versions.astro carried a Console whose own source read “the procedure, not a transcript”
while the component rendered REAL OUTPUT above it — honest in its text, contradicted by its
chrome. It is a summary of cut-release/SKILL.md, so it is now a plain <pre> with a caption
saying so. The component is vendored and could not be changed; the call site could.
One thing found in passing
versions.astro also carried a hand-typed table of fifteen module versions, and three were already
wrong — backlog, adr and session were bumped this week. Now derived from the claims snapshot,
which gained a version field. Same class WI-051 fixed for gate counts, on a page it had missed.
Review
Verified 2026-08-16.
1 · A console block with invented lines fails the gate. Tested with the actual fabricated line — the one that was live on the site and that an external reviewer read as shipped capability:
check-transcripts: 1 console line(s) claim output that was never produced:
index.astro: "this rule says MANDATORY and has no gate" is not in the captured
output of `npx @rungs/cli doctor`
Restored, passes. Met — and this is the strongest available test, because it is the failure that actually happened rather than an invented one.
2 · The current blocks pass without being rewritten. Both landing-page consoles match their captures unchanged. The two matcher refinements were made because the gate was wrong about honest blocks, not because the blocks were wrong. Met.
3 · The gate states what it does not cover, on every run:
It does not check that the capture is current — re-run `npm run transcripts` for that.
It matches each line as a substring, so a page may wrap or trim a long emitted line.
It says nothing about lines a page chose to omit: a subset is allowed, by design.
Met.
4 · Gates, tests, build. rungs check 25 pass · 0 fail · 0 unimplemented · 0 error;
npm test 24 pass; site builds 128 routes, 2,042 links, 0 broken. Confirmed in the browser: the
landing page shows exactly two real output · labels, both transcript-backed, and the versions page
shows none — its procedure block renders with the caption “Summarised from
modules/release/skills/cut-release/SKILL.md. Not command output.”, and the module table now reads
adr 1.1.0, backlog 1.1.0. Met.
What is still asserted rather than proved
The capture’s freshness. check-transcripts reads the committed transcript rather than
re-running the CLI, because a gate that rungs check runs must not invoke its own runner and must
work in a pristine checkout. So a command whose output changes goes stale until someone runs
npm run transcripts — and the diff is at least visible in review, which the previous state was not.
Stated in the gate’s own output rather than left for a reader to discover.