clearotron 0.4.0-beta.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/THIRD-PARTY-NOTICES.md +5 -5
  2. package/build-info.json +2 -2
  3. package/driver/CHANGELOG.md +90 -0
  4. package/driver/ask-ledger.mjs +2 -2
  5. package/driver/coverage-form.mjs +6 -0
  6. package/driver/coverage-ledger.mjs +14 -2
  7. package/driver/driver.config.mjs +24 -7
  8. package/driver/engine/mcp/band-server.mjs +3 -3
  9. package/driver/engine/mcp/stdio-server.mjs +12 -1
  10. package/driver/gateway.mjs +27 -3
  11. package/driver/package.json +1 -1
  12. package/driver/pipeline-knockout.mjs +41 -10
  13. package/driver/pipeline.mjs +104 -12
  14. package/driver/progress.mjs +6 -1
  15. package/driver/provider-usage.mjs +16 -0
  16. package/driver/publish/index.mjs +11 -5
  17. package/driver/publish/knockout.mjs +16 -3
  18. package/driver/publish/render-knockout.mjs +38 -11
  19. package/driver/record-carry.mjs +17 -1
  20. package/driver/reference-strip-signatures.mjs +11 -1
  21. package/driver/register-plan.mjs +7 -3
  22. package/driver/register-served.mjs +91 -0
  23. package/driver/remedy-accounting.mjs +38 -9
  24. package/driver/reviewer-open-points.mjs +20 -23
  25. package/driver/score-redaction.mjs +416 -20
  26. package/driver/screen-gate.mjs +3 -3
  27. package/driver/skills/clearance-register/SKILL.md +0 -8
  28. package/driver/skills/clearance-register/digest.md +1 -1
  29. package/driver/skills/clearance-register/providers/corsearch.md +1 -1
  30. package/driver/skills/clearance-register/register-recipes.md +3 -9
  31. package/driver/skills/clearance-search/SKILL.md +2 -2
  32. package/driver/skills/clearance-search/phase2-execution.md +1 -1
  33. package/driver/skills/knockout-assess/SKILL.md +2 -0
  34. package/driver/stages-knockout.mjs +22 -0
  35. package/driver/suite-census.json +152 -26
  36. package/driver/unit-inventory.mjs +38 -43
  37. package/mcp-server/CHANGELOG.md +8 -0
  38. package/mcp-server/package.json +1 -1
  39. package/node_modules/brace-expansion/index.js +78 -22
  40. package/node_modules/brace-expansion/package.json +1 -1
  41. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  42. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  43. package/package.json +1 -1
  44. package/portal-ui/package.json +2 -2
  45. package/providers/_shared/term-shape.mjs +1 -1
  46. package/providers/jx/src/core.js +0 -1
  47. package/providers/jx/src/judge.js +0 -1
  48. package/providers/jx/src/nativeread.js +0 -1
  49. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  50. package/providers/oauth-mcp-bridge/package.json +1 -1
  51. package/providers/signa/src/core.js +125 -17
  52. package/scripts/e2e-first-time.mjs +12 -4
  53. package/scripts/e2e-scenario-ops.mjs +25 -2
  54. package/scripts/e2e.mjs +246 -24
  55. package/scripts/mint-reference-strip-backlog.mjs +36 -2
  56. package/scripts/score.mjs +102 -29
  57. package/shared/identifier-scan.mjs +31 -1
