clearotron 0.4.1 → 0.5.0-beta.1

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 (104) hide show
  1. package/README.md +1 -0
  2. package/THIRD-PARTY-NOTICES.md +1 -1
  3. package/bin/start.mjs +33 -8
  4. package/build-info.json +2 -2
  5. package/driver/CHANGELOG.md +32 -0
  6. package/driver/ask-ledger.mjs +90 -16
  7. package/driver/band-shape.mjs +65 -11
  8. package/driver/band-size.mjs +10 -0
  9. package/driver/close-verify.mjs +29 -2
  10. package/driver/common-law-coverage-status.mjs +1 -1
  11. package/driver/connotation-search.mjs +7 -9
  12. package/driver/contract-e3-backlog.mjs +34 -34
  13. package/driver/contract-e3-baseline.json +5 -5
  14. package/driver/contract-vocabulary.mjs +12 -12
  15. package/driver/corrections-delivered.mjs +113 -0
  16. package/driver/corrections-feedforward.mjs +16 -6
  17. package/driver/corrective-arm.mjs +11 -8
  18. package/driver/coverage-form-io.mjs +2 -1
  19. package/driver/coverage-form.mjs +17 -2
  20. package/driver/coverage-union.mjs +11 -2
  21. package/driver/digest-queue.mjs +1 -1
  22. package/driver/disposition-call.mjs +1 -1
  23. package/driver/document-coverage.mjs +3 -2
  24. package/driver/effective-scope.mjs +12 -7
  25. package/driver/engine/mcp/band-server.mjs +77 -13
  26. package/driver/engine/mcp/dispositions-server.mjs +2 -2
  27. package/driver/engine/mcp/held-answers.mjs +128 -0
  28. package/driver/engine/mcp/recording-server.mjs +10 -6
  29. package/driver/engine/mcp/supplemental.mjs +74 -12
  30. package/driver/findings-model.mjs +16 -4
  31. package/driver/floor-duty.mjs +9 -0
  32. package/driver/gate-metrics.mjs +7 -1
  33. package/driver/gateway.mjs +28 -14
  34. package/driver/goods-evidence.mjs +295 -0
  35. package/driver/knockout-assess-record.mjs +1 -1
  36. package/driver/narrative-refutation-record.mjs +40 -2
  37. package/driver/package.json +1 -1
  38. package/driver/pipeline.mjs +1994 -323
  39. package/driver/placement-decisions.mjs +243 -0
  40. package/driver/placement-form.mjs +42 -8
  41. package/driver/placement-union.mjs +14 -6
  42. package/driver/provider-usage.mjs +10 -7
  43. package/driver/publish/audit-from-spine.mjs +5 -2
  44. package/driver/publish/index.mjs +19 -4
  45. package/driver/publish/xlsx.mjs +6 -6
  46. package/driver/recall-reconciliation.mjs +15 -5
  47. package/driver/record-carry.mjs +70 -6
  48. package/driver/record-discard.mjs +7 -1
  49. package/driver/record-fold.mjs +14 -0
  50. package/driver/record-goods.mjs +101 -5
  51. package/driver/register-plan.mjs +221 -6
  52. package/driver/registry-fidelity.mjs +34 -3
  53. package/driver/remedy-accounting.mjs +52 -14
  54. package/driver/repair-composers.mjs +265 -105
  55. package/driver/repair-scope.mjs +327 -0
  56. package/driver/repair-targets.mjs +92 -0
  57. package/driver/result-noun-fields.mjs +3 -3
  58. package/driver/scope-facts.mjs +6 -2
  59. package/driver/screen-gate.mjs +96 -0
  60. package/driver/skills/clearance-common-law/SKILL.md +8 -0
  61. package/driver/skills/clearance-register/SKILL.md +29 -21
  62. package/driver/skills/clearance-register/digest.md +22 -17
  63. package/driver/skills/clearance-register/providers/clarivate.md +4 -4
  64. package/driver/skills/clearance-register/providers/corsearch.md +1 -1
  65. package/driver/skills/clearance-register/providers/euipo.md +4 -4
  66. package/driver/skills/clearance-register/providers/free-tier.md +4 -4
  67. package/driver/skills/clearance-register/providers/uspto-local.md +3 -3
  68. package/driver/skills/clearance-register/register-recipes.md +6 -6
  69. package/driver/skills/clearance-register/unit.md +29 -22
  70. package/driver/skills/clearance-search/delivery-contract.md +2 -58
  71. package/driver/skills/clearance-search/report-prose.md +12 -3
  72. package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
  73. package/driver/skills/clearance-search/synthesis-rules.md +10 -13
  74. package/driver/skills/clearance-variants/SKILL.md +4 -4
  75. package/driver/skills/knockout-assess/SKILL.md +9 -14
  76. package/driver/skills/knockout-frame/SKILL.md +5 -5
  77. package/driver/skills/narrative-refutation/SKILL.md +1 -1
  78. package/driver/skills/placement-inquiry/SKILL.md +1 -1
  79. package/driver/skills/whatif-memo/SKILL.md +1 -1
  80. package/driver/stage-context.mjs +2 -1
  81. package/driver/stages-knockout.mjs +1 -1
  82. package/driver/stages.mjs +96 -61
  83. package/driver/suite-census.json +415 -25
  84. package/driver/synthesis-record.mjs +310 -8
  85. package/driver/terminal-clamp.mjs +53 -2
  86. package/driver/verify-knockout.mjs +22 -4
  87. package/driver/verify.mjs +23 -3
  88. package/mcp-server/CHANGELOG.md +8 -0
  89. package/mcp-server/lib/driver.mjs +5 -0
  90. package/mcp-server/lib/events.mjs +25 -4
  91. package/mcp-server/lib/findings.mjs +4 -2
  92. package/mcp-server/package.json +1 -1
  93. package/package.json +1 -1
  94. package/portal-ui/package.json +1 -1
  95. package/providers/_shared/enumerate.mjs +12 -6
  96. package/providers/_shared/execute-plan.mjs +25 -1
  97. package/providers/_shared/follow-up-searches.mjs +139 -0
  98. package/providers/jx/src/core.js +3 -4
  99. package/providers/jx/src/judge.js +2 -2
  100. package/providers/jx/src/nativeread.js +1 -2
  101. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  102. package/providers/oauth-mcp-bridge/package.json +1 -1
  103. package/scripts/e2e-first-time.mjs +30 -13
  104. package/scripts/release-await-cut.mjs +4 -2
package/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/licence-AGPL--3.0--only-860F09?style=flat-square" alt="Licence: AGPL-3.0-only"></a>
10
10
  <a href=".nvmrc"><img src="https://img.shields.io/badge/node-%E2%89%A5%2022.13-250902?style=flat-square" alt="Node 22.13+"></a>
11
11
  <a href="https://scorecard.dev/viewer/?uri=github.com/CordilleraSarl/clearotron"><img src="https://img.shields.io/ossf-scorecard/github.com/CordilleraSarl/clearotron?label=openssf%20scorecard&style=flat-square" alt="OpenSSF Scorecard"></a>
