clearotron 0.4.1 → 0.5.0-beta.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 (90) 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 +23 -0
  6. package/driver/ask-ledger.mjs +86 -15
  7. package/driver/band-shape.mjs +61 -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.mjs +3 -0
  19. package/driver/coverage-union.mjs +11 -2
  20. package/driver/digest-queue.mjs +1 -1
  21. package/driver/disposition-call.mjs +1 -1
  22. package/driver/document-coverage.mjs +3 -2
  23. package/driver/effective-scope.mjs +2 -2
  24. package/driver/engine/mcp/band-server.mjs +77 -13
  25. package/driver/engine/mcp/dispositions-server.mjs +2 -2
  26. package/driver/engine/mcp/recording-server.mjs +10 -6
  27. package/driver/findings-model.mjs +16 -4
  28. package/driver/floor-duty.mjs +9 -0
  29. package/driver/gate-metrics.mjs +7 -1
  30. package/driver/gateway.mjs +28 -14
  31. package/driver/goods-evidence.mjs +295 -0
  32. package/driver/knockout-assess-record.mjs +1 -1
  33. package/driver/narrative-refutation-record.mjs +40 -2
  34. package/driver/package.json +1 -1
  35. package/driver/pipeline.mjs +1945 -314
  36. package/driver/placement-decisions.mjs +243 -0
  37. package/driver/placement-form.mjs +42 -8
  38. package/driver/placement-union.mjs +14 -6
  39. package/driver/provider-usage.mjs +10 -7
  40. package/driver/publish/audit-from-spine.mjs +5 -2
  41. package/driver/publish/index.mjs +19 -4
  42. package/driver/publish/xlsx.mjs +6 -6
  43. package/driver/recall-reconciliation.mjs +15 -5
  44. package/driver/record-carry.mjs +70 -6
  45. package/driver/record-discard.mjs +7 -1
  46. package/driver/record-fold.mjs +14 -0
  47. package/driver/record-goods.mjs +101 -5
  48. package/driver/register-plan.mjs +89 -0
  49. package/driver/registry-fidelity.mjs +34 -3
  50. package/driver/remedy-accounting.mjs +52 -14
  51. package/driver/repair-composers.mjs +265 -105
  52. package/driver/repair-scope.mjs +327 -0
  53. package/driver/repair-targets.mjs +92 -0
  54. package/driver/result-noun-fields.mjs +3 -3
  55. package/driver/screen-gate.mjs +96 -0
  56. package/driver/skills/clearance-common-law/SKILL.md +8 -0
  57. package/driver/skills/clearance-register/SKILL.md +5 -5
  58. package/driver/skills/clearance-register/digest.md +14 -14
  59. package/driver/skills/clearance-register/unit.md +3 -3
  60. package/driver/skills/clearance-search/delivery-contract.md +2 -58
  61. package/driver/skills/clearance-search/report-prose.md +12 -3
  62. package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
  63. package/driver/skills/clearance-search/synthesis-rules.md +10 -13
  64. package/driver/skills/clearance-variants/SKILL.md +4 -4
  65. package/driver/skills/knockout-assess/SKILL.md +9 -14
  66. package/driver/skills/knockout-frame/SKILL.md +5 -5
  67. package/driver/skills/narrative-refutation/SKILL.md +1 -1
  68. package/driver/skills/placement-inquiry/SKILL.md +1 -1
  69. package/driver/skills/whatif-memo/SKILL.md +1 -1
  70. package/driver/stage-context.mjs +2 -1
  71. package/driver/stages-knockout.mjs +1 -1
  72. package/driver/stages.mjs +95 -60
  73. package/driver/suite-census.json +367 -19
  74. package/driver/synthesis-record.mjs +310 -8
  75. package/driver/terminal-clamp.mjs +53 -2
  76. package/driver/verify-knockout.mjs +22 -4
  77. package/driver/verify.mjs +23 -3
  78. package/mcp-server/CHANGELOG.md +4 -0
  79. package/mcp-server/lib/driver.mjs +5 -0
  80. package/mcp-server/lib/events.mjs +25 -4
  81. package/mcp-server/lib/findings.mjs +4 -2
  82. package/mcp-server/package.json +1 -1
  83. package/package.json +1 -1
  84. package/portal-ui/package.json +1 -1
  85. package/providers/jx/src/core.js +3 -4
  86. package/providers/jx/src/judge.js +2 -2
  87. package/providers/jx/src/nativeread.js +1 -2
  88. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  89. package/providers/oauth-mcp-bridge/package.json +1 -1
  90. package/scripts/e2e-first-time.mjs +29 -12
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": "e2212d67aeaa269f95682ab906b16c03d37ffab4",
3
+ "version": "0.5.0-beta.0"
4
4
  }
