ADR-0065 The two expectations meet, and the last list leaves the CLI
#Context
ADR-0064 moved the scene roster and the themed set's two lists into data modules, because four consumers had grown around one array that could not be imported and two of them were wrong. It ended by naming what it had not done: *the MUST_FAIL list inside cli.js — the list whose comment caused the first wrong figure — is still unreachable and still read by nothing but the CLI. Smaller, because only one consumer wants it.*
That last clause was wrong, and moving the list is what showed it. A second consumer wants it, and the assertion it enables is one nothing in this repository was making.
#A scene is declared pass or fail twice, in two lists that never met
The engine roster carries expect: 'pass' | 'fail' per scene. The themed set carries MUST_FAIL, a list of scene prefixes whose siblings are expected to fail. They can disagree, and one direction of the disagreement is silent.
A scene declared fail unthemed and missing from the themed list is caught by the themed run itself: its siblings fail while expected to pass, loudly, on the next CI run.
The reverse is not caught by anything. A scene declared pass unthemed and present in the themed list expects its siblings to fail — and if they do, the run is green while the themed set asserts the opposite of what the engine asserts about the same screen. Nothing compared the two, because nothing could read one of them.
#Decision
THEMED_MUST_FAIL moves to packages/budget/src/themed-must-fail.js, verbatim with its comments, and cli.js imports it. That is the last of the three lists the CLI held, and cli.js now holds no data at all.
themed:check gains two assertions:
*A scene declared must-fail is declared must-fail in both halves.* Compared only for scenes on both lists — 06-overlay-violation is themed, must-fail, and has no unthemed expectation because it is not on the roster, which is the case proving the filter is not pedantry.
*Every themed must-fail prefix names a scene the themed set holds.* A prefix matching nothing is a scene that left and took its reason with it — ADR-0041's stale-entry shape, applied to a list of prefixes rather than of files.
The check is 10 assertions, up from 8.
#What the move corrected
The list holds thirteen entries. ADR-0064 said fourteen. That figure came from the same lossy parse the record was written to condemn — the more careful of the two scans, which dropped entries sharing a line — and it went into a record about not trusting scans of that file. It is corrected in ADR-0064, in playbook §15 and in docs/STATUS.md.
There is no better argument for this pass than that. A record naming a parsing hazard carried a figure produced by that hazard, and the only thing that settled it was importing the array and counting it.
#Rejected options
Leave the list where it is, since the CLI is its only consumer. ADR-0064's own reasoning, and it was wrong for a reason worth stating: *"only one consumer wants it"* describes the present tense of a repository, not a property of the data. The second consumer appeared the moment the list could be read, and the assertion it makes possible had been absent since the themed set was written. A list that cannot be read has no second consumer by construction, which is not the same as not needing one.
Assert the agreement inside the themed run instead. Cheaper — expectationFor already has both facts in scope at run time. Rejected because the themed run needs a browser, 170 renders and several minutes, and this is a comparison of two arrays. A check that reads two lists should not require a rasteriser, and themed:check runs in milliseconds in the same CI job as the other list checks.
Derive the themed expectations from the roster's expect and delete MUST_FAIL. The tempting collapse: one list, no disagreement possible. Rejected because the two are not the same claim. 06-overlay-violation is must-fail in the themed set and is not on the roster at all, and the themed set restricts it to one theme precisely because its verdict *does* move with the palette — an emission failure that would legally pass in the dark themes. A derived list could not express that, and the exclusion machinery ADR-0064 built exists because these two sets genuinely differ at the edges.
#Consequences
cli.js holds no data. Three lists have left it across two passes — the scene roster, the themed set's two, and now the must-fail prefixes — and every one is importable by whatever wants it.
themed:check is 10 assertions; the battery stays at 23 checks and playbook §10 at 27 rows, because this is the same check doing more rather than a new one.
All three new breakage cases observed red before being trusted green (ADR-0041): a passing scene added to the themed must-fail list, a must-fail scene removed from it, and a prefix pointed at a scene that does not exist. Each named the scene and the direction of the disagreement.
One figure in ADR-0064 corrected, and the correction is in that record rather than only here, because a record that quietly stops being wrong is indistinguishable from one that was never checked (ADR-0049).
#Measured
THEMED_MUST_FAIL: 13 entries, counted by importing the module. ADR-0064 said 14, from a scan that drops entries sharing a line.themed:check: 8 → 10 assertions. 53 scenes on both lists, every expectation agreeing; 13 must-fail prefixes, each matching a themed scene.- Three breakage cases observed red and restored green.
cli.js: three data lists moved out across ADR-0064 and this record; none remain.pnpm battery— the verdict this pass is measured by.