12
+ <a href="https://www.bestpractices.dev/projects/15666"><img src="https://img.shields.io/cii/level/15666?label=openssf%20best%20practices&style=flat-square" alt="OpenSSF Best Practices"></a>
12
13
  </p>
13
14
 
14
15
  Before your company commits to a name, find out what stands in its way. Give Clearotron the name, the
@@ -2439,7 +2439,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2439
2439
  SOFTWARE.
2440
2440
  ```
2441
2441
 
2442
- ## hono@4.13.8
2442
+ ## hono@4.13.13
2443
2443
 
2444
2444
  - **Licence declared:** `MIT`
2445
2445
  - **Repository:** https://github.com/honojs/hono
package/bin/start.mjs CHANGED
@@ -379,6 +379,32 @@ function liveClientDoor(unitEnvFile = unitEnvPath(), run = execFileSync) {
379
379
  }
380
380
 
381
381
  /** The two ports, from the environment or the shipped defaults. Throws on a value that is not a port. */
382
+ // ── WHAT `start` SAYS ABOUT A CLIENT DOOR THAT IS NOT UP (the owner's words, 2026-10-18) ──────────────
383
+ //
384
+ // The client door is non-fatal: the portal and the engine door serve without it. It drains no queue, so its
385
+ // exit is not the queue worker's, whose line tells the reader to start one with `--watch`. And a door still
386
+ // up when the 30-second wait ran out has printed nothing, so it does not send the reader to look at its
387
+ // output for a reason. Each line says what is true of the client door.
388
+
389
+ /** The line printed when a non-fatal child exits: the client door's own, else the queue worker's. PURE. */
390
+ export function nonFatalExitLine({ name, script, code, signal, clientDoor = false }) {
391
+ const how = signal ? `on ${signal}` : `with code ${code}`;
392
+ return clientDoor
393
+ ? `\nstart: ${name} (${script}) exited ${how} — the portal and the engine door are unaffected; a client assistant cannot connect until it is up.\n`
394
+ : `\nstart: ${name} (${script}) exited ${how} — the portal is still up, but nothing is draining the queue now. Start one with:\n node ${script} --watch\n`;
395
+ }
396
+
397
+ /**
398
+ * The summary's two lines for a client door that is not running. A door that exited printed why before it
399
+ * went; a door still up when the wait ran out did not answer. PURE.
400
+ */
401
+ export function clientDoorDownLines({ host, port, exited }) {
402
+ return [
403
+ ` Client door NOT RUNNING on ${host}:${port} — ${exited ? "its output above says why" : "it did not answer within 30s"}. The portal and`,
404
+ " the engine door are unaffected; a client assistant cannot connect until it is up.",
405
+ ];
406
+ }
407
+
382
408
  export function resolvePorts(env = {}) {
383
409
  const one = (name, dflt) => {
384
410
  const raw = String(env[name] ?? "").trim();
@@ -2208,7 +2234,7 @@ if (isMain) {
2208
2234
  const children = [];
2209
2235
  let stopping = false;
2210
2236
 
2211
- const start = (name, script, env, { args = [], fatal = true } = {}) => {
2237
+ const start = (name, script, env, { args = [], fatal = true, clientDoor = false } = {}) => {
2212
2238
  const child = spawn(process.execPath, [join(REPO, script), ...args], {
2213
2239
  cwd: REPO,
2214
2240
  // Its OWN process group, so Ctrl-C reaches this supervisor alone and teardown is one ordered
@@ -2256,10 +2282,9 @@ if (isMain) {
2256
2282
  if (stopping) return;
2257
2283
  // REPORTED. Not absorbed into a panel that says a service is unreachable.
2258
2284
  if (!fatal) {
2259
- // Loudly, because the queue stops draining the instant this goes — but the portal keeps serving,
2260
- // which is the whole point of a child this install is supported without.
2261
- err(`\nstart: ${name} (${script}) exited ${signal ? `on ${signal}` : `with code ${code}`} — the portal is still up,`
2262
- + ` but nothing is draining the queue now. Start one with:\n node ${script} --watch\n`);
2285
+ // Loudly, because what this child did stops the instant it goes — but the portal keeps serving, which
2286
+ // is the whole point of a child this install is supported without. The client door says its own line.
2287
+ err(nonFatalExitLine({ name, script, code, signal, clientDoor }));
2263
2288
  return;
2264
2289
  }
2265
2290
  err(childExitReport({ name, script, code, signal, tail }));
@@ -2444,7 +2469,7 @@ if (isMain) {
2444
2469
  // assistant is correctly connected to. The unit IS the door; this path just does not add another.
2445
2470
  const clientDoor = adoptedClientDoor
2446
2471
  ? null
2447
- : start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false });
2472
+ : start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false, clientDoor: true });
2448
2473
  // ── THE BANNER NAMES THIS DOOR ONLY ONCE IT ANSWERS ─────────────────────────────────────────────────
2449
2474
  //
2450
2475
  // The engine door and the portal are each awaited above before anything is printed. This door was
@@ -2492,8 +2517,8 @@ if (isMain) {
2492
2517
  else
2493
2518
  say(` It refuses every caller until a key is issued: ${DEMO ? demoConnectCommand({ prefix: invocationPrefix(), base: paths.base }) : keyIssueCommand({ prefix: invocationPrefix(), demo: DEMO, user, base: paths.base, defaultBase: join(homedir(), "trademark") })}`);
2494
2519
  } else {
2495
- say(` Client door NOT RUNNING on ${HOST}:${ports.client} — its output above says why. The portal and`);
2496
- say(" the engine door are unaffected; a client assistant cannot connect until it is up.");
2520
+ // A door that exited printed why; one still up when the wait ran out printed nothing, and is not sent there.
2521
+ for (const line of clientDoorDownLines({ host: HOST, port: ports.client, exited: clientDoor?.child?.exitCode !== null })) say(line);
2497
2522
  }
2498
2523
  say(` Engine door http://${HOST}:${ports.mcp}/mcp — the portal's Start button calls this. Staff.`);
2499
2524
  say("");
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "e482d42d8937293290be77fc35633fd956d4bf10",
3
- "version": "0.4.1"
2
+ "commit": "39f3bdc47543630a4f206ac667aa0fd923a2b32b",
3
+ "version": "0.5.0-beta.1"
4
4
  }