@@ -1,5 +1,28 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.5.0-beta.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
9
+ ### Patch Changes
10
+
11
+ - 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.
12
+ - Fixed: A finding added by a late correction now appears with its own card in the delivered report.
13
+ - 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.
14
+ - 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.
15
+ - 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.
16
+ - 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.
17
+ - 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.
18
+ - Fixed: Knockout reports no longer carry two standing caveats. The caveat on a conditional result stays.
19
+ - Fixed: A resumed clearance no longer shows the false line "A machine check on the report did not pass".
20
+ - 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.
21
+ - 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.
22
+ - 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.
23
+ - 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.
24
+ - Fixed: The Audit Trail now lists each register record set aside while the results were ranked, with the reason given.
25
+
3
26
  ## 0.4.1
4
27
 
5
28
  ### Patch Changes
@@ -63,10 +63,16 @@ import { squash } from "./doubt-ledger.mjs";
63
63
  // qids: [ ... ], // the plan qids that would answer it (may be [])
64
64
  // ending: { kind, by, evidence, reasons, ts } | null,
65
65
  // handoff: "<the surface a human reads an un-ended/handed-over ask on>" | null,
66
+ // part, family, // only on a request kept as parts: "ran" or "family", and that family's qid
67
+ // short_id, // every part of a frame request: the request's short id from before its hash
68
+ // legacy_id, // the one part an old closure of that short id may settle: the ran part
66
69
  // }
67
70
 