package/scripts/e2e.mjs CHANGED
@@ -57,6 +57,7 @@ import { isLiveQueueMarker, isQueueSidecar, liveQueueState, LIVE_QUEUE_STATES, T
57
57
  // leave the real one to park tomorrow's re-run as a duplicate. usage-ledger.mjs is a pure leaf too
58
58
  // (node:fs + node:path + queue-markers.mjs), so it drags no driver machinery in either.
59
59
  import { matterLedgerPath } from "../driver/usage-ledger.mjs";
60
+ import { registerServedLine } from "../driver/register-served.mjs"; // which register a run recorded as having served it
60
61
  import { probeWorker } from "../driver/queue-watch-probe.mjs"; // the drain this deployment actually has
61
62
  // Teardown asks whether a process is actually producing a run before it rewrites the record that says so.
62
63
  import { claimLivenessForCodename, claimForbidsDestruction } from "../driver/claim-liveness.mjs";
@@ -336,6 +337,75 @@ const allScenarios = () => {
336
337
  .sort((a, b) => byScenarioNumber(a.id, b.id));
337
338
  };
338
339
 
340
+ // ── A SCENARIO MAY NAME THE REGISTER ITS NUMBERS WERE MEASURED AGAINST ──────────────────────────────
341
+ //
342
+ // THE DEFECT THIS CLOSES. A scenario's register floors are counts, and a count is an answer one register
343
+ // gave. Run the same scenario against a different register and the floor decides nothing about the
344
+ // engine: measured 2026-09-30 on a knockout whose floors are `identical: 8` and `containing: 300`, the
345
+ // other register answered 2 and 471 for the SAME mark. One floor failed and one passed, in one run, and
346
+ // neither outcome was about the engine. A red like that costs a round: it reads as a regression, it is
347
+ // investigated as one, and the run that produced it is already spent.
348
+ //
349
+ // WHAT THIS DOES NOT DO, deliberately. It does not encode which register returns more. The measurement
350
+ // says the two DISAGREE — 2 against 8 on one predicate and 471 against 300 on another, in opposite
351
+ // directions — and nothing here has established why. A floor per register would need that why, and a
352
+ // harness rule must not smuggle one in. So a scenario states which register its numbers came from, and
353
+ // the harness refuses to spend the run anywhere else.
354
+ //
355
+ // OPTIONAL BY CONSTRUCTION, because the store is a DIFFERENT REPO. A scenario that declares nothing
356
+ // behaves exactly as it does today. There is no ordering problem to manage and no day on which this
357
+ // harness refuses a store that has not caught up.
358
+ //
359
+ // WHY THIS DOES NOT ASK THE DRIVER. `driver.config.mjs` resolves the provider, and this file may not
360
+ // import it — its unset-env defaults are PRODUCTION, which is the rule stated at four points above. The
361
+ // variable is read here directly and with no fallback: unset stays unset, and unset is reported as its
362
+ // own state rather than resolved to anything.
363
+ //
364
+ // CONFIGURED, NOT SERVED, and the difference is a live gap. What a run was configured for is the
365
+ // launch value; what actually served it is a separate record the runs do not yet carry (measured
366
+ // 2026-09-30: absent on all four runs of a round, on both the knockout and the clearance path). When
367
+ // that record exists, an assertion could read what served. Until then this is a PRE-RUN refusal on the
368
+ // configured value, which is the honest instrument available — and it is placed before the spend, where
369
+ // being approximately right still saves the money.
370
+ export const REGISTER_ENV = "CLEAROTRON_DATABASE";
371
+
372
+ /** The registers a scenario declares its numbers were measured against, lowercased. `[]` when it declares none. PURE. */
373
+ export function registersDeclaredBy(sc) {
374
+ const v = sc?.register;
375
+ const list = Array.isArray(v) ? v : v == null ? [] : [v];
376
+ return list.map((x) => String(x ?? "").trim().toLowerCase()).filter(Boolean);
377
+ }
378
+
379
+ /** The register this process is configured for, or `null` when the variable names none. No default, ever. PURE apart from the read. */
380
+ export function configuredRegister(env = process.env) {
381
+ return String(env?.[REGISTER_ENV] ?? "").trim().toLowerCase() || null;
382
+ }
383
+
384
+ /**
385
+ * The refusal text for running `sc` here, or `null` when there is nothing to refuse. PURE.
386
+ *
387
+ * Three cases, and the middle one is the reason this returns text rather than a boolean: a scenario that
388
+ * declares a register and an environment that names none is NOT a match and NOT a mismatch. Running it
389
+ * would spend against whatever the driver defaults to, which this file is not allowed to ask about, so
390
+ * the refusal says exactly that rather than guessing either way.
391
+ */
392
+ export function registerRefusal(sc, env = process.env) {
393
+ const want = registersDeclaredBy(sc);
394
+ if (!want.length) return null;
395
+ const have = configuredRegister(env);
396
+ const declares = want.length === 1 ? want[0] : `one of ${want.join(", ")}`;
397
+ if (!have) {
398
+ return `${sc.id} states that its numbers were measured against ${declares}, and ${REGISTER_ENV} names no register `
399
+ + `on this instance. This harness does not resolve a default — the module that would is the one it may not import. `
400
+ + `Set ${REGISTER_ENV} to ${declares} and run again.`;
401
+ }
402
+ if (want.includes(have)) return null;
403
+ return `${sc.id} states that its numbers were measured against ${declares}, and this instance is configured for ${have}. `
404
+ + `What one register answered is not what another answers, so here this scenario would judge the register `
405
+ + `and not the engine — and a red read as a regression arrives on a run that is already spent. `
406
+ + `Set ${REGISTER_ENV} to ${declares}, or run a scenario whose numbers were measured against ${have}.`;
407
+ }
408
+
339
409
  // ── — the store is the input to the most expensive thing this repo does ─────────────────────────
340
410
  //
341
411
  // Every job block in the store, through BOTH admission gates, against the outcome the scenario declares.
@@ -439,6 +509,20 @@ export function lintScenarios(scenarios) {
439
509
  for (const f of sc.expect?.artifacts ?? []) {
440
510
  if (/^\/|\.\./.test(f)) wrong.push(`${label}: artifact ${JSON.stringify(f)} must be a plain run-relative name`);
441
511
  }
512
+ // ── `register`, IF STATED, MUST BE STATABLE ────────────────────────────────────────────────────
513
+ //
514
+ // SHAPE ONLY, AND NOT THE NAME. Checking the value against the known providers would mean importing
515
+ // `driver.config.mjs`, which this file may not do. That is not a hole worth patching another way: a
516
+ // misspelled register never runs anywhere, and the refusal it produces prints the scenario's word
517
+ // and the instance's word side by side, which is a typo shown rather than described. What IS worth
518
+ // refusing is a value nothing can compare at all — an empty string, a number, an empty list — since
519
+ // that would declare a register and then silently compare against nothing.
520
+ if (Object.prototype.hasOwnProperty.call(sc, "register")) {
521
+ const raw = Array.isArray(sc.register) ? sc.register : [sc.register];
522
+ const bad = raw.length === 0 || raw.some((x) => typeof x !== "string" || !x.trim());
523
+ if (bad) wrong.push(`${label}: \`register\` is ${JSON.stringify(sc.register)} — it must be a non-empty register name, or a non-empty list of them. `
524
+ + `A scenario states this when its numbers are counts one register returned, so that a run against another is refused before it spends.`);
525
+ }
442
526
  const allAsserts = [...(sc.expect?.assert ?? []), ...(sc.cases ?? []).flatMap((c) => c.expect?.assert ?? [])];
443
527
  for (const a of allAsserts) {
444
528
  if (/^_driver\/scope-ledger\.json/.test(String(a.path ?? ""))) {
@@ -1213,6 +1297,11 @@ export function outboxPackets({ runId = null, runIds = [], queueBase = null } =
1213
1297
  return { packets: [...hit].sort(), unreadable: null };
1214
1298
  }
1215
1299
 
1300
+ // WITHHELD UNLESS ASKED, and the default is off so nothing has to remember to turn it on. Set once from
1301
+ // argv; `evalAssertion` takes it as an option so a test can drive both paths without touching argv.
1302
+ export let REPORT_NAMES = false;
1303
+ export const setReportNames = (v) => { REPORT_NAMES = Boolean(v); };
1304
+
1216
1305
  // ── assertions ───────────────────────────────────────────────────────────────────────────────────────
1217
1306
  // Every op is implemented or explicitly reported UNIMPLEMENTED. An op that silently passes because
1218
1307
  // nobody wrote it is the exact failure mode this whole suite exists to prevent.
@@ -1339,7 +1428,70 @@ function bandShapeUnder(full, file, field) {
1339
1428
  return { doc, unread, shallow, verdict };
1340
1429
  }
1341
1430
 
1342
- function evalAssertion(a, runDir) {
1431
+ /**
1432
+ * A quotation from a RUN, or where to find it.
1433
+ *
1434
+ * WHY THIS EXISTS. `report` is the cheap instrument — free, re-runnable, and the doctrine tells a lane to
1435
+ * re-run it rather than re-run the clearance — so it is the one most often pointed at a real matter, in a
1436
+ * session that records whatever it prints. Its INVESTIGATE rows earned their usefulness by showing the
1437
+ * sentence they object to, and that sentence is the engine's own words about a client's matter.
1438
+ *
1439
+ * A LOCATION IS AS ACTIONABLE AS A QUOTE AND CARRIES NOTHING. "sentence 14 of knockout-findings.md" sends
1440
+ * a reader to the same words, in the file that already holds them, without copying them anywhere else.
1441
+ * The one job that needs them on the page — reading a surprising row against the text — asks with
1442
+ * `--names`, the same flag the scorer takes for the same reason.
1443
+ *
1444
+ * The count is never withheld. How many claims exceeded their evidence is the finding; which words they
1445
+ * used is the detail.
1446
+ */
1447
+ /**
1448
+ * One door's refusal line.
1449
+ *
1450
+ * A DOOR'S REFUSAL TEXT IS THE DOOR'S, NOT OURS, and that is the reason to gate it rather than a reason
1451
+ * not to. Measured across every receipt on this box — 397 door answers, 213 refusals, 113 distinct
1452
+ * reasons — not one echoes the order: they carry an HTTP status and the server's own error envelope, or
1453
+ * a socket error. So this guards a case that has not happened, and it is worth one line, because the
1454
+ * half of these that CAN echo is structural: an `initialize` refusal is sent before any order exists and
1455
+ * cannot carry one, a `tools/call` refusal is sent after and can. Which it does is the door's own
1456
+ * error-message discipline, which nothing here governs and no clipping saves — an echoing server puts
1457
+ * the mark near the front, well inside the 160 characters this used to print.
1458
+ *
1459
+ * A MISSING REASON IS NOT A WITHHELD ONE. "(no reason recorded)" is a fact about the receipt and says so
1460
+ * plainly; offering to print it with `--names` would promise words that do not exist.
1461
+ */
1462
+ export function doorReasonLine(r, names = REPORT_NAMES) {
1463
+ if (r?.reason == null || String(r.reason).trim() === "") return "(no reason recorded)";
1464
+ return fromTheRun({ names, quotes: [String(r.reason)],
1465
+ where: "the door's own words are in the round receipt, at rounds[].cases[].answers[].reason" });
1466
+ }
1467
+
1468
+ function fromTheRun({ names, quotes, where }) {
1469
+ // THE LOCATION IS KEPT WHEN THE WORDS ARE SHOWN. It costs one clause beside the quotation it stands in
1470
+ // for, and a reader chasing a surprising row wants to go and read around it rather than only at it.
1471
+ if (names) return `${quotes.map((q) => `"${String(q).slice(0, 160)}"`).join(" · ")} (${where})`;
1472
+ return `${where} — run again with --names to read them here`;
1473
+ }
1474
+
1475
+ /**
1476
+ * The row that fires when a listing does not hold the mark an assertion asked for.
1477
+ *
1478
+ * It is the WIDEST row in either floor op: it printed the name asked for and every name the listing did
1479
+ * hold, in clear, with no flag involved, and it fires precisely when somebody is investigating — a mark
1480
+ * is absent, so a reader is reading the report. It exists twice, once per floor op, with one verb each.
1481
+ *
1482
+ * The COUNT is never withheld. A listing that held nothing is a different defect from one that held four
1483
+ * other marks, and that distinction is the finding.
1484
+ */
1485
+ function noSuchMark({ names, field, marks, file, verb }) {
1486
+ const listed = (marks ?? []).map((x) => x?.name).filter((x) => typeof x === "string");
1487
+ return { ok: false, saw: `the asked-for mark is not in ${file}, which ${verb} ${listed.length} mark(s)`
1488
+ + (listed.length
1489
+ ? ` — ${fromTheRun({ names, quotes: [field, ...listed],
1490
+ where: `the name asked for is the field of this assertion's path, and the names ${verb} are the "marks" array of ${file}` })}`
1491
+ : "") };
1492
+ }
1493
+
1494
+ function evalAssertion(a, runDir, { names = REPORT_NAMES } = {}) {
1343
1495
  const [file, field] = String(a.path ?? "").split(":");
1344
1496
  const full = join(runDir, file || "");
1345
1497
 
@@ -1566,13 +1718,19 @@ function evalAssertion(a, runDir) {
1566
1718
  const held = recordsFile
1567
1719
  ? (recordsFile.marks ?? []).reduce((n, m) => n + (m.records ?? []).length, 0)
1568
1720
  : null;
1569
- const bad = sentences.filter((s) => aboutRegister.test(s) && !staysInside.test(s)
1721
+ // Carried WITH ITS POSITION, because the index is what a withheld row prints instead of the words.
1722
+ // `indexOf` on the sentence would be wrong the moment a document repeats one: both rows would send
1723
+ // the reader to the first copy, and a run that says the same thing twice is exactly the run somebody
1724
+ // is reading the report about.
1725
+ const badAt = sentences.map((s, i) => ({ s, i })).filter(({ s }) => aboutRegister.test(s) && !staysInside.test(s)
1570
1726
  && (exceedsACount.test(s) || (singularAbsence.test(s) && !(held > 0))));
1727
+ const bad = badAt.map(({ s }) => s);
1571
1728
  const basis = held === null
1572
1729
  ? "_driver/register-records.json is absent or unreadable, so this is as strict as a run holding zero records — identical verdict, different reason: one is a fact about the search, this is a fact about the check's own evidence"
1573
1730
  : `the run holds ${held} register record(s), so a status read off one is supported and only a claim over the field is not`;
1574
1731
  return { ok: bad.length === 0,
1575
- saw: bad.length ? `${bad.length} register claim(s) wider than the records this run holds — ${bad.slice(0, 2).map((s) => `"${s.slice(0, 120)}"`).join(" · ")} (${basis})`
1732
+ saw: bad.length ? `${bad.length} register claim(s) wider than the records this run holds — `
1733
+ + `${fromTheRun({ names, quotes: bad.slice(0, 2), where: `sentence ${badAt.slice(0, 2).map(({ i }) => i + 1).join(" and ")} of ${file}` })} (${basis})`
1576
1734
  : `${sentences.filter((s) => aboutRegister.test(s)).length} register sentence(s), each a count, a labelled expectation, or a status the records support — ${basis}` };
1577
1735
  }
1578
1736
  // `survivor-not-clear` — a mark this lane did not knock out is a SURVIVOR, never a clear. The two words
@@ -1712,7 +1870,7 @@ function evalAssertion(a, runDir) {
1712
1870
  const want = field ? String(field).trim().toLowerCase() : null;
1713
1871
  if (!want) return { ok: false, saw: "no mark given (path must be <file>:<MARK NAME>)" };
1714
1872
  const m = (doc.marks ?? []).find((x) => String(x?.name ?? "").trim().toLowerCase() === want);
1715
- if (!m) return { ok: false, saw: `no mark ${JSON.stringify(field)} in ${file} — it counted ${(doc.marks ?? []).map((x) => JSON.stringify(x?.name)).join(", ") || "nothing"}` };
1873
+ if (!m) return noSuchMark({ names, field, marks: doc.marks, file, verb: "counted" });
1716
1874
  const floors = a.value && typeof a.value === "object" ? a.value : null;
1717
1875
  if (!floors) return { ok: false, saw: "value must be an object of predicate floors, e.g. {\"identical\": 45}" };
1718
1876
  const saw = [], short = [], untaken = [];
@@ -1720,7 +1878,12 @@ function evalAssertion(a, runDir) {
1720
1878
  const cell = m.counts?.[pred];
1721
1879
  if (!cell) { untaken.push(`${pred}: no such predicate on this run's sidecar`); continue; }
1722
1880
  if (!Number.isFinite(cell.total)) {
1723
- untaken.push(`${pred}: NOT TAKEN — ${String(cell.unavailable ?? "no reason recorded").slice(0, 160)}`);
1881
+ // THE SAME FIELD AND THE SAME PROVENANCE as the `unavailable` twenty lines below, which is
1882
+ // withheld — this one is the engine's own prose about why a count could not be taken, out of the
1883
+ // run's sidecar. Withheld with it, and located by the predicate it belongs to.
1884
+ untaken.push(`${pred}: NOT TAKEN — ${cell.unavailable === undefined || cell.unavailable === null
1885
+ ? "no reason recorded"
1886
+ : fromTheRun({ names, quotes: [String(cell.unavailable)], where: `the reason is counts.${pred}.unavailable of ${file}` })}`);
1724
1887
  continue;
1725
1888
  }
1726
1889
  saw.push(`${pred}=${cell.total} (floor ${floor})`);
@@ -1744,11 +1907,14 @@ function evalAssertion(a, runDir) {
1744
1907
  if (!existsSync(full)) return { ok: false, saw: `${file} absent — the filings lane wrote nothing, not even its refusal` };
1745
1908
  const doc = readJson(full);
1746
1909
  if (!doc) return { ok: false, saw: `${file} present but unparseable` };
1747
- if (doc.unavailable) return { ok: false, saw: `the filings were never listed — ${String(doc.unavailable).slice(0, 200)}. A listing that was refused is not a register that holds nothing.` };
1910
+ if (doc.unavailable) return { ok: false, saw: `the filings were never listed — `
1911
+ + `${fromTheRun({ names, quotes: [String(doc.unavailable)], where: `the reason is the "unavailable" field of ${file}` })}. `
1912
+ + `A listing that was refused is not a register that holds nothing.` };
1748
1913
  const want = field ? String(field).trim().toLowerCase() : null;
1749
1914
  if (!want) return { ok: false, saw: "no mark given (path must be <file>:<MARK NAME>)" };
1750
- const m = (doc.marks ?? []).find((x) => String(x?.name ?? "").trim().toLowerCase() === want);
1751
- if (!m) return { ok: false, saw: `no mark ${JSON.stringify(field)} in ${file} — it listed ${(doc.marks ?? []).map((x) => JSON.stringify(x?.name)).join(", ") || "nothing"}` };
1915
+ const markIndex = (doc.marks ?? []).findIndex((x) => String(x?.name ?? "").trim().toLowerCase() === want);
1916
+ const m = markIndex === -1 ? null : doc.marks[markIndex];
1917
+ if (!m) return noSuchMark({ names, field, marks: doc.marks, file, verb: "listed" });
1752
1918
  const floors = a.value && typeof a.value === "object" ? a.value : {};
1753
1919
  const minRecords = Number.isFinite(floors.records) ? floors.records : 1;
1754
1920
  const minOffices = Number.isFinite(floors.offices) ? floors.offices : 1;
@@ -1756,8 +1922,15 @@ function evalAssertion(a, runDir) {
1756
1922
  const offices = [...new Set(records.map((r) => String(r?.territory ?? "").trim().toLowerCase()).filter(Boolean))];
1757
1923
  // A term the register refused is REDUCED COVERAGE, and it is named whichever way the floor goes: met,
1758
1924
  // it qualifies what was proved; missed, it says the shortfall may be the fetch rather than the register.
1759
- const failed = (m.terms ?? []).filter((t) => t?.ok !== true)
1760
- .map((t) => `${t?.term ?? "?"}: ${String(t?.reason ?? "no reason recorded").slice(0, 120)}`);
1925
+ // A TERM IS A SPELLING OF A MARK and the reason is the engine's own prose, so both are withheld by
1926
+ // default; the INDEX still says which of the run's terms to go and read.
1927
+ const failedRows = (m.terms ?? []).map((t, i) => ({ t, i })).filter(({ t }) => t?.ok !== true);
1928
+ const failed = names
1929
+ ? failedRows.map(({ t }) => `${t?.term ?? "?"}: ${String(t?.reason ?? "no reason recorded").slice(0, 120)}`)
1930
+ // NAMED BY POSITION IN THE FILE, both coordinates. `term 2 of knockout-filings.json` is ambiguous
1931
+ // the moment the listing holds more than one mark, and it always does — so the row would send a
1932
+ // reader to a term belonging to a different mark and read just as confidently.
1933
+ : failedRows.map(({ i }) => `term ${i + 1} of mark ${markIndex + 1} of ${file}`);
1761
1934
  const met = records.length >= minRecords && offices.length >= minOffices;
1762
1935
  const body = `${records.length} record(s) (floor ${minRecords}) across ${offices.length} office(s) [${offices.join(", ") || "none"}] (floor ${minOffices})`;
1763
1936
  if (met) return { ok: true, saw: failed.length ? `${body} — floor met, but ${failed.length} term(s) were refused and this run covered less than it asked for: ${failed.join(" · ")}` : body };
@@ -2095,7 +2268,11 @@ export function provenanceLines(cost, today = new Date()) {
2095
2268
 
2096
2269
  function cmdList() {
2097
2270
  console.log("\nE2E scenarios — each is one complete clearance unless marked $0");
2098
- console.log(`store: ${storeLine(STORE)}\n`);
2271
+ console.log(`store: ${storeLine(STORE)}`);
2272
+ // WHAT THIS INSTANCE IS CONFIGURED FOR, once at the top, because it decides which of the scenarios
2273
+ // below `run` will start. Printed even when nothing declares a register: a reader choosing a scenario
2274
+ // needs to know the variable names none BEFORE the refusal tells them.
2275
+ console.log(`register: ${configuredRegister() ?? `${REGISTER_ENV} names none`}\n`);
2099
2276
  sweepStoreOrDie();
2100
2277
  for (const s of allScenarios()) {
2101
2278
  const cost = s.cost?.measured ? `~${s.cost.wallMinutes} min` : "UNMEASURED";
@@ -2110,6 +2287,16 @@ function cmdList() {
2110
2287
  // all, and an absent benchmark is not a met one.
2111
2288
  for (const l of turnaroundVerdict(bandForScenario(s)).lines) console.log(` ${l}`);
2112
2289
  for (const l of provenanceLines(s.cost)) console.log(` ${l}`);
2290
+ // WHETHER `run` WOULD START THIS ONE HERE, at a glance, so the choice is made in the list rather
2291
+ // than discovered one scenario at a time. The refusal's own words, not a second wording of them.
2292
+ {
2293
+ const declared = registersDeclaredBy(s);
2294
+ if (declared.length) {
2295
+ const no = registerRefusal(s);
2296
+ console.log(` register: measured against ${declared.join(", ")} — ${no ? "WOULD REFUSE HERE" : "runnable here"}`);
2297
+ if (no) console.log(` ${no}`);
2298
+ }
2299
+ }
2113
2300
  // — which scenarios prove the register HIT path, at a glance. Unconditional, and an
2114
2301
  // unstated label prints as loudly as a stated one.
2115
2302
  {
@@ -2173,6 +2360,13 @@ async function cmdRun(id) {
2173
2360
  // Which store this came from, on the record before the run spends. The synthetic and the real scenario
2174
2361
  // share an ID, so the ledger afterwards cannot tell you which one ran unless the run says so now.
2175
2362
  console.log(`store: ${storeLine(STORE)}`);
2363
+ // BEFORE THE SPEND, because that is the only place this check is worth anything: a scenario whose
2364
+ // floors were measured against another register produces a red that reads as an engine regression,
2365
+ // and by then the run is paid for. `die` rather than a warning — a warning printed above a three-hour
2366
+ // run is a warning nobody reads until the verdict.
2367
+ { const no = registerRefusal(s); if (no) die(`REFUSING: ${no}`); }
2368
+ console.log(`register: ${configuredRegister() ?? `${REGISTER_ENV} names none`}${
2369
+ registersDeclaredBy(s).length ? ` — the scenario states its numbers were measured against ${registersDeclaredBy(s).join(", ")}` : ""}`);
2176
2370
  // Every scenario spends, R0 included — so every scenario refuses on stale code. R0 was exempted here
2177
2371
  // on the belief that it is refused at the door before any model call, which its own `why` also claimed.
2178
2372
  // It is not: R0d's FIRST submission is expected to admit (that is how it produces a duplicate to
@@ -3091,6 +3285,10 @@ async function cmdReport(id, { round: requestedToken = null } = {}) {
3091
3285
  // — the handover's "commit SHA on test" field, READ rather than reconstructed. An absence
3092
3286
  // is printed as itself: a run that predates the stamp has none, and inventing one from a reflog
3093
3287
  // is the reconstruction this replaces.
3288
+ // — WHICH REGISTER SERVED IT, beside the engine build because it is the same class of fact: what
3289
+ // this run actually ran against, read as a field rather than searched for as a substring. An absence
3290
+ // prints as itself, because "not recorded" is not "none served" and a reader must not have to guess.
3291
+ console.log(` register: ${registerServedLine(readJson(join(run.runDir, "status.json")))}`);
3094
3292
  const eb = engineBuildOf(run.runDir);
3095
3293
  if (!eb) console.log(` engine build: NOT RECORDED — this run predates the run-start stamp (#1423); any attribution for it is a reflog reconstruction`);
3096
3294
  else {
@@ -3177,7 +3375,7 @@ async function cmdReport(id, { round: requestedToken = null } = {}) {
3177
3375
  notProbed.push(`${ref}: the doors refused, but this receipt records no REASON — re-run with the current e2e.mjs to check expect.reasonMatches`);
3178
3376
  console.log(` reason: NOT RECORDED by the round that wrote this receipt — not a pass`);
3179
3377
  } else {
3180
- for (const r of refusal.reasons) console.log(` [${r.door}] ${String(r.reason ?? "(no reason recorded)").slice(0, 160)}`);
3378
+ for (const r of refusal.reasons) console.log(` [${r.door}] ${doorReasonLine(r)}`);
3181
3379
  if (refusal.wantReason && refusal.missed.length)
3182
3380
  toInvestigate.push(`${ref}: refused, but ${refusal.missed.join(", ")} did not say "${refusal.wantReason}" — a refusal for the wrong reason reads exactly like the right one`);
3183
3381
  else if (refusal.wantReason) console.log(` [ ok ] every door's reason carries "${refusal.wantReason}"`);
@@ -3392,8 +3590,27 @@ async function cmdReport(id, { round: requestedToken = null } = {}) {
3392
3590
  // A multi-name knockout stamps NO single url BY DESIGN: one address would be the first
3393
3591
  // name standing for the batch, so the packet carries `reports[{mark, url}]` instead. Read it —
3394
3592
  // from the run dir, or from the pool meta once the run dir is archived — and probe every per-mark
3395
- // address exactly as the single-url arm does. Only a delivered run with neither a url nor a
3396
- // reports[] list is the CLEAROTRON_REPORTS_URL absence this arm was written for.
3593
+ // address exactly as the single-url arm does.
3594
+ //
3595
+ // ── WHAT THE TWO ABSENCE ARMS BELOW MEASURE, AND WHAT THEY CANNOT SEE ───────────────────────────
3596
+ //
3597
+ // They measure one thing: no entry carries a url. They used to REPORT a second thing they never
3598
+ // looked at — that CLEAROTRON_REPORTS_URL is unset — and the row said it flatly, as a fact about
3599
+ // the instance.
3600
+ //
3601
+ // IT IS NOT ALWAYS TRUE, measured on a test instance 2026-09-30: the variable was present and
3602
+ // non-empty both in the instance's env file and in the worker process's own environment, and the
3603
+ // run's eight report entries each carried a `url` KEY WITH AN EMPTY VALUE. The stamp had run and
3604
+ // produced nothing. On that run the stated cause was simply wrong, and the cause of the empty
3605
+ // stamp is still unknown.
3606
+ //
3607
+ // WHAT IT COSTS TO STATE A CAUSE YOU DID NOT MEASURE: acting on the row, the obvious repair is to
3608
+ // restore the variable from a backup — overwriting a working value with an older one and calling
3609
+ // it a fix. That was nearly done here.
3610
+ //
3611
+ // So the row states the measurement and the investigate line keeps the hypothesis, marked as one.
3612
+ // A reader who wants the cause reads the variable on the instance and the stamp's own output; this
3613
+ // check is not in a position to tell them.
3397
3614
  const batchReports = (() => {
3398
3615
  try { return JSON.parse(readFileSync(driverDir(runDir, "delivery.json"), "utf8")).reports ?? []; } catch { /* archived or pre-batch */ }
3399
3616
  try { return JSON.parse(readFileSync(join(poolDir, "meta.json"), "utf8")).reports ?? []; } catch { /* no pool entry */ }
@@ -3411,13 +3628,14 @@ async function cmdReport(id, { round: requestedToken = null } = {}) {
3411
3628
  else if (error) { failures++; toInvestigate.push(`${ref}: could not probe ${r.mark}'s report URL (${error}) — ${r.url}`); }
3412
3629
  }
3413
3630
  } else if (batchReports.length) {
3414
- console.log(` [FAIL] the run's stamped URL resolves\n delivered as a batch of ${batchReports.length}, but no report entry carries a url — CLEAROTRON_REPORTS_URL is unset on this instance`);
3415
- failures++; toInvestigate.push(`${ref}: batch delivered with no per-report URL stamped (CLEAROTRON_REPORTS_URL unset?)`);
3631
+ console.log(` [FAIL] the run's stamped URL resolves\n delivered as a batch of ${batchReports.length}, and not one of those entries carries a url`);
3632
+ failures++; toInvestigate.push(`${ref}: batch delivered with no per-report URL stamped — cause NOT measured here; read CLEAROTRON_REPORTS_URL on the instance AND what the stamp wrote, because a set variable can still stamp empty`);
3416
3633
  } else {
3417
- // A delivered run with no URL at all is the same absence one step earlier: CLEAROTRON_REPORTS_URL unset
3418
- // makes publishReport stamp null, and the handoff packet then carries no address for anyone to open.
3419
- console.log(` [FAIL] the run's stamped URL resolves\n delivered, but status.json carries no url — CLEAROTRON_REPORTS_URL is unset on this instance`);
3420
- failures++; toInvestigate.push(`${ref}: delivered with no report URL stamped (CLEAROTRON_REPORTS_URL unset?)`);
3634
+ // A delivered run with no URL at all is the same absence one step earlier, and the same limit
3635
+ // applies: this arm sees that the record carries no address, never why. The handoff packet then
3636
+ // carries no address for anyone to open, whatever produced that.
3637
+ console.log(` [FAIL] the run's stamped URL resolves\n delivered, and status.json carries no url`);
3638
+ failures++; toInvestigate.push(`${ref}: delivered with no report URL stamped — cause NOT measured here; read CLEAROTRON_REPORTS_URL on the instance AND what the stamp wrote, because a set variable can still stamp empty`);
3421
3639
  }
3422
3640
  }
3423
3641
 
@@ -4040,7 +4258,7 @@ function cmdTeardown(id) {
4040
4258
  // stamp can never disagree about whether a round is done.
4041
4259
  // adds `SCENARIO_FILE` — the ONE filename pattern, exported so the test that pins the widening
4042
4260
  // reads the same regex `list` and the sweep read, rather than a copy that agrees today.
4043
- export { evalAssertion, OPS, queueDrainState, DOORS, runLedger, investigate, brief, secs, queueOutcomes,
4261
+ export { evalAssertion, fromTheRun, OPS, queueDrainState, DOORS, runLedger, investigate, brief, secs, queueOutcomes,
4044
4262
  TERMINAL_BY_SUFFIX, TERMINAL_BY_SUFFIX_RAN, SCENARIO_FILE };
4045
4263
 
4046
4264
  // ── main ─────────────────────────────────────────────────────────────────────────────────────────────
@@ -4064,6 +4282,10 @@ if (invokedDirectly) {
4064
4282
  if (a === "--round") {
4065
4283
  requestedRound = raw[++i] ?? null;
4066
4284
  if (!requestedRound || requestedRound.startsWith("--")) die("--round needs a round TOKEN: e2e.mjs report <ID> --round <token>\n The token is on `run`'s output and in the ROUNDS block of any report.");
4285
+ } else if (a === "--names") {
4286
+ // The words the run wrote, on the page. Off by default because `report` is the cheap instrument and
4287
+ // is pointed at real matters in sessions that record what they print.
4288
+ setReportNames(true);
4067
4289
  } else if (a.startsWith("--round=")) {
4068
4290
  requestedRound = a.slice("--round=".length);
4069
4291
  if (!requestedRound) die("--round= needs a round TOKEN: e2e.mjs report <ID> --round=<token>");
@@ -4071,13 +4293,13 @@ if (invokedDirectly) {
4071
4293
  }
4072
4294
  const [cmd, arg, ...extra] = positional;
4073
4295
  if (extra.length) die(`unexpected argument "${extra[0]}"\n`
4074
- + "usage: e2e.mjs list | run <ID> [--stale] | status | report <ID> [--round <token>] | teardown <ID> [--waive-unread]");
4296
+ + "usage: e2e.mjs list | run <ID> [--stale] | status | report <ID> [--round <token>] [--names] | teardown <ID> [--waive-unread]");
4075
4297
  switch (cmd) {
4076
4298
  case "list": cmdList(); break;
4077
4299
  case "run": if (!arg) die("usage: e2e.mjs run <ID> [--stale]"); await cmdRun(arg); break;
4078
4300
  case "status": cmdStatus(); break;
4079
- case "report": if (!arg) die("usage: e2e.mjs report <ID> [--round <token>]"); await cmdReport(arg, { round: requestedRound }); break;
4301
+ case "report": if (!arg) die("usage: e2e.mjs report <ID> [--round <token>] [--names]"); await cmdReport(arg, { round: requestedRound }); break;
4080
4302
  case "teardown": if (!arg) die("usage: e2e.mjs teardown <ID> [--waive-unread]"); cmdTeardown(arg); break;
4081
- default: die("usage: e2e.mjs list | run <ID> [--stale] | status | report <ID> [--round <token>] | teardown <ID> [--waive-unread]");
4303
+ default: die("usage: e2e.mjs list | run <ID> [--stale] | status | report <ID> [--round <token>] [--names] | teardown <ID> [--waive-unread]");
4082
4304
  }
4083
4305
  }
@@ -36,17 +36,51 @@ if (p.laid) console.log(`mint-reference-strip-backlog: ${p.laid} tracked path(s)
36
36
  const tracked = p.files;
37
37
  const minted = censusOf(ROOT, tracked, publishedReader(ROOT, (f) => readFileSync(join(ROOT, f), "utf8")));
38
38
 
39
+ // ── EVERY ROW IS EXPLAINED, OR THE TABLE IS A NUMBER NOBODY CAN ACT ON ────────────────────────────
40
+ //
41
+ // A count on its own tells a reader a file has residue in it. It does not tell them whether the line is
42
+ // residue at all — and once the table reached zero, the next row to appear is either a regression to
43
+ // repair or a known-good the scanner cannot tell from one. The first `.js` row is the second kind: an
44
+ // adapter header names a list separator as `(, ; / etc)`, which matches the third signature and is also
45
+ // correct English about punctuation. Without a sentence beside it that row is a number that will never
46
+ // fall, and a floor with one of those in it stops being read.
47
+ //
48
+ // So notes are carried by the minter rather than typed into the artifact: a re-mint keeps the note for a
49
+ // file that still has rows, drops it for a file that no longer does (a repair takes its explanation with
50
+ // it), and says which rows are still unexplained. It never invents one — an unexplained row is reported,
51
+ // and the arm that reads this table refuses it.
52
+ const NOTES = {
53
+ "providers/clarivate/src/core.js":
54
+ "NOT residue — the header names a list separator as `(, ; / etc)`. It matches the third signature "
55
+ + "and is correct English about punctuation, so it is recorded rather than repaired.",
56
+ };
57
+ const notesFor = (files) => Object.fromEntries(
58
+ Object.keys(files).filter((f) => NOTES[f]).map((f) => [f, NOTES[f]]).sort(([a], [b]) => a < b ? -1 : 1));
59
+ const unexplained = (files) => Object.keys(files).filter((f) => !NOTES[f]).sort();
60
+
39
61
  if (process.argv.includes("--check")) {
40
62
  const have = JSON.parse(readFileSync(TABLE, "utf8"));
41
63
  const a = JSON.stringify(have.files), b = JSON.stringify(minted.files);
64
+ if (JSON.stringify(have.notes ?? {}) !== JSON.stringify(notesFor(minted.files))) {
65
+ console.error("reference-strip backlog: the committed notes do not match this tree's rows.");
66
+ console.error(" re-mint with: node scripts/mint-reference-strip-backlog.mjs");
67
+ process.exit(1);
68
+ }
42
69
  if (a !== b || have.total !== minted.total) {
43
70
  console.error("reference-strip backlog is STALE against the tree.");
44
71
  console.error(` committed total ${have.total}, tree has ${minted.total}`);
45
72
  console.error(" re-mint with: node scripts/mint-reference-strip-backlog.mjs");
46
73
  process.exit(1);
47
74
  }
48
- console.log(`reference-strip backlog: current — ${minted.total} line(s) still to repair`);
75
+ const open = unexplained(minted.files);
76
+ const held = Object.keys(minted.files).length - open.length;
77
+ console.log(`reference-strip backlog: current — ${minted.total} matching line(s); `
78
+ + `${open.length} file(s) to repair, ${held} recorded as not residue`);
49
79
  } else {
50
- writeFileSync(TABLE, JSON.stringify({ signatures: SIGNATURES.map((s) => s.name), ...minted }, null, 2) + "\n");
80
+ writeFileSync(TABLE, JSON.stringify({ signatures: SIGNATURES.map((s) => s.name), ...minted,
81
+ notes: notesFor(minted.files) }, null, 2) + "\n");
51
82
  console.log(`minted ${TABLE}: ${minted.total} line(s) across ${Object.keys(minted.files).length} file(s)`);
83
+ const open = unexplained(minted.files);
84
+ if (open.length) console.log(`mint-reference-strip-backlog: ${open.length} file(s) carry rows with no note — `
85
+ + `each is a line to repair, or a note to add here if it is not residue:\n ${open.join("\n ")}`);
52
86
  }