@@ -1,5 +1,37 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.5.0-beta.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed: A search with more similar marks than can be read now narrows step by step, and stops once the lists it has read answer it. Before, it could keep searching far past what the advice needed.
8
+ - Fixed: A crowded search that the lists already read have answered no longer appears among the report's coverage limits. Its count is still recorded.
9
+ - For operators: A clearance now stops asking further follow-up searches after 120, and delivers its report with what it has read.
10
+ - For operators: On the Signa register, follow-up searches now send spellings as lists, so a clearance makes fewer requests.
11
+
12
+ ## 0.5.0-beta.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Fixed: Faults the final checks find in a finding's own summary sentence, a short entry or a grouped line are now corrected before delivery. Before, they shipped as found.
17
+
18
+ ### Patch Changes
19
+
20
+ - Fixed: A record whose register lookup failed, or came back empty, no longer counts as retrieved. The clearance now fetches it again, or says it could not.
21
+ - Fixed: A finding added by a late correction now appears with its own card in the delivered report.
22
+ - Fixed: A repeated search can change the ranking after the report was written, too late to rewrite the report. The report now says how many register records were re-ranked, and that the audit trail gives their final ranking. A clear result becomes conditional.
23
+ - Fixed: The report and the audit workbook now say a record was "retrieved from the register" rather than "read". The workbook's summary explains what that means.
24
+ - Fixed: A clearance that resumes after a pause is now held to the same standard as one that never paused. The conditions on its result are those that apply to the report it delivers. A live registration identical or near-identical to the mark that the report leaves unaddressed makes the result conditional, and the report says so. One the clearance's own final pass answers no longer does.
25
+ - Fixed: A result is no longer held at conditional just because a wider search that was rightly withheld was asked for again. Such a result can now read clear, and its goods category reads as searched.
26
+ - Fixed: Knockout ratings now weigh the earlier filings found for the mark itself. The register review follows the same ranking of results as the step before it, so some ratings change.
27
+ - Fixed: Knockout reports no longer carry two standing caveats. The caveat on a conditional result stays.
28
+ - Fixed: A resumed clearance no longer shows the false line "A machine check on the report did not pass".
29
+ - Fixed: A register record that a repeated search finds after the results were ranked is now ranked and reviewed before the report is written. If that cannot be done, the report is still delivered, and it counts the record among those left open. Before, such a record could be left out of the ranking, the review and the report.
30
+ - Fixed: A clearance no longer stops without a report when register records it retrieved are still unaddressed at the end. The report is delivered, a clear result becomes conditional, and the report states once how many records remain open.
31
+ - For operators: When a client door is not up, `clearotron start` now says what is true of it. Before, it gave the queue worker's advice, or pointed to output it never printed.
32
+ - Fixed: After a late correction, the Audit Trail delivered with the report describes the corrected result, and its open questions agree with it. If it cannot be rebuilt, the earlier Audit Trail is withheld rather than shown as current. The audit workbook is still built from the current findings.
33
+ - Fixed: The Audit Trail now lists each register record set aside while the results were ranked, with the reason given.
34
+
3
35
  ## 0.4.1
4
36
 
5
37
  ### Patch Changes
@@ -54,6 +54,7 @@
54
54
  // DRIVER's instructed values appear in the model's frame, where this checks the MODEL's quote
55
55
  // appears in a driver-held file. Same expression, different question; it stays separate.
56
56
  import { squash } from "./doubt-ledger.mjs";
57
+ import { OUTSTANDING_REASON } from "../providers/_shared/follow-up-searches.mjs";
57
58
  //
58
59
  // Ask record shape (frozen — _driver/asks.json and the audit's Ask Ledger both render it):
59
60
  // {
@@ -63,10 +64,16 @@ import { squash } from "./doubt-ledger.mjs";
63
64
  // qids: [ ... ], // the plan qids that would answer it (may be [])
64
65
  // ending: { kind, by, evidence, reasons, ts } | null,
65
66
  // handoff: "<the surface a human reads an un-ended/handed-over ask on>" | null,
67
+ // part, family, // only on a request kept as parts: "ran" or "family", and that family's qid
68
+ // short_id, // every part of a frame request: the request's short id from before its hash
69
+ // legacy_id, // the one part an old closure of that short id may settle: the ran part
66
70
  // }
67
71
 