68
71
  const clip = (s, n = 200) => { const t = String(s ?? "").replace(/\s+/g, " ").trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
69
72
  const slug = (s) => String(s ?? "").toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "x";
73
+ // A short, stable hash of an exact string (FNV-1a, 32 bits), beside a slug, so two qids the slug folds or cuts to
74
+ // one spelling still give two ids. PURE: no import, no clock.
75
+ 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
76
 
71
77
  export const ASK_ENDINGS = ["executed", "judged-immaterial", "recovery"];
72
78
 
@@ -170,6 +176,21 @@ function endingForQids(qids, join, ts) {
170
176
  return null;
171
177
  }
172
178
 
179
+ /** The ending for a frame directive whose every slice repeated a family the reading turn withheld: that
180
+ * judgment, in the turn's own recorded words, and nothing else — never `executed`, since nothing ran. A
181
+ * family whose record cannot be read leaves the ask OPEN: there is no judgment to show, and an ending
182
+ * without one would be invented. `withheld` is the record as readWithheldFamilies returns it. PURE. */
183
+ function withheldEnding(families, withheld, ts) {
184
+ const reasons = families.map((f) => String(withheld?.[f]?.reason ?? "").trim());
185
+ if (!families.length || reasons.some((r) => !r)) return null;
186
+ const axes = [...new Set(families.map((f) => String(withheld?.[f]?.axis ?? "").trim()).filter(Boolean))];
187
+ return mkEnding("judged-immaterial", "reading-turn", {
188
+ reasons: [...new Set(reasons)],
189
+ evidence: axes.length ? axes.map((a) => `_driver/withheld-families-${a}.json`).join(", ") : "_driver/withheld-families",
190
+ ts,
191
+ });
192
+ }
193
+
173
194
  /** The {ending, handoff} pair for one cross-check directive. Only `recall` is subject to the
174
195
  * discharge rule; every other net keeps the ending it always had. PURE. */
175
196
  function recallEnding(name, qid, join, ts) {
@@ -195,7 +216,8 @@ function recallEnding(name, qid, join, ts) {
195
216
  * 3. escalation-skip run.jsonl `escalation-skipped` events
196
217
  * 4. envelope run.jsonl `envelope-decision` / `envelope-closed` events (+ state.failed)
197
218
  * 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)
219
+ * 6. frame-diff _driver/frame-reopen.json (requested/swept/deferrals + directive_qids), and the
220
+ * reading turn's withheld-family record for a slice its judgment settled
199
221
  * 7. form-neighbourhood same receipt, rows the receipt's `born` map marks form-oracle-injected
200
222
  * 8. supplemental-proposal register-units/<axis>-supplemental-plan.json entries[] + rejected[]
201
223
  * 9. cross-check _driver/register-xcheck.json + _driver/register-recall.json (+ overflow)
@@ -207,7 +229,7 @@ export function deriveAsks({
207
229
  escalationState = null, events = null,
208
230
  frameReopen = null, screenGateUnresolved = null,
209
231
  supplementalPlans = null, xcheck = null, recall = null,
210
- planExecution = null,
232
+ planExecution = null, withheldFamilies = null,
211
233
  } = {}, { ts = null } = {}) {
212
234
  const join = planJoinFrom(planExecution);
213
235
  const evs = Array.isArray(events) ? events.filter((e) => e && typeof e === "object") : [];
@@ -346,25 +368,56 @@ export function deriveAsks({
346
368
  const twinOf = new Map((Array.isArray(fr.answered_elsewhere) ? fr.answered_elsewhere : [])
347
369
  .filter((x) => typeof x?.directive === "string")
348
370
  .map((x) => [x.directive, new Map((Array.isArray(x.slices) ? x.slices : []).filter((t) => t?.qid && t?.twin).map((t) => [t.qid, t.twin]))]));
371
+ // A slice refused as a family the reading turn withheld never ran either, and nothing answered it: the turn
372
+ // decided not to ask that family, and its record keeps why. The receipt names such slices
373
+ // (`withheld_by_judgment`). They are left out of the computed ending, which rests on the slices that ran,
374
+ // and a directive with no other slice ends on the turn's recorded judgment.
375
+ //
376
+ // EACH SLICE ENDS ON WHAT HAPPENED TO IT. A slice that ran ends on the execution record, through its twin
377
+ // where the fold refused it as already asked. A slice whose family the turn still withholds ends on the
378
+ // turn's recorded reason. A slice whose family the turn released after the fold is no longer withheld on
379
+ // record, so it ends on what the execution record says of the family itself: it ran, it was deferred, or it
380
+ // stays open; so does a slice whose record cannot be read, which has no judgment to end on.
381
+ //
382
+ // ONE RECORD PER PART, AND A PART KEEPS ITS ID. The slices that ran are one part, under the directive's own
383
+ // id. Each family the settled slices repeat is a part of its own, under an id made from that family, so its
384
+ // id never moves when the family's disposition changes (withheld, then released and run) or a sibling part
385
+ // disappears. A closure names one part, and settles only that part; an id no part carries settles nothing.
386
+ const withheldOf = new Map((Array.isArray(fr.withheld_by_judgment) ? fr.withheld_by_judgment : [])
387
+ .filter((x) => typeof x?.directive === "string")
388
+ .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
389
  for (const key of requested) {
350
390
  const qids = (Array.isArray(qidsByKey[key]) ? qidsByKey[key] : []).filter(Boolean);
351
391
  const place = bornByKey[key] === "form-neighbourhood" ? "form-neighbourhood" : "frame-diff";
352
- let ending = null, handoff = null;
392
+ const born = { place, artifact: "_driver/frame-reopen.json", ref: key, ts: fr.ts ?? null };
393
+ 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 };
394
+ // THE ID CARRIES A HASH OF THE WHOLE KEY. The slug cuts at 48 characters and folds punctuation, so two
395
+ // requests that differ only after the cut, or only in punctuation, gave one id, and the second was dropped.
396
+ // The short id stays on the ran part as `legacy_id`, for a closure written before the hash (applyAskClosure).
397
+ const id = `ask:frame:${slug(key)}-${fnv32(key)}`;
398
+ const legacy = `ask:frame:${slug(key)}`;
399
+ let parts = [{ id, qids, ending: null, handoff: null, legacy }];
353
400
  const d = deferrals.get(key);
354
401
  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";
402
+ parts = [{ id, qids, ending: mkEnding("recovery", "frame-reopen", { reasons: [String(d.reason ?? "deferred")], evidence: "_driver/frame-reopen.json deferrals", ts }),
403
+ handoff: "open coverage row (frame-gap) + the CLEAR→CONDITIONAL clamp — the report states the unswept omission", legacy }];
404
+ } else if (swept.has(key) && !qids.length) {
405
+ 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
406
  } 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 });
407
+ const settled = withheldOf.get(key) ?? new Map();
408
+ const ran = qids.filter((q) => !settled.has(q));
409
+ const families = [...new Set(qids.filter((q) => settled.has(q)).map((q) => settled.get(q)))];
410
+ parts = [
411
+ 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
412
+ ...(families.length ? { part: "ran" } : {}), legacy },
413
+ ...families.map((fam) => ({
414
+ id: `${id}:family:${slug(fam)}-${fnv32(fam)}`, qids: qids.filter((q) => settled.get(q) === fam), part: "family", family: fam,
415
+ ending: String(withheldFamilies?.[fam]?.reason ?? "").trim() ? withheldEnding([fam], withheldFamilies, ts) : endingForQids([fam], join, ts),
416
+ })),
417
+ ].filter(Boolean);
361
418
  }
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
- });
419
+ for (const p of parts) push({ ask_id: p.id, born, ask, qids: p.qids, ending: p.ending, handoff: p.handoff ?? null,
420
+ ...(p.part ? { part: p.part } : {}), ...(p.family ? { family: p.family } : {}), short_id: legacy, ...(p.legacy ? { legacy_id: p.legacy } : {}) });
368
421
  }