68
72
  const clip = (s, n = 200) => { const t = String(s ?? "").replace(/\s+/g, " ").trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
69
73
  const slug = (s) => String(s ?? "").toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "x";
74
+ // A short, stable hash of an exact string (FNV-1a, 32 bits), beside a slug, so two qids the slug folds or cuts to
75
+ // one spelling still give two ids. PURE: no import, no clock.
76
+ const fnv32 = (s) => { let h = 0x811c9dc5; for (const c of String(s ?? "")) { h ^= c.codePointAt(0); h = Math.imul(h, 0x01000193) >>> 0; } return h.toString(16).padStart(8, "0"); };
70
77
 
71
78
  export const ASK_ENDINGS = ["executed", "judged-immaterial", "recovery"];
72
79
 
@@ -170,6 +177,21 @@ function endingForQids(qids, join, ts) {
170
177
  return null;
171
178
  }
172
179
 
180
+ /** The ending for a frame directive whose every slice repeated a family the reading turn withheld: that
181
+ * judgment, in the turn's own recorded words, and nothing else — never `executed`, since nothing ran. A
182
+ * family whose record cannot be read leaves the ask OPEN: there is no judgment to show, and an ending
183
+ * without one would be invented. `withheld` is the record as readWithheldFamilies returns it. PURE. */
184
+ function withheldEnding(families, withheld, ts) {
185
+ const reasons = families.map((f) => String(withheld?.[f]?.reason ?? "").trim());
186
+ if (!families.length || reasons.some((r) => !r)) return null;
187
+ const axes = [...new Set(families.map((f) => String(withheld?.[f]?.axis ?? "").trim()).filter(Boolean))];
188
+ return mkEnding("judged-immaterial", "reading-turn", {
189
+ reasons: [...new Set(reasons)],
190
+ evidence: axes.length ? axes.map((a) => `_driver/withheld-families-${a}.json`).join(", ") : "_driver/withheld-families",
191
+ ts,
192
+ });
193
+ }
194
+
173
195
  /** The {ending, handoff} pair for one cross-check directive. Only `recall` is subject to the
174
196
  * discharge rule; every other net keeps the ending it always had. PURE. */
175
197
  function recallEnding(name, qid, join, ts) {
@@ -195,7 +217,8 @@ function recallEnding(name, qid, join, ts) {
195
217
  * 3. escalation-skip run.jsonl `escalation-skipped` events
196
218
  * 4. envelope run.jsonl `envelope-decision` / `envelope-closed` events (+ state.failed)
197
219
  * 5. screen-gate run.jsonl `screen-gate-violation` events + the unresolved sidecar
198
- * 6. frame-diff _driver/frame-reopen.json (requested/swept/deferrals + directive_qids)
220
+ * 6. frame-diff _driver/frame-reopen.json (requested/swept/deferrals + directive_qids), and the
221
+ * reading turn's withheld-family record for a slice its judgment settled
199
222
  * 7. form-neighbourhood same receipt, rows the receipt's `born` map marks form-oracle-injected
200
223
  * 8. supplemental-proposal register-units/<axis>-supplemental-plan.json entries[] + rejected[]
201
224
  * 9. cross-check _driver/register-xcheck.json + _driver/register-recall.json (+ overflow)
@@ -207,7 +230,7 @@ export function deriveAsks({
207
230
  escalationState = null, events = null,
208
231
  frameReopen = null, screenGateUnresolved = null,
209
232
  supplementalPlans = null, xcheck = null, recall = null,
210
- planExecution = null,
233
+ planExecution = null, withheldFamilies = null,
211
234
  } = {}, { ts = null } = {}) {
212
235
  const join = planJoinFrom(planExecution);
213
236
  const evs = Array.isArray(events) ? events.filter((e) => e && typeof e === "object") : [];
@@ -346,25 +369,56 @@ export function deriveAsks({
346
369
  const twinOf = new Map((Array.isArray(fr.answered_elsewhere) ? fr.answered_elsewhere : [])
347
370
  .filter((x) => typeof x?.directive === "string")
348
371
  .map((x) => [x.directive, new Map((Array.isArray(x.slices) ? x.slices : []).filter((t) => t?.qid && t?.twin).map((t) => [t.qid, t.twin]))]));
372
+ // A slice refused as a family the reading turn withheld never ran either, and nothing answered it: the turn
373
+ // decided not to ask that family, and its record keeps why. The receipt names such slices
374
+ // (`withheld_by_judgment`). They are left out of the computed ending, which rests on the slices that ran,
375
+ // and a directive with no other slice ends on the turn's recorded judgment.
376
+ //
377
+ // EACH SLICE ENDS ON WHAT HAPPENED TO IT. A slice that ran ends on the execution record, through its twin
378
+ // where the fold refused it as already asked. A slice whose family the turn still withholds ends on the
379
+ // turn's recorded reason. A slice whose family the turn released after the fold is no longer withheld on
380
+ // record, so it ends on what the execution record says of the family itself: it ran, it was deferred, or it
381
+ // stays open; so does a slice whose record cannot be read, which has no judgment to end on.
382
+ //
383
+ // ONE RECORD PER PART, AND A PART KEEPS ITS ID. The slices that ran are one part, under the directive's own
384
+ // id. Each family the settled slices repeat is a part of its own, under an id made from that family, so its
385
+ // id never moves when the family's disposition changes (withheld, then released and run) or a sibling part
386
+ // disappears. A closure names one part, and settles only that part; an id no part carries settles nothing.
387
+ const withheldOf = new Map((Array.isArray(fr.withheld_by_judgment) ? fr.withheld_by_judgment : [])
388
+ .filter((x) => typeof x?.directive === "string")
389
+ .map((x) => [x.directive, new Map((Array.isArray(x.slices) ? x.slices : []).filter((t) => t?.qid && t?.family).map((t) => [t.qid, t.family]))]));
349
390
  for (const key of requested) {
350
391
  const qids = (Array.isArray(qidsByKey[key]) ? qidsByKey[key] : []).filter(Boolean);
351
392
  const place = bornByKey[key] === "form-neighbourhood" ? "form-neighbourhood" : "frame-diff";
352
- let ending = null, handoff = null;
393
+ const born = { place, artifact: "_driver/frame-reopen.json", ref: key, ts: fr.ts ?? null };
394
+ const ask = { text: `${place === "form-neighbourhood" ? "mechanical form-oracle gap" : "blind frame-diff omission"}: ${key} — sweep it or say why not`, owner: "register", structured: null };
395
+ // THE ID CARRIES A HASH OF THE WHOLE KEY. The slug cuts at 48 characters and folds punctuation, so two
396
+ // requests that differ only after the cut, or only in punctuation, gave one id, and the second was dropped.
397
+ // The short id stays on the ran part as `legacy_id`, for a closure written before the hash (applyAskClosure).
398
+ const id = `ask:frame:${slug(key)}-${fnv32(key)}`;
399
+ const legacy = `ask:frame:${slug(key)}`;
400
+ let parts = [{ id, qids, ending: null, handoff: null, legacy }];
353
401
  const d = deferrals.get(key);
354
402
  if (d) {
355
- ending = mkEnding("recovery", "frame-reopen", { reasons: [String(d.reason ?? "deferred")], evidence: "_driver/frame-reopen.json deferrals", ts });
356
- handoff = "open coverage row (frame-gap) + the CLEAR→CONDITIONAL clamp — the report states the unswept omission";
403
+ parts = [{ id, qids, ending: mkEnding("recovery", "frame-reopen", { reasons: [String(d.reason ?? "deferred")], evidence: "_driver/frame-reopen.json deferrals", ts }),
404
+ handoff: "open coverage row (frame-gap) + the CLEAR→CONDITIONAL clamp — the report states the unswept omission", legacy }];
405
+ } else if (swept.has(key) && !qids.length) {
406
+ parts = [{ id, qids, ending: mkEnding("executed", "frame-reopen-receipt", { evidence: "swept per the frame-reopen receipt (warm-resume arm — axis-level verification)", ts }), legacy }];
357
407
  } else if (swept.has(key)) {
358
- ending = qids.length
359
- ? endingForQids(qids.map((q) => twinOf.get(key)?.get(q) ?? q), join, ts) // computed — null (open) when the execution record disagrees
360
- : mkEnding("executed", "frame-reopen-receipt", { evidence: "swept per the frame-reopen receipt (warm-resume arm — axis-level verification)", ts });
408
+ const settled = withheldOf.get(key) ?? new Map();
409
+ const ran = qids.filter((q) => !settled.has(q));
410
+ const families = [...new Set(qids.filter((q) => settled.has(q)).map((q) => settled.get(q)))];
411
+ parts = [
412
+ ran.length && { id, qids: ran, ending: endingForQids(ran.map((q) => twinOf.get(key)?.get(q) ?? q), join, ts), // null (open) when the execution record disagrees
413
+ ...(families.length ? { part: "ran" } : {}), legacy },
414
+ ...families.map((fam) => ({
415
+ id: `${id}:family:${slug(fam)}-${fnv32(fam)}`, qids: qids.filter((q) => settled.get(q) === fam), part: "family", family: fam,
416
+ ending: String(withheldFamilies?.[fam]?.reason ?? "").trim() ? withheldEnding([fam], withheldFamilies, ts) : endingForQids([fam], join, ts),
417
+ })),
418
+ ].filter(Boolean);
361
419
  }
362
- push({
363
- ask_id: `ask:frame:${slug(key)}`,
364
- born: { place, artifact: "_driver/frame-reopen.json", ref: key, ts: fr.ts ?? null },
365
- ask: { text: `${place === "form-neighbourhood" ? "mechanical form-oracle gap" : "blind frame-diff omission"}: ${key} — sweep it or say why not`, owner: "register", structured: null },
366
- qids, ending, handoff,
367
- });
420
+ for (const p of parts) push({ ask_id: p.id, born, ask, qids: p.qids, ending: p.ending, handoff: p.handoff ?? null,
421
+ ...(p.part ? { part: p.part } : {}), ...(p.family ? { family: p.family } : {}), short_id: legacy, ...(p.legacy ? { legacy_id: p.legacy } : {}) });
368
422
  }
369
423
  }
370
424
 
@@ -416,7 +470,9 @@ export function deriveAsks({
416
470
  // must show it working. So EVERY rejection attempts the join, and one that did not mint later
417
471
  // ends OPEN — a real unanswered question the closure stage settles with a citation or that ships
418
472
  // visibly. "judged-immaterial" is reserved for a materiality judgment somebody actually made.
419
- const supersededBy = mintedByIdentity.get(proposalIdentity(r?.proposal)) ?? null;
473
+ // A follow-up the run's safety net did not send is never superseded: the identity join ignores the regions, so a
474
+ // question minted for one market would otherwise bury the same question refused for another.
475
+ const supersededBy = issue === OUTSTANDING_REASON ? null : (mintedByIdentity.get(proposalIdentity(r?.proposal)) ?? null);
420
476
  push({
421
477
  ask_id: `ask:supplemental-rejected:${slug(axis)}:${i + 1}`,
422
478
  born: { place: "supplemental-proposal", artifact: `register-units/${axis}-supplemental-plan.json`, ref: `rejected[${i}]`, ts: r?.ts ?? null },
@@ -651,12 +707,30 @@ export function applyAskClosure(asks, closureLines, fileTexts = {}, { ts = null
651
707
  const byId = new Map();
652
708
  for (const l of closureLines ?? []) if (l?.id && !byId.has(l.id)) byId.set(l.id, l);
653
709
  const unverified = [];
710
+ // A CLOSURE WRITTEN BEFORE FRAME IDS CARRIED THEIR HASH names a request by its short id. Every request that had that
711
+ // short id could be the one it meant, a request now kept only as family parts included. It settles the ran part of
712
+ // the one request that had it, and nothing where two did: settling either could end an obligation it never judged.
713
+ // An ambiguous citation is reported with the parts it could have meant, while any of them is still open.
714
+ const requestsOf = new Map(), partsOf = new Map();
715
+ for (const a of asks ?? []) {
716
+ const short = a?.short_id ?? a?.legacy_id;
717
+ if (!short || short === a.ask_id) continue;
718
+ requestsOf.set(short, new Set([...(requestsOf.get(short) ?? []), a.born?.ref ?? a.ask_id]));
719
+ partsOf.set(short, [...(partsOf.get(short) ?? []), a]);
720
+ }
721
+ for (const [short, requests] of requestsOf) {
722
+ const l = byId.get(short);
723
+ const parts = partsOf.get(short) ?? [];
724
+ if (l && requests.size > 1 && parts.some((a) => !a.ending)) unverified.push({ ask_id: short, file: l.file, quote: l.quote, ambiguous: parts.map((a) => a.ask_id) });
725
+ }
726
+ const lineFor = (a) => byId.get(a.ask_id)
727
+ ?? (a.legacy_id && requestsOf.get(a.legacy_id)?.size === 1 ? byId.get(a.legacy_id) : undefined);
654
728
  const carryIntoFindings = [];
655
729
  let immaterialByStage = 0;
656
730
  const delivered = deliveredMarks(fileTexts?.["findings.json"]);
657
731
  const out = (asks ?? []).map((a) => {
658
732
  if (a?.ending) return a; // the stage may never touch an ended ask
659
- const l = byId.get(a.ask_id);
733
+ const l = lineFor(a);
660
734
  if (!l) return a;
661
735
  if (l.verdict === "OPEN") return { ...a, handoff: clip(l.reason, 300) || a.handoff };
662
736
  const hay = squash(fileTexts?.[l.file]);
@@ -708,7 +708,7 @@ function bindingOffices(territories, code) {
708
708
  return out;
709
709
  }
710
710
 
711
- export function bandGroups(records, { classify, inScope, goods = null, dominantTerm = "", keysOf = null, markets = [], territories = [] } = {}) {
711
+ export function bandGroups(records, { classify, inScope, goods = null, dominantTerm = "", keysOf = null, markets = [], territories = [], evidenceOf = null } = {}) {
712
712
  const fold = (x) => foldDiacritics(String(x ?? "")).toLowerCase();
713
713
  const dom = fold(dominantTerm).trim();
714
714
  const domRe = dom ? new RegExp(`(^|[^\\p{L}\\p{N}])${dom.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}($|[^\\p{L}\\p{N}])`, "u") : null;
@@ -744,7 +744,9 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
744
744
  // A floor is never counted in the rest (ruled 2026-10-12): held only outside the order's territories, it
745
745
  // is still listed, in (a) or (b), where the step reads it and may set it aside by name on its territory.
746
746
  const group = outside && !floor ? "rest" : recordGroup({ tier, live, inScope: s, hits, dominant });
747
- const rec = { r, tier, hits, words, live, inScope: s, group, outside, floor, onFile: Boolean(g), territories: ts, inMarket: ts.some((t) => market.has(t)) };
747
+ // "On file" is a body `band_record` can open: goods read off the band row decide the group, never that.
748
+ const onFile = goods?.held ? goods.held(r) : Boolean(g);
749
+ const rec = { r, tier, hits, words, live, inScope: s, group, outside, floor, onFile, territories: ts, inMarket: ts.some((t) => market.has(t)) };
748
750
  const keys = typeof keysOf === "function" ? keysOf(r) : null;
749
751
  const ownerKey = keys?.owner ?? (normKey(r?.owner_name) ? `owner:${normKey(r?.owner_name)}` : `record:${String(r?.record_id ?? "").trim().toLowerCase()}`);
750
752
  const key = keys?.line ?? `line:${ownerKey}|${normMark(r?.mark_text) || String(r?.record_id ?? "").trim().toLowerCase()}`;
@@ -778,6 +780,13 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
778
780
  // A line the goods test could not place shows a record whose goods are on file where it has one, so
779
781
  // `band_record` opens the record the line names; `goods_on_file` says so of that record.
780
782
  record_id: (known ? best : (inGroup.find((x) => x.onFile) ?? best)).r?.record_id ?? null,
783
+ // What the goods of the record the line names rest on (goods-evidence.mjs), where the run recorded it, and, where
784
+ // their completeness is not established, whether any goods text is held at all (`goods_text: false` where none is,
785
+ // as for a held record with no goods field).
786
+ ...(typeof evidenceOf === "function" ? (() => {
787
+ const ev = evidenceOf((known ? best : (inGroup.find((x) => x.onFile) ?? best)).r);
788
+ return { goods_evidence: ev?.state ?? null, ...(ev?.state === "unknown" && ev?.readable == null ? { goods_text: false } : {}) };
789
+ })() : {}),
781
790
  floors: l.recs.filter((x) => x.floor).length,
782
791
  ...(known ? {} : { goods_on_file: inGroup.some((x) => x.onFile) }),
783
792
  ...(offices.size && l.recs.every((x) => x.outside) ? { outside: true } : {}),
@@ -821,7 +830,7 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
821
830
  * (charter P2d); the band itself stays per-registration
822
831
  * @returns { shape, md } deterministic — same band + inputs ⇒ same bytes (no timestamps)
823
832
  */
824
- export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdContext = null, caps = {}, positions = null, goods = null, goodsTest = null, goodsWordCount = 0, dominantTerm = "", manifestDominant = "", keysOf = null, markets = [], territories = [] } = {}) {
833
+ export function buildBandShape(band, { crowdNarrowing = [], targets = [], inScopeClasses = [], crowdContext = null, caps = {}, positions = null, goods = null, goodsTest = null, goodsWordCount = 0, dominantTerm = "", manifestDominant = "", keysOf = null, markets = [], territories = [], evidenceOf = null } = {}) {
825
834
  const prepared = prepareTargets(Array.isArray(targets) && targets.every(isPrepared) ? targets.map((t) => t.text) : targets);
826
835
  const records = Array.isArray(band?.enumerated) ? band.enumerated : [];
827
836
  const crowds = Array.isArray(band?.crowds) ? band.crowds : [];
@@ -879,7 +888,7 @@ export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdC
879
888
  const floorsOrdered = probeOrder(floors, "floors");
880
889
 
881
890
  const ownerCap = Number.isFinite(caps.owners) ? caps.owners : 40;
882
- const grouped = bandGroups(records, { classify: (r) => classified.get(r), inScope, goods, dominantTerm, keysOf, markets, territories });
891
+ const grouped = bandGroups(records, { classify: (r) => classified.get(r), inScope, goods, dominantTerm, keysOf, markets, territories, evidenceOf });
883
892
  const ownersIn = (ls) => new Set(ls.map((l) => l.owner_key)).size;
884
893
  const groupOf = (g) => {
885
894
  const ls = grouped.by_group[g].lines;
@@ -905,8 +914,11 @@ export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdC
905
914
  // context slice joins a crowd when one of its terms appears in the crowd's query (case-folded).
906
915
  const ccSlices = Array.isArray(crowdContext?.slices) ? crowdContext.slices : [];
907
916
  const fold = (s) => foldDiacritics(String(s ?? "")).toLowerCase();
917
+ // what was retrieved under each crowd (register-plan.mjs, crowdNarrowing): data on the descriptor, by its qid
918
+ const endingByQid = new Map((Array.isArray(crowdNarrowing) ? crowdNarrowing : []).filter((r) => r?.qid && r?.narrowing).map((r) => [String(r.qid), r]));
908
919
  const shapeCrowds = crowds.map((c) => {
909
920
  const q = fold(c.query);
921
+ const end = c.qid ? endingByQid.get(String(c.qid)) : null;
910
922
  const joined = ccSlices
911
923
  .filter((s) => (Array.isArray(s.terms) ? s.terms : []).some((t) => fold(t) && q.includes(fold(t))))
912
924
  .map((s) => ({ unit: s.unit ?? null, axis: s.axis ?? null, terms: s.terms ?? [],
@@ -920,6 +932,7 @@ export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdC
920
932
  // executor and judgment, and dropping them here would undo the first carry silently.
921
933
  ...(c.error === true ? { error: true } : {}),
922
934
  ...(c.deferred === true ? { deferred: true } : {}),
935
+ ...(end ? { narrowing: end.narrowing, ...(end.lists?.length ? { lists: end.lists } : {}), ...(end.not_covered?.length ? { not_covered: end.not_covered } : {}) } : {}),
923
936
  crowd_context_slice: joined.length ? joined : null,
924
937
  };
925
938
  });
@@ -1084,12 +1097,41 @@ export const groupPageCall = (g) => `band_lookup {"group":"${g}"}`;
1084
1097
 
1085
1098
  // PLACEMENT'S ORDERS IN THE SHAPE. Three sentences under the groups tell the reader to give every line a fate,
1086
1099
  // and only placement owes the groups that. The band server serves the shape without them to every other
1087
- // stage (Krzys's ruling, 2026-10-14), so they are declared once here and removed there by these same strings.
1100
+ // stage (the owner's ruling, 2026-10-14), so they are declared once here and removed there by these same strings.
1088
1101
  export const SORT_THE_NEAR_ONES = "A few hundred? Sort by the client's goods and markets, read the near ones first, list the rest.";
1089
1102
  export const ONE_SET_ASIDE_ROW = "Write down what you set aside and why: one row, with this count.";
1090
1103
  export const A_FATE_BY_NAME = "Each line takes its own fate by name; no group row covers these lines.";
1091
1104
  export const PLACEMENT_GROUP_ORDERS = Object.freeze([SORT_THE_NEAR_ONES, ONE_SET_ASIDE_ROW, A_FATE_BY_NAME]);
1092
1105
 
1106
+ // AND THE FATES THE GROUPS' SIZE SENTENCES ORDER (ruled 2026-10-18). Five more of placement's orders sit inside the
1107
+ // groups' size sentences, and the shape's commands to page a group name a lookup only placement is offered. Every
1108
+ // other stage is served the shape without them, removed by these same strings (`withoutPlacementOrders`); each
1109
+ // group's identity, members and count stay, and so does the fact that a crowded group prints only its first page.
1110
+ export const FLOORS_BY_NAME_HERE = "Every floor here is placed or set aside by name; this group's row covers only its other lines.";
1111
+ export const A_CROWDS_ONE_ROW = "Read the first page, carry forward any line a lawyer would raise, and set the rest aside in this group's one row with the count: the client is told the field is crowded.";
1112
+ export const AN_UNREAD_CROWDS_ONE_ROW = "Carry forward any line a lawyer would raise, and set the rest aside in this list's one row with the count, on the ground that their goods are unreadable and the field is crowded.";
1113
+ export const AN_UNREAD_CROWDS_FLOOR = "A floor here is placed or set aside by name on its status or its territory; this row never covers it.";
1114
+ export const AN_UNREAD_LISTS_FLOOR = "A floor here is placed, or set aside by name on its status or its territory; any other line may be set aside on the ground that its goods are unknown.";
1115
+ // What placement does with a line of that list before its fate, by its marker (approved wording, 2026-10-19). Placement's
1116
+ // alone, as its other orders are: every other stage keeps the markers as evidence and is not told of a fate.
1117
+ export const AN_UNREAD_LINE_IS_CHECKED = 'A line marked "on file, not readable", "cut by the register, no goods word found", "goods held; completeness not established" or "no goods text held; completeness not established" must be checked with band_record before its fate. Read the evidence held for it, keeping its source and goods completeness clear. A line marked "no goods on file", "fetch failed, no goods on file" or "the register\'s record lists no goods" has no goods text on file. Missing goods do not establish that the goods are unrelated.';
1118
+ export const PLACEMENT_LINE_ORDERS = Object.freeze([FLOORS_BY_NAME_HERE, A_CROWDS_ONE_ROW, AN_UNREAD_CROWDS_ONE_ROW, AN_UNREAD_CROWDS_FLOOR, AN_UNREAD_LISTS_FLOOR, AN_UNREAD_LINE_IS_CHECKED]);
1119
+ const SHAPE_LISTS = ["a", "b", "c", "d", "rest", "no_goods"];
1120
+ const pageEveryOtherLine = (g) => `; page every other line with ${groupPageCall(g)}`;
1121
+ const pageWhereALineNeedsIt = (g) => `; page with ${groupPageCall(g)} only where a line needs reading`;
1122
+ const pageTheFloorsLeft = (g) => `Where more floors are counted than this page shows, page the rest with ${groupPageCall(g)}; they come first.`;
1123
+ /**
1124
+ * The shape as every stage but placement is served it: placement's orders and its group-page commands removed,
1125
+ * each with the space before it, inside its line, so no line moves and the line each listed mark is printed on
1126
+ * still holds. A crowded group's "The first N lines are below" stays, as a fact about what is printed. PURE.
1127
+ */
1128
+ export function withoutPlacementOrders(text) {
1129
+ let t = String(text ?? "");
1130
+ for (const order of [...PLACEMENT_GROUP_ORDERS, ...PLACEMENT_LINE_ORDERS, ...SHAPE_LISTS.map(pageTheFloorsLeft)]) t = t.split(` ${order}`).join("").split(order).join("");
1131
+ for (const g of SHAPE_LISTS) t = t.split(pageEveryOtherLine(g)).join("").split(pageWhereALineNeedsIt(g)).join("");
1132
+ return t;
1133
+ }
1134
+
1093
1135
  function renderGroupsMd(md, groups, rows = []) {
1094
1136
  md.push("", "## The band in the lawyer's order — for the client's goods first", "");
1095
1137
  if (groups.dominant_term && groups.manifest_dominant && normKey(groups.dominant_term) !== normKey(groups.manifest_dominant))
@@ -1111,7 +1153,19 @@ function renderGroupsMd(md, groups, rows = []) {
1111
1153
  const word = groups.dominant_term || groups.manifest_dominant;
1112
1154
  const carrying = word ? `carrying «${cell(word)}»` : "carrying the searched word";
1113
1155
  const heading = (g) => (g === "c" ? `(c) The same family as the mark, or ${carrying}, for the client's goods` : GROUP_HEADINGS[g]);
1114
- const unread = (l) => (tested.source === "none" ? "—" : l.goods_on_file ? "on file, not readable" : "no goods on file");
1156
+ // A line the goods test could not place says why, from what its goods rest on: cut by the register, held but not
1157
+ // established whole (with goods text, or with none, as a held record with no goods field is), the register's
1158
+ // record listing none, a fetch that failed, or nothing held. Goods held whole that the test still could not read
1159
+ // are in a language it cannot use. A shape derived with no record of the goods evidence reads as it always did.
1160
+ const unread = (l) => {
1161
+ if (tested.source === "none") return "—";
1162
+ if (l.goods_evidence === "cut") return "cut by the register, no goods word found";
1163
+ if (l.goods_evidence === "unknown") return l.goods_text === false ? "no goods text held; completeness not established" : "goods held; completeness not established";
1164
+ if (l.goods_evidence === "absent") return "the register's record lists no goods";
1165
+ if (l.goods_evidence === "unavailable") return "fetch failed, no goods on file";
1166
+ if (l.goods_evidence === "complete") return "on file, not readable";
1167
+ return l.goods_on_file ? "on file, not readable" : "no goods on file";
1168
+ };
1115
1169
  const words = (l) => (l.goods_words == null ? unread(l) : `${fmtN(l.goods_words.length)} of ${fmtN(total)}${l.goods_words.length ? `: ${l.goods_words.join(", ")}` : ""}`);
1116
1170
  const count = (grp) => `${fmtN(grp.records)} records in ${fmtN(grp.lines)} lines from ${fmtN(grp.owners)} owners`;
1117
1171
  const where = (l) => `${l.territories.join(",")}${l.outside ? " (outside the order)" : ""}`;
@@ -1129,10 +1183,10 @@ function renderGroupsMd(md, groups, rows = []) {
1129
1183
  const readA = "These are the identical and near-identical marks live in the ordered classes for the client's goods: read every line.";
1130
1184
  md.push(g === "a"
1131
1185
  ? (crowded
1132
- ? `${count(grp)}. ${readA} ${firstPage}; page every other line with ${groupPageCall(g)}. Every floor here is placed or set aside by name; this group's row covers only its other lines.`
1186
+ ? `${count(grp)}. ${readA} ${firstPage}${pageEveryOtherLine(g)}. ${FLOORS_BY_NAME_HERE}`
1133
1187
  : grp.lines <= READ_ALL_LINES ? `${count(grp)}. Fifty? Read them all.` : `${count(grp)}. ${readA}`)
1134
1188
  : crowded
1135
- ? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. Read the first page, carry forward any line a lawyer would raise, and set the rest aside in this group's one row with the count: the client is told the field is crowded. ${firstPage}; page with ${groupPageCall(g)} only where a line needs reading.`
1189
+ ? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}.`
1136
1190
  : grp.lines <= READ_ALL_LINES
1137
1191
  ? `${count(grp)}. Fifty? Read them all.`
1138
1192
  : `${count(grp)}. ${SORT_THE_NEAR_ONES}`, "");
@@ -1147,7 +1201,7 @@ function renderGroupsMd(md, groups, rows = []) {
1147
1201
  const restCrowded = rest.records >= groups.crowded_at;
1148
1202
  md.push(`**The rest: ${count(rest)}${outside}** — every record in none of (a) to (d) above. ${!rest.lines ? ONE_SET_ASIDE_ROW
1149
1203
  : restCrowded
1150
- ? `A thousand or more? The field is crowded. Read the first page, carry forward any line a lawyer would raise, and set the rest aside in this group's one row with the count: the client is told the field is crowded. ${firstPage}; page with ${groupPageCall("rest")} only where a line needs reading.`
1204
+ ? `A thousand or more? The field is crowded. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt("rest")}.`
1151
1205
  : `${rest.lines <= READ_ALL_LINES ? "Fifty? Read them all." : SORT_THE_NEAR_ONES} ${ONE_SET_ASIDE_ROW}`}`);
1152
1206
  if (rest.lines) {
1153
1207
  md.push("", ...head);
@@ -1164,8 +1218,8 @@ function renderGroupsMd(md, groups, rows = []) {
1164
1218
  ? `**Not tested on goods: ${count(ng)}.** No goods words were given, so the goods test could not place them.`
1165
1219
  : `**Goods the test could not read: ${count(ng)}.**`;
1166
1220
  md.push("", crowded
1167
- ? `${lead} A thousand or more? ${firstPage}, the floors first, then the live ones in the ordered classes. Carry forward any line a lawyer would raise, and set the rest aside in this list's one row with the count, on the ground that their goods are unreadable and the field is crowded. A floor here is placed or set aside by name on its status or its territory; this row never covers it. Where more floors are counted than this page shows, page the rest with ${groupPageCall("no_goods")}; they come first.`
1168
- : `${lead} ${ng.lines <= READ_ALL_LINES ? "Fifty? Read them all." : "A few hundred? Read the live ones in the ordered classes first."} ${A_FATE_BY_NAME}${tested.source === "none" ? "" : ' A line marked "on file, not readable" has goods on file: open its record with band_record before its fate. A line marked "no goods on file" has nothing to open.'} A floor here is placed, or set aside by name on its status or its territory; any other line may be set aside on the ground that its goods are unknown.`, "");
1221
+ ? `${lead} A thousand or more? ${firstPage}, the floors first, then the live ones in the ordered classes. ${AN_UNREAD_CROWDS_ONE_ROW} ${AN_UNREAD_CROWDS_FLOOR} ${pageTheFloorsLeft("no_goods")}`
1222
+ : `${lead} ${ng.lines <= READ_ALL_LINES ? "Fifty? Read them all." : "A few hundred? Read the live ones in the ordered classes first."} ${A_FATE_BY_NAME}${tested.source === "none" ? "" : ` ${AN_UNREAD_LINE_IS_CHECKED}`} ${AN_UNREAD_LISTS_FLOOR}`, "");
1169
1223
  md.push(...head);
1170
1224
  for (const l of crowded ? ng.list.slice(0, groups.first_page) : ng.list) { rows.push([md.length, l.key]); md.push(row(l)); }
1171
1225
  }
@@ -118,6 +118,16 @@ export function limitExceedsCeiling(sec) {
118
118
  return Number.isFinite(sec) && sec > LIMIT_CEILING_SEC;
119
119
  }
120
120
 
121
+ /**
122
+ * The band-reading stages whose dispatch would refuse a band of `bytes`: each stage's own base, derived from the band's
123
+ * bytes as `stageOnce` derives it, against the same ceiling. `baseOf` gives a stage's base (the stage table's
124
+ * `timeoutSec`). A band that cannot be measured refuses nothing, as its dispatch returns the base. PURE.
125
+ */
126
+ export function stagesRefusingBand(bytes, baseOf) {
127
+ if (!Number.isFinite(bytes)) return [];
128
+ return [...BAND_READING_STAGES].filter((s) => limitExceedsCeiling(derivedLimitSec(baseOf(s), { bytes }).sec));
129
+ }
130
+
121
131
  /** The refusal a dispatch past the ceiling carries — it NAMES the size, which is the finding. PURE. */
122
132
  export function ceilingRefusal(stage, derived) {
123
133
  const mb = Number.isFinite(derived?.inputBytes) ? (derived.inputBytes / (1024 * 1024)).toFixed(1) : "an unmeasured";
@@ -71,6 +71,24 @@ export function answeredAs(qid, foldRefusals) {
71
71
  return r && r.kind === "duplicate-question" && r.twin ? String(r.twin) : qid;
72
72
  }
73
73
 
74
+ /**
75
+ * The reading turn's judgment that settles a minted slice, or null. The frame reopen's re-sweep refuses a slice
76
+ * that repeats exactly a waiting family the reading turn WITHHELD (register-plan.mjs, refuseWithheldFamilies), and
77
+ * its twin is that family. The twin never ran, by the turn's decision, so nothing answers the slice: it is settled
78
+ * by the turn's recorded reason instead, and it is never read as a question that ran, was answered or came back
79
+ * clean. `answeredAs` above stays the duplicate rule alone, and resolves such a slice to nothing.
80
+ *
81
+ * ONLY THE WHOLE RECORD SETTLES: the `withheld-by-judgment` kind, the family it repeats, and the reason on the
82
+ * family's record. Anything less is a slice nobody judged, and it stays a slice that never ran. One rule for the
83
+ * directive below and the remedy term ledger, so the two cannot read a slice apart. PURE.
84
+ * @returns {{family: string, reason: string}|null}
85
+ */
86
+ export function withheldAs(qid, foldRefusals) {
87
+ const r = foldRefusals?.get?.(qid);
88
+ const reason = String(r?.reason ?? "").trim();
89
+ return r && r.kind === "withheld-by-judgment" && r.twin && reason ? { family: String(r.twin), reason } : null;
90
+ }
91
+
74
92
  /**
75
93
  * Verify a register frame-reopen directive was actually SEARCHED as intended (Part B, arm #1). A directive
76
94
  * is closed ONLY if EVERY minted qid clears three checks:
@@ -85,7 +103,12 @@ export function answeredAs(qid, foldRefusals) {
85
103
  *
86
104
  * A slice the fold refused as already-asked is checked on the row that answered it (`answeredAs`), on the same
87
105
  * three checks, and a directive closed that way names each such row in `answered_by`.
88
- * @returns {{closed: boolean, reason: string|null, answered_by?: {qid: string, twin: string}[]}}
106
+ *
107
+ * A slice refused as a family the reading turn withheld (`withheldAs`) is not checked at all, because nothing ran
108
+ * for it: it is settled by that judgment, and a directive closed with one names it, and the family, in
109
+ * `withheld_by_judgment`, never in `answered_by`. It settles only itself. Every other slice must still clear the
110
+ * three checks, so a search that failed is never excused by a withheld sibling.
111
+ * @returns {{closed: boolean, reason: string|null, answered_by?: {qid: string, twin: string}[], withheld_by_judgment?: {qid: string, family: string}[]}}
89
112
  */
90
113
  export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], executedQids, blocksByQid, foldRefusals = new Map() } = {}) {
91
114
  const list = qids ?? [];
@@ -94,7 +117,10 @@ export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], execu
94
117
  const executed = executedQids ?? new Set();
95
118
  const byQid = blocksByQid ?? new Map();
96
119
  const answeredBy = [];
120
+ const withheld = [];
97
121
  for (const qid of list) {
122
+ const settled = withheldAs(qid, foldRefusals);
123
+ if (settled) { withheld.push({ qid, family: settled.family }); continue; }
98
124
  const q = answeredAs(qid, foldRefusals);
99
125
  const as = q === qid ? qid : `${qid} (answered by ${q})`;
100
126
  if (!executed.has(q)) return { closed: false, reason: `slice-not-landed:${as}` };
@@ -106,5 +132,6 @@ export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], execu
106
132
  return { closed: false, reason: `wrong-scope:${as} searched[${searched.join(",")}]⊉intended[${want.join(",")}]` };
107
133
  if (q !== qid) answeredBy.push({ qid, twin: q });
108
134
  }
109
- return { closed: true, reason: null, ...(answeredBy.length ? { answered_by: answeredBy } : {}) };
135
+ return { closed: true, reason: null, ...(answeredBy.length ? { answered_by: answeredBy } : {}),
136
+ ...(withheld.length ? { withheld_by_judgment: withheld } : {}) };
110
137
  }
@@ -116,7 +116,7 @@ export function recordCoverageStatus(spec, received, { now = () => new Date().to
116
116
  export const NOT_STATED = "not stated";
117
117
 
118
118
  /**
119
- * THE STATUSES A FINDINGS FILE STATES, RECORDED BY CODE (Krzys, 2026-10-01). A seat whose findings carry
119
+ * THE STATUSES A FINDINGS FILE STATES, RECORDED BY CODE (the owner, 2026-10-01). A seat whose findings carry
120
120
  * a coverage ledger but made no `record_coverage_status` call is accepted, not re-run: a re-run for this
121
121
  * was a full stage re-run, and nothing is added to what the model reads. Each ledger row's status is read
122
122
  * from the row as the findings wrote it — one of the three words, else "not stated" — and recorded in the