369
422
  }
370
423
 
@@ -651,12 +704,30 @@ export function applyAskClosure(asks, closureLines, fileTexts = {}, { ts = null
651
704
  const byId = new Map();
652
705
  for (const l of closureLines ?? []) if (l?.id && !byId.has(l.id)) byId.set(l.id, l);
653
706
  const unverified = [];
707
+ // A CLOSURE WRITTEN BEFORE FRAME IDS CARRIED THEIR HASH names a request by its short id. Every request that had that
708
+ // short id could be the one it meant, a request now kept only as family parts included. It settles the ran part of
709
+ // the one request that had it, and nothing where two did: settling either could end an obligation it never judged.
710
+ // An ambiguous citation is reported with the parts it could have meant, while any of them is still open.
711
+ const requestsOf = new Map(), partsOf = new Map();
712
+ for (const a of asks ?? []) {
713
+ const short = a?.short_id ?? a?.legacy_id;
714
+ if (!short || short === a.ask_id) continue;
715
+ requestsOf.set(short, new Set([...(requestsOf.get(short) ?? []), a.born?.ref ?? a.ask_id]));
716
+ partsOf.set(short, [...(partsOf.get(short) ?? []), a]);
717
+ }
718
+ for (const [short, requests] of requestsOf) {
719
+ const l = byId.get(short);
720
+ const parts = partsOf.get(short) ?? [];
721
+ 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) });
722
+ }
723
+ const lineFor = (a) => byId.get(a.ask_id)
724
+ ?? (a.legacy_id && requestsOf.get(a.legacy_id)?.size === 1 ? byId.get(a.legacy_id) : undefined);
654
725
  const carryIntoFindings = [];
655
726
  let immaterialByStage = 0;
656
727
  const delivered = deliveredMarks(fileTexts?.["findings.json"]);
657
728
  const out = (asks ?? []).map((a) => {
658
729
  if (a?.ending) return a; // the stage may never touch an ended ask
659
- const l = byId.get(a.ask_id);
730
+ const l = lineFor(a);
660
731
  if (!l) return a;
661
732
  if (l.verdict === "OPEN") return { ...a, handoff: clip(l.reason, 300) || a.handoff };
662
733
  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, { 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;
@@ -1084,12 +1093,41 @@ export const groupPageCall = (g) => `band_lookup {"group":"${g}"}`;
1084
1093
 
1085
1094
  // PLACEMENT'S ORDERS IN THE SHAPE. Three sentences under the groups tell the reader to give every line a fate,
1086
1095
  // 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.
1096
+ // stage (the owner's ruling, 2026-10-14), so they are declared once here and removed there by these same strings.
1088
1097
  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
1098
  export const ONE_SET_ASIDE_ROW = "Write down what you set aside and why: one row, with this count.";
1090
1099
  export const A_FATE_BY_NAME = "Each line takes its own fate by name; no group row covers these lines.";
1091
1100
  export const PLACEMENT_GROUP_ORDERS = Object.freeze([SORT_THE_NEAR_ONES, ONE_SET_ASIDE_ROW, A_FATE_BY_NAME]);
1092
1101
 
1102
+ // AND THE FATES THE GROUPS' SIZE SENTENCES ORDER (ruled 2026-10-18). Five more of placement's orders sit inside the
1103
+ // groups' size sentences, and the shape's commands to page a group name a lookup only placement is offered. Every
1104
+ // other stage is served the shape without them, removed by these same strings (`withoutPlacementOrders`); each
1105
+ // group's identity, members and count stay, and so does the fact that a crowded group prints only its first page.
1106
+ 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.";
1107
+ 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.";
1108
+ 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.";
1109
+ 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.";
1110
+ 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.";
1111
+ // What placement does with a line of that list before its fate, by its marker (approved wording, 2026-10-19). Placement's
1112
+ // alone, as its other orders are: every other stage keeps the markers as evidence and is not told of a fate.
1113
+ 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.';
1114
+ 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]);
1115
+ const SHAPE_LISTS = ["a", "b", "c", "d", "rest", "no_goods"];
1116
+ const pageEveryOtherLine = (g) => `; page every other line with ${groupPageCall(g)}`;
1117
+ const pageWhereALineNeedsIt = (g) => `; page with ${groupPageCall(g)} only where a line needs reading`;
1118
+ const pageTheFloorsLeft = (g) => `Where more floors are counted than this page shows, page the rest with ${groupPageCall(g)}; they come first.`;
1119
+ /**
1120
+ * The shape as every stage but placement is served it: placement's orders and its group-page commands removed,
1121
+ * each with the space before it, inside its line, so no line moves and the line each listed mark is printed on
1122
+ * still holds. A crowded group's "The first N lines are below" stays, as a fact about what is printed. PURE.
1123
+ */
1124
+ export function withoutPlacementOrders(text) {
1125
+ let t = String(text ?? "");
1126
+ for (const order of [...PLACEMENT_GROUP_ORDERS, ...PLACEMENT_LINE_ORDERS, ...SHAPE_LISTS.map(pageTheFloorsLeft)]) t = t.split(` ${order}`).join("").split(order).join("");
1127
+ for (const g of SHAPE_LISTS) t = t.split(pageEveryOtherLine(g)).join("").split(pageWhereALineNeedsIt(g)).join("");
1128
+ return t;
1129
+ }
1130
+
1093
1131
  function renderGroupsMd(md, groups, rows = []) {
1094
1132
  md.push("", "## The band in the lawyer's order — for the client's goods first", "");
1095
1133
  if (groups.dominant_term && groups.manifest_dominant && normKey(groups.dominant_term) !== normKey(groups.manifest_dominant))
@@ -1111,7 +1149,19 @@ function renderGroupsMd(md, groups, rows = []) {
1111
1149
  const word = groups.dominant_term || groups.manifest_dominant;
1112
1150
  const carrying = word ? `carrying «${cell(word)}»` : "carrying the searched word";
1113
1151
  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");
1152
+ // A line the goods test could not place says why, from what its goods rest on: cut by the register, held but not
1153
+ // established whole (with goods text, or with none, as a held record with no goods field is), the register's
1154
+ // record listing none, a fetch that failed, or nothing held. Goods held whole that the test still could not read
1155
+ // are in a language it cannot use. A shape derived with no record of the goods evidence reads as it always did.
1156
+ const unread = (l) => {
1157
+ if (tested.source === "none") return "—";
1158
+ if (l.goods_evidence === "cut") return "cut by the register, no goods word found";
1159
+ if (l.goods_evidence === "unknown") return l.goods_text === false ? "no goods text held; completeness not established" : "goods held; completeness not established";
1160
+ if (l.goods_evidence === "absent") return "the register's record lists no goods";
1161
+ if (l.goods_evidence === "unavailable") return "fetch failed, no goods on file";
1162
+ if (l.goods_evidence === "complete") return "on file, not readable";
1163
+ return l.goods_on_file ? "on file, not readable" : "no goods on file";
1164
+ };
1115
1165
  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
1166
  const count = (grp) => `${fmtN(grp.records)} records in ${fmtN(grp.lines)} lines from ${fmtN(grp.owners)} owners`;
1117
1167
  const where = (l) => `${l.territories.join(",")}${l.outside ? " (outside the order)" : ""}`;
@@ -1129,10 +1179,10 @@ function renderGroupsMd(md, groups, rows = []) {
1129
1179
  const readA = "These are the identical and near-identical marks live in the ordered classes for the client's goods: read every line.";
1130
1180
  md.push(g === "a"
1131
1181
  ? (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.`
1182
+ ? `${count(grp)}. ${readA} ${firstPage}${pageEveryOtherLine(g)}. ${FLOORS_BY_NAME_HERE}`
1133
1183
  : grp.lines <= READ_ALL_LINES ? `${count(grp)}. Fifty? Read them all.` : `${count(grp)}. ${readA}`)
1134
1184
  : 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.`
1185
+ ? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}.`
1136
1186
  : grp.lines <= READ_ALL_LINES
1137
1187
  ? `${count(grp)}. Fifty? Read them all.`
1138
1188
  : `${count(grp)}. ${SORT_THE_NEAR_ONES}`, "");
@@ -1147,7 +1197,7 @@ function renderGroupsMd(md, groups, rows = []) {
1147
1197
  const restCrowded = rest.records >= groups.crowded_at;
1148
1198
  md.push(`**The rest: ${count(rest)}${outside}** — every record in none of (a) to (d) above. ${!rest.lines ? ONE_SET_ASIDE_ROW
1149
1199
  : 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.`
1200
+ ? `A thousand or more? The field is crowded. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt("rest")}.`
1151
1201
  : `${rest.lines <= READ_ALL_LINES ? "Fifty? Read them all." : SORT_THE_NEAR_ONES} ${ONE_SET_ASIDE_ROW}`}`);
1152
1202
  if (rest.lines) {
1153
1203
  md.push("", ...head);
@@ -1164,8 +1214,8 @@ function renderGroupsMd(md, groups, rows = []) {
1164
1214
  ? `**Not tested on goods: ${count(ng)}.** No goods words were given, so the goods test could not place them.`
1165
1215
  : `**Goods the test could not read: ${count(ng)}.**`;
1166
1216
  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.`, "");
1217
+ ? `${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")}`
1218
+ : `${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
1219
  md.push(...head);
1170
1220
  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
1221
  }
@@ -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
@@ -624,14 +624,12 @@ export function quoteJoins(quote, candidates) {
624
624
  const POINT_DIRECTIVE = [
625
625
  "On every row the tool's answer marks as owing one, give `segment_index` — the NUMBER of the passage",
626
626
  "you relied on, from the numbered passages of the receipt you ruled on.",
627
- "And copy `fragment` — a few characters taken EXACTLY out of that same passage, in whatever script it",
628
- "is written in.",
629
- "The number says WHICH passage; the characters show you read it. The driver copies the passage into the",
627
+ "The driver copies the passage into the",
630
628
  "record for you, so never reproduce it, never translate it, and never join two passages together.",
631
- "If the passage is not in your language, copy its characters as they are written — a translation or a",
632
- "paraphrase is not a copy and will not be accepted.",
633
- "A row owing these whose fragment does not appear in the passage it names is not counted as ruled, and",
634
- "this stage fails while any remain.",
629
+ "`fragment` is OPTIONAL, and never refused: the driver extracts the quote it stores from `segment_index`",
630
+ "ALONE, so pointing at the passage is the whole obligation. If a few characters of that passage come",
631
+ "easily, send them and they are recorded as a transcription check; if the script is one you cannot",
632
+ "reproduce, omit it and nothing is lost.",
635
633
  ];
636
634
 
637
635
  // The anchor's own bar. SHORT ON PURPOSE — this is the piece a model must reproduce exactly, and every
@@ -1412,7 +1410,7 @@ export function renderConnotationObligations(ob, { ledgerPath = "your half ledge
1412
1410
  "your `ruling` and your `note`.",
1413
1411
  "Rows are validated as they arrive: rows that validate are KEPT even when others in the same call",
1414
1412
  "are refused, and the tool's answer names every obligation still outstanding and which of them owe",
1415
- "a `segment_index` and `fragment`. Your rulings ACCUMULATE across calls and across attempts — nothing",
1413
+ "a `segment_index`. Your rulings ACCUMULATE across calls and across attempts — nothing",
1416
1414
  "already recorded",
1417
1415
  "needs re-sending. Send the rest in a further call until the answer reports nothing outstanding.",
1418
1416
  "",
@@ -1500,7 +1498,7 @@ export function meaningSweepReceiptsInstruction({
1500
1498
  "A row listed with exactly ONE receipt is already resolved: it takes no",
1501
1499
  "`receipt_index` — give that row only your `ruling` and `note`.",
1502
1500
  "Rows that validate are KEPT even when others in the same call are refused, and the tool's answer",
1503
- "names every obligation still outstanding and which of them owe a `segment_index` and `fragment`.",
1501
+ "names every obligation still outstanding and which of them owe a `segment_index`.",
1504
1502
  "Your rulings ACCUMULATE",
1505
1503
  "across calls and across attempts: a row you have ruled on stays ruled, so nothing already answered",
1506
1504
  "needs redoing — send only what the answer reports outstanding.",