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
@@ -157,7 +157,7 @@ export const E3_BACKLOG = [
157
157
  },
158
158
  // ── DISCHARGED 2026-08-17 by, and the row said this could not happen ──────────────────────────
159
159
  //
160
- // It read: stage "blind-frame", where "driver/stages.mjs:974", evidence "Emit the STRUCTURED model
160
+ // It read: stage "blind-frame", where "driver/stages.mjs:981", evidence "Emit the STRUCTURED model
161
161
  // (dictated keys + closed enums per the skill). It is your ONLY output file — do NOT write a prose
162
162
  // companion.", reparsedBy "verify.mjs validators.blindFrame …", removedByMove **"NOTHING ON THE PLAN
163
163
  // REMOVES THIS"**.
@@ -171,7 +171,7 @@ export const E3_BACKLOG = [
171
171
  {
172
172
  stage: "common-law",
173
173
  kind: "dictated-line-shape",
174
- where: "driver/stages.mjs:1085 (emitted at 1032, 1061, 1147, 1276)",
174
+ where: "driver/stages.mjs:1102 (emitted at 1891, 1920, 2100, 2320)",
175
175
  surface: "stage-message",
176
176
  evidence: "CROSS-CHECK HAND-OFF: … record it on its OWN line in EXACTLY the form \"CROSS-CHECK REQUIRED: <what> — <why>\" (that exact prefix; an em-dash between what and why; name the mark in CAPS in <what>). The driver parses ONLY this exact line shape…",
177
177
  reparsedBy: "driver/doubt-ledger.mjs:183 CROSS_CHECK_RE = /^(?:[-*]\\s+)?CROSS-CHECK REQUIRED:\\s*(.+?)\\s+—\\s+(.+?)\\s*$/ → mintCrossCheckDoubts",
@@ -218,7 +218,7 @@ export const E3_BACKLOG = [
218
218
  {
219
219
  stage: "common-law",
220
220
  kind: "dictated-line-shape",
221
- where: "driver/skills/clearance-common-law/SKILL.md:192 (restated at driver/skills/clearance-search/synthesis-rules.md:394)",
221
+ where: "driver/skills/clearance-common-law/SKILL.md:192 (restated at driver/skills/clearance-search/synthesis-rules.md:391)",
222
222
  surface: "skill-file",
223
223
  evidence: "A clean PR/connotation row MUST cite its search — add a `**Connotation-search source:** <URL | \"perplexity_research — no result\">` line.",
224
224
  reparsedBy: "driver/connotation-search.mjs — validators.commonLaw rejects a clean claim with no such line (connotation_search_missing); the hint is re-dictated by correctionHint() in gateway.mjs",
@@ -227,7 +227,7 @@ export const E3_BACKLOG = [
227
227
  {
228
228
  stage: "common-law",
229
229
  kind: "dictated-line-shape",
230
- where: "driver/stages.mjs:1055 and driver/stages.mjs:1060 (the no-grid-spec legacy branch)",
230
+ where: "driver/stages.mjs:1063 and driver/stages.mjs:1068 (the no-grid-spec legacy branch)",
231
231
  surface: "stage-message",
232
232
  evidence: "GRID KEYS (the validator checks EXACTLY these N terms — use each VERBATIM as its Negative-results matrix key…) … MACHINE RECEIPTS (MANDATORY): save the grid call's stdout JSON VERBATIM … the single stdout object, or a JSON ARRAY of the per-batch stdout objects in batch order when batched.",
233
233
  reparsedBy: "driver/common-law-receipts.mjs — the receipts gate's exact identity join on the dictated key list; validators.commonLaw grid-completeness arm",
@@ -256,7 +256,7 @@ export const E3_BACKLOG = [
256
256
  {
257
257
  stage: "register-unit",
258
258
  kind: "literal-json-skeleton",
259
- where: "driver/stages.mjs:2286 (the non-supplemental-lane branch)",
259
+ where: "driver/stages.mjs:2307 (the non-supplemental-lane branch)",
260
260
  surface: "stage-message",
261
261
  evidence: "BAND ARTIFACT (MANDATORY): ALSO write the COMPLETE NAMED BAND for this axis to <path> — a JSON ARRAY, one block per register_enumerate / count-probe call, in the named-band contract: {\"state\":\"enumerated\",\"query\":\"<what was searched>\",\"total_hits\":N,\"records\":[{record_id, mark_text, classes, status,",
262
262
  reparsedBy: "driver/named-band.mjs parseNamedBand / bandRecords / bandCrowds / mergeNamedBands (named in driver/skills/clearance-register/unit.md:60-62); validators.registerUnit",
@@ -265,7 +265,7 @@ export const E3_BACKLOG = [
265
265
  {
266
266
  stage: "register-unit",
267
267
  kind: "exactly-these-keys",
268
- where: "driver/stages.mjs:2249",
268
+ where: "driver/stages.mjs:2270",
269
269
  surface: "stage-message",
270
270
  evidence: "Every block you append MUST carry \"state\":\"enumerated\" (ONLY if you paged it to has_more:false) or \"state\":\"incomplete\" — EXACTLY those two strings; there is no \"verified\"/\"checked\"/\"complete\"/\"clean\" state, and any other value fails the stage.",
271
271
  reparsedBy: "driver/named-band.mjs parseNamedBand (off-enum state fails validators.registerUnit)",
@@ -283,7 +283,7 @@ export const E3_BACKLOG = [
283
283
  {
284
284
  stage: "register-unit",
285
285
  kind: "exactly-these-keys",
286
- where: "driver/stages.mjs:4241 (the frame-reopen / scoped-retry message builder). A second number stood here and had been stale for some time: it pointed at a contract-element description rather than a builder, at its old line and at every mechanical shift of it. Two candidate builders sit beside 4157 and picking one would be a guess, so the wrong pointer is removed rather than moved a third time — one accurate citation beats one accurate and one invented.",
286
+ where: "driver/stages.mjs:4248 (the frame-reopen / scoped-retry message builder). A second number stood here and had been stale for some time: it pointed at a contract-element description rather than a builder, at its old line and at every mechanical shift of it. Two candidate builders sit beside 4164 and picking one would be a guess, so the wrong pointer is removed rather than moved a third time — one accurate citation beats one accurate and one invented.",
287
287
  surface: "stage-message",
288
288
  evidence: "Every block you append MUST carry \"state\":\"enumerated\" (ONLY if paged to has_more:false) or \"state\":\"incomplete\" — EXACTLY those two strings … (re-dispatch builders, which REPLACE def.message)",
289
289
  reparsedBy: "driver/named-band.mjs parseNamedBand. Scope warning: these builders replace def.message on every escalation / envelope-close / frame-reopen dispatch, so an E3 lint that walks STAGES[*].message only never sees them",
@@ -292,7 +292,7 @@ export const E3_BACKLOG = [
292
292
  {
293
293
  stage: "placement-inquiry",
294
294
  kind: "literal-json-skeleton",
295
- where: "driver/stages.mjs:2351-2353",
295
+ where: "driver/stages.mjs:2359-2353",
296
296
  surface: "stage-message",
297
297
  evidence: "PLACEMENT FORM (MANDATORY): record every placement in <path> — {\"rows\":[…]} … · A REGISTER candidate: {\"select\":\"<one record URI it holds>\",\"tier\":\"…\",\"reason\":\"…\"} (+ optional \"borderline\":true) … · A COMMON-LAW candidate …: {\"kind\":\"seat\",\"mark\",\"owner\",\"jurisdiction\",\"records\":[],\"tier\",\"reason\"}",
298
298
  reparsedBy: "driver/placement-form.mjs (SELECT_ROW_FIELDS at placement-form.mjs:93, the seat-row contract SEAT_ROW_CONTRACT, formRowKey/rowIsSettled/renderEntry) via validators.placement. The same field list is ALSO carried in the driver-written form's own seat_row_contract, so the shape exists twice",
@@ -301,7 +301,7 @@ export const E3_BACKLOG = [
301
301
  {
302
302
  stage: "placement-inquiry",
303
303
  kind: "exactly-these-keys",
304
- where: "driver/stages.mjs:2447",
304
+ where: "driver/stages.mjs:2468",
305
305
  surface: "stage-message",
306
306
  evidence: "· tier EXACTLY one of headline-candidate / sheet-2 / watchlist-annex / out-of-scope-filtered.",
307
307
  reparsedBy: "driver/placement-form.mjs / driver/placement-model.mjs via validators.placement",
@@ -347,7 +347,7 @@ export const E3_BACKLOG = [
347
347
  // (coverage-form.mjs coverageFormBrief) and the tool schema, with every row receiver-validated at
348
348
  // call time. Same dictated vocabulary, same authored site, new route — this row's evidence follows
349
349
  // the dictation so the row keeps describing something that exists.
350
- where: "driver/stages.mjs:2656 (the digest message) + driver/coverage-form.mjs (coverageFormBrief — the dispatch block carrying the enum and the row shape)",
350
+ where: "driver/stages.mjs:2678 (the digest message) + driver/coverage-form.mjs (coverageFormBrief — the dispatch block carrying the enum and the row shape)",
351
351
  surface: "stage-message",
352
352
  evidence: "Record a \"status\" and a \"reason\" on EVERY row ONLY by calling the … tool — the driver validates each row as it arrives, holds the record itself, and renders both the ## Coverage ledger table and the coverage JSON from it",
353
353
  reparsedBy: "driver/coverage-call.mjs validateCoverageCall (receiver-validated at call time, the same predicates the gate judges with) + driver/coverage-form.mjs rowIsSettled via validators.registerFindings over the _driver/ accumulator",
@@ -430,7 +430,7 @@ export const E3_BACKLOG = [
430
430
  {
431
431
  stage: "frame-diff",
432
432
  kind: "exactly-these-keys",
433
- where: "driver/stages.mjs:2836",
433
+ where: "driver/stages.mjs:2850",
434
434
  surface: "stage-message",
435
435
  evidence: "For each blind-model variant / field / source the run did NOT scope or search, emit one directive {layer, item, observation, severity} … severity = dominant-element (the omission is ON the spine) | material (a real omission worth a targeted sweep) | minor (already covered, or presentation only).",
436
436
  reparsedBy: "driver/verify.mjs validators.frameDiff + driver/pipeline.mjs runSupplementalSweeps (the parser REFUSES a firing variant directive that dictates nothing dispatchable)",
@@ -454,7 +454,7 @@ export const E3_BACKLOG = [
454
454
  {
455
455
  stage: "synthesis",
456
456
  kind: "literal-json-skeleton",
457
- where: "driver/stages.mjs:3218",
457
+ where: "driver/stages.mjs:3232",
458
458
  surface: "stage-message",
459
459
  evidence: "MACHINE FINDINGS (MANDATORY): … a JSON OBJECT {\"schema_version\":<FINDINGS_SCHEMA_VERSION>,\"rated_under_framework\":\"…\",\"findings\":[...],\"coverage\":[...],\"context_notes\":[...],\"actions\":[...],\"ask_answers\":[...]} … Each finding object has EXACTLY these keys: {\"ordinal\",\"mark\",\"owner\",\"band\",\"net\",\"bor",
460
460
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson via validators.narrative",
@@ -468,7 +468,7 @@ export const E3_BACKLOG = [
468
468
  {
469
469
  stage: "synthesis",
470
470
  kind: "exactly-these-keys",
471
- where: "driver/stages.mjs:3254",
471
+ where: "driver/stages.mjs:3265",
472
472
  surface: "stage-message",
473
473
  evidence: "- off_field_ground (MANDATORY on every off-field finding, FORBIDDEN on every other disposition): EXACTLY one bare token of: ${OFF_FIELD_GROUNDS.join(\" / \")}",
474
474
  reparsedBy: "driver/findings-model.mjs validateOffFieldGround — the enum is imported from findings-model.mjs and interpolated back into the prompt, so code already holds the list it asks the model to type",
@@ -477,7 +477,7 @@ export const E3_BACKLOG = [
477
477
  {
478
478
  stage: "synthesis",
479
479
  kind: "literal-json-skeleton",
480
- where: "driver/stages.mjs:3255",
480
+ where: "driver/stages.mjs:3266",
481
481
  surface: "stage-message",
482
482
  evidence: "- manageable …: {\"category\":\"<EXACTLY one of large-competitor / commercial-partner / troll / well-known-enforcer>\",\"reason\":\"<one-two lines…>\"}",
483
483
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson",
@@ -486,7 +486,7 @@ export const E3_BACKLOG = [
486
486
  {
487
487
  stage: "synthesis",
488
488
  kind: "literal-json-skeleton",
489
- where: "driver/stages.mjs:3271",
489
+ where: "driver/stages.mjs:3282",
490
490
  surface: "stage-message",
491
491
  evidence: "- meters: {\"mark_similarity\":{...},\"goods_proximity\":{...},\"use\":{...},\"enforcer\":{...}} — all four present, each {\"token\",\"basis\",\"source\"}. … mark_similarity = high | medium | low. goods_proximity = high | medium | low. enforcer = high | medium | low | unknown. use = confirmed | not-confirmed | un",
492
492
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson; driver/verify.mjs:1171 checkFindingsSibling gates meters.*.source; finding_basis_source_missing",
@@ -495,7 +495,7 @@ export const E3_BACKLOG = [
495
495
  {
496
496
  stage: "synthesis",
497
497
  kind: "literal-json-skeleton",
498
- where: "driver/stages.mjs:3272",
498
+ where: "driver/stages.mjs:3283",
499
499
  surface: "stage-message",
500
500
  evidence: "- quadrant: {\"x\",\"y\"} numbers in [0,1]. x = goods/services proximity (0 = distant, 1 = identical). y = mark similarity (0 = distinct, 1 = identical).",
501
501
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson",
@@ -504,7 +504,7 @@ export const E3_BACKLOG = [
504
504
  {
505
505
  stage: "synthesis",
506
506
  kind: "literal-json-skeleton",
507
- where: "driver/stages.mjs:3273",
507
+ where: "driver/stages.mjs:3284",
508
508
  surface: "stage-message",
509
509
  evidence: "- source: {\"source_type\",\"resolved_link\"}. source_type EXACTLY one of: register-vendor / register-euipo / common-law-marketplace / common-law-web / case-law",
510
510
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson",
@@ -513,7 +513,7 @@ export const E3_BACKLOG = [
513
513
  {
514
514
  stage: "synthesis",
515
515
  kind: "exactly-these-keys",
516
- where: "driver/stages.mjs:3274",
516
+ where: "driver/stages.mjs:3285",
517
517
  surface: "stage-message",
518
518
  evidence: "coverage[]: ONE object per coverage AREA, EXACTLY {\"area\",\"state\",\"note\"}. … state EXACTLY one of: confirmed-clean / coverage-limited / open / not-searched / note.",
519
519
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson; the render owns the coverage panel from these typed states",
@@ -522,7 +522,7 @@ export const E3_BACKLOG = [
522
522
  {
523
523
  stage: "synthesis",
524
524
  kind: "literal-json-skeleton",
525
- where: "driver/stages.mjs:3283",
525
+ where: "driver/stages.mjs:3294",
526
526
  surface: "stage-message",
527
527
  evidence: "use_check = {\"source\",\"quality\"}: … quality: OPTIONAL, EXACTLY one of owner-site / independent / register-mirror … own_rights = {\"source\"}",
528
528
  reparsedBy: "driver/verify.mjs checkFindingsSibling (finding_use_check_missing); driver/own-rights.mjs:19-22",
@@ -531,7 +531,7 @@ export const E3_BACKLOG = [
531
531
  {
532
532
  stage: "synthesis",
533
533
  kind: "literal-json-skeleton",
534
- where: "driver/stages.mjs:3340",
534
+ where: "driver/stages.mjs:3351",
535
535
  surface: "stage-message",
536
536
  evidence: "MARK ASSESSMENT … STRUCTURED FORM …: either field may instead be an OBJECT {\"read\":\"…\",\"spectrum\":\"…\",\"per_class\":[{\"class\":\"5\",\"note\":\"…\"}],\"per_market\":[{\"market\":\"CN\",\"note\":\"…\"}],\"counter_registrations\":[{\"mark\":\"…\",\"uri\":\"/mark/…\",\"note\":\"…\"}],\"acquired\":\"<optional>\",\"note\":\"<optional residual>",
537
537
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson; the report collapses the rows behind toggles and the audit workbook renders them",
@@ -540,7 +540,7 @@ export const E3_BACKLOG = [
540
540
  {
541
541
  stage: "synthesis",
542
542
  kind: "literal-json-skeleton",
543
- where: "driver/stages.mjs:3346",
543
+ where: "driver/stages.mjs:3357",
544
544
  surface: "stage-message",
545
545
  evidence: "FOUR ANSWERS …: \"four_answers\": {\"third_party_rights\":{...},\"objection_likelihood\":{...},\"registrability\":{...},\"client_enforceability\":{...}} … Each answer … is {\"read\":\"…\",\"token\":\"…\",\"basis\":\"…\",\"ordinals\":[…]}. Tokens (closed enums …): third_party_rights = strong|moderate|weak; objection_likelih",
546
546
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson",
@@ -549,7 +549,7 @@ export const E3_BACKLOG = [
549
549
  {
550
550
  stage: "synthesis",
551
551
  kind: "literal-json-skeleton",
552
- where: "driver/stages.mjs:3223",
552
+ where: "driver/stages.mjs:3231",
553
553
  surface: "stage-message",
554
554
  evidence: "ACTIONS REGISTER …: emit \"actions\": [...] — ONE object per forward step …, each EXACTLY {\"id\",\"kind\",\"text\",\"ordinals\"} plus an OPTIONAL \"deadline\" and an OPTIONAL \"condition\". … kind: EXACTLY one of — consent / coexistence-agreement / territorial-delimitation / goods-amendment / mark-modification /",
555
555
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson; pipeline applyCoverageFloor legalActions arm derives the delivered disposition from the closed kind enum",
@@ -558,7 +558,7 @@ export const E3_BACKLOG = [
558
558
  {
559
559
  stage: "synthesis",
560
560
  kind: "exactly-these-keys",
561
- where: "driver/stages.mjs:3353",
561
+ where: "driver/stages.mjs:3364",
562
562
  surface: "stage-message",
563
563
  evidence: "COVERAGE JUDGMENT …: emit \"coverage_judgment\": {\"sufficient\":<bool>, \"reason\":\"<one line…>\"} — EXACTLY those two keys. Do NOT emit \"rows\": the driver writes that register itself … anything you type there is replaced wholesale.",
564
564
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson. The \"Do NOT emit rows\" clause is the purest E3 case in the tree — the prompt names a field, dictates its shape and states in the same breath that code overwrites it",
@@ -567,7 +567,7 @@ export const E3_BACKLOG = [
567
567
  {
568
568
  stage: "synthesis",
569
569
  kind: "literal-json-skeleton",
570
- where: "driver/stages.mjs:3196",
570
+ where: "driver/stages.mjs:3210",
571
571
  surface: "stage-message",
572
572
  // RE-QUOTED, NOT PARKED. The writer's conversion reworded this dictation — the
573
573
  // ask answers ride the findings RECORD now and the driver renders the labelled line into both the
@@ -591,7 +591,7 @@ export const E3_BACKLOG = [
591
591
  {
592
592
  stage: "synthesis",
593
593
  kind: "dictated-line-shape",
594
- where: "driver/stages.mjs:3115 (restated at driver/skills/clearance-search/synthesis-rules.md:428)",
594
+ where: "driver/stages.mjs:3129 (restated at driver/skills/clearance-search/synthesis-rules.md:425)",
595
595
  surface: "stage-message",
596
596
  evidence: "END that finding's actual-use line with a literal \"- **Use-check source:** <result URL | \"perplexity_research — no result\">\" line",
597
597
  reparsedBy: "driver/verify.mjs validators.narrative (spec-11 hard reject); the repair hint re-dictates the literal in correctionHint() in gateway.mjs",
@@ -600,7 +600,7 @@ export const E3_BACKLOG = [
600
600
  {
601
601
  stage: "synthesis",
602
602
  kind: "dictated-line-shape",
603
- where: "driver/stages.mjs:3128 (restated at driver/skills/clearance-search/synthesis-rules.md:475)",
603
+ where: "driver/stages.mjs:3142 (restated at driver/skills/clearance-search/synthesis-rules.md:472)",
604
604
  surface: "stage-message",
605
605
  evidence: "END that finding's reasoning with a literal \"- **Own-rights source:** <record URI(s) | \"no applicant-owned registrations in the searched register material\">\" line",
606
606
  reparsedBy: "driver/own-rights.mjs, in its header — \"This module only requires the 'Own-rights source:' line to exist\"; repair hint in correctionHint() in gateway.mjs (the `own_rights_missing` branch; re-verified 2026-08-29 — the old :1736 predated this branch and pointed into the A4 repeat-signature block)",
@@ -609,7 +609,7 @@ export const E3_BACKLOG = [
609
609
  {
610
610
  stage: "synthesis",
611
611
  kind: "exactly-these-keys",
612
- where: "driver/stages.mjs:3332",
612
+ where: "driver/stages.mjs:3343",
613
613
  surface: "stage-message",
614
614
  evidence: "add it to the top-level \"context_notes\" array — each object EXACTLY {\"type\":\"famous-neighbour-ungrounded\",\"mark\",\"owner\",\"context\"}",
615
615
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson",
@@ -618,7 +618,7 @@ export const E3_BACKLOG = [
618
618
  {
619
619
  stage: "synthesis",
620
620
  kind: "literal-json-skeleton",
621
- where: "driver/stages.mjs:3220",
621
+ where: "driver/stages.mjs:3234",
622
622
  surface: "stage-message",
623
623
  evidence: "- owner: {\"name\",\"country\",\"registrations\":[...]}. … Each registration: {\"uri\", optionally \"classes\":[\"9\",\"41\"],\"status\",\"filed\",\"expiry\",\"jurisdiction\"}. The \"uri\" is the ONLY field that matters: the driver BINDS classes/status/filed/expiry/jurisdiction AND the owner name from the FETCHED record ke",
624
624
  reparsedBy: "driver/findings-model.mjs:848 parseFindingsJson + the record-binding join. Six of the seven keys are stated in the prompt and overwritten by code in the same sentence",
@@ -627,7 +627,7 @@ export const E3_BACKLOG = [
627
627
  {
628
628
  stage: "case-law",
629
629
  kind: "literal-json-skeleton",
630
- where: "driver/stages.mjs:3502",
630
+ where: "driver/stages.mjs:3513",
631
631
  surface: "stage-message",
632
632
  evidence: "ALSO write the RETRIEVAL RECORD to <path> — a JSON OBJECT with EXACTLY these keys: {\"schema_version\":2,\"queries\":[{\"query\":\"<the search you dispatched, verbatim>\",\"jurisdiction\":\"…\",\"results\":<how many hits it returned, or null when the query never reached a source — never 0 for a query you could not send>}, …],\"citations\":[{\"proceeding\":\"…\",\"forum\":\"…\",\"jurisdiction\":\"…\",\"decided\":\"…\"",
633
633
  reparsedBy: "driver/verify.mjs validators.caseLaw — the ledger arm, armed by the stage-contract marker `citations` (the case-law stage's `contract: { citations: 1 }` in stages.mjs)",
@@ -636,7 +636,7 @@ export const E3_BACKLOG = [
636
636
  {
637
637
  stage: "case-law",
638
638
  kind: "dictated-line-shape",
639
- where: "driver/stages.mjs:3510",
639
+ where: "driver/stages.mjs:3521",
640
640
  surface: "stage-message",
641
641
  evidence: "EVERY \"Grounded profile\" section MUST start its body with the line \"- ord: <N>\" naming which finding it grounds (use the ordinal from this list; a profile that grounds no listed finding omits the line)",
642
642
  reparsedBy: "driver/publish/parse.mjs:339, parseCaseLawProfiles() in parse.mjs (\"the optional '- ord: <N>' first body line … gives an EXACT join\"); driver/findings-model.mjs:274 /^-\\s*ord:\\s*(\\d+)\\s*$/m; driver/publish/index.mjs publishReport (joinCaseLawProfiles)",
@@ -670,7 +670,7 @@ export const E3_BACKLOG = [
670
670
  {
671
671
  stage: "narrative-refutation",
672
672
  kind: "dictated-line-shape",
673
- where: "driver/stages.mjs:3644",
673
+ where: "driver/stages.mjs:4797",
674
674
  surface: "stage-message",
675
675
  // RE-QUOTED BY CONVERSION 9. The LINE-TOKEN half is gone — no "anywhere on the line", no "[kind: …]
676
676
  // token", no "a line with no token is treated as fact", because a kind is a typed field now and an
@@ -704,7 +704,7 @@ export const E3_BACKLOG = [
704
704
  {
705
705
  stage: "narrative-refutation",
706
706
  kind: "dictated-line-shape",
707
- where: "driver/stages.mjs:1871 and driver/skills/narrative-refutation/SKILL.md:41-50",
707
+ where: "driver/stages.mjs:1879 and driver/skills/narrative-refutation/SKILL.md:41-50",
708
708
  // RE-QUOTED 2026-08-16. The dictated SENTENCE survives in narrative-refutation's SKILL.md — it is the
709
709
  // `evidence` below, verbatim. What changed is that the four kinds moved from an inline list after the
710
710
  // colon onto their own `- [kind: …]` bullet lines. Measured, not assumed: the old anchor matched 59 of
@@ -769,7 +769,7 @@ export const E3_BACKLOG = [
769
769
  {
770
770
  stage: "report-card",
771
771
  kind: "literal-json-skeleton",
772
- where: "driver/stages.mjs:3953",
772
+ where: "driver/stages.mjs:3960",
773
773
  surface: "stage-message",
774
774
  evidence: "The finding's OWN record — the ONLY source for this card …:\\n```json\\n<JSON.stringify(finding, null, 2)>\\n```",
775
775
  reparsedBy: "none — this is the INPUT side, and that is why it belongs in the survey: a full JSON object rendered into the prompt is exactly the mechanism #850 proves produced R-RECEIPT (the model pattern-matches a shown shape). E3's clause 1 as written (\"a code fence or inline example showing the exact object shape the model must emit\") does not reach an injected record, so the lint needs an explicit rule for shown-but-not-owed structure",
@@ -859,7 +859,7 @@ export const E3_EVIDENCE_UNRESOLVED = [
859
859
  // absorbed. It was the narrative "## Answers to your instructions" row deleted above: its dictation is
860
860
  // gone, so there is no un-anchorable quote left to be unresolved. The one that REMAINS is a live row
861
861
  // whose quote spans a template line. Measured before deleting, not predicted — two rows shared
862
- // `where: "driver/stages.mjs:3075"`, so which one occupied this slot could not be read off the strings.
862
+ // `where: "driver/stages.mjs:3083"`, so which one occupied this slot could not be read off the strings.
863
863
  "synthesis|driver/stages.mjs",
864
864
  ];
865
865
 
@@ -36,9 +36,9 @@
36
36
  "dictated-line-shape": 1
37
37
  },
38
38
  "register-digest": {
39
- "literal-json-skeleton": 1,
39
+ "literal-json-skeleton": 0,
40
40
  "exactly-these-keys": 0,
41
- "dictated-line-shape": 2
41
+ "dictated-line-shape": 1
42
42
  },
43
43
  "skeptic": {
44
44
  "literal-json-skeleton": 0,
@@ -120,7 +120,7 @@
120
120
  "skills/clearance-register/SKILL.md": {
121
121
  "literal-json-skeleton": 0,
122
122
  "exactly-these-keys": 0,
123
- "dictated-line-shape": 4
123
+ "dictated-line-shape": 3
124
124
  },
125
125
  "skills/clearance-register/unit.md": {
126
126
  "literal-json-skeleton": 5,
@@ -189,7 +189,7 @@
189
189
  }
190
190
  },
191
191
  "totals": {
192
- "stages": 45,
193
- "files": 64
192
+ "stages": 43,
193
+ "files": 63
194
194
  }
195
195
  }
@@ -474,21 +474,21 @@ export const INNER_CODES = Object.freeze([
474
474
  // `no_recorded_queries`, and the `connotation_` family row (dynamic D1) is declared against exactly
475
475
  // that list. The four call codes are folded by a template — `connotation_${callFail.reason}` at
476
476
  // verify.mjs — so they are namespaced by construction rather than one branch at a time.
477
- { code: "call_never_made", mints: ["driver/connotation-search.mjs:1981"], rollsUpTo: ["connotation_call_never_made"],
477
+ { code: "call_never_made", mints: ["driver/connotation-search.mjs:1979"], rollsUpTo: ["connotation_call_never_made"],
478
478
  why: "CALL_AUDIT_ROWS. The typed transport's four call states, handed in by disposition-call-audit.mjs and namespaced at verify.mjs." },
479
- { code: "call_truncated", mints: ["driver/connotation-search.mjs:1982"], rollsUpTo: ["connotation_call_truncated"],
479
+ { code: "call_truncated", mints: ["driver/connotation-search.mjs:1980"], rollsUpTo: ["connotation_call_truncated"],
480
480
  why: "As call_never_made — same table, same projection." },
481
- { code: "call_schema_violation", mints: ["driver/connotation-search.mjs:1983"], rollsUpTo: ["connotation_call_schema_violation"],
481
+ { code: "call_schema_violation", mints: ["driver/connotation-search.mjs:1981"], rollsUpTo: ["connotation_call_schema_violation"],
482
482
  why: "As call_never_made — same table, same projection." },
483
- { code: "call_partial", mints: ["driver/connotation-search.mjs:1984"], rollsUpTo: ["connotation_call_partial"],
483
+ { code: "call_partial", mints: ["driver/connotation-search.mjs:1982"], rollsUpTo: ["connotation_call_partial"],
484
484
  why: "As call_never_made — same table, same projection. This is the pair #1211 cites as its worked example: the composite is covered, the bare form reaches no stage." },
485
- { code: "quote_unbound", mints: ["driver/connotation-search.mjs:2115"], rollsUpTo: ["connotation_quote_unbound"],
485
+ { code: "quote_unbound", mints: ["driver/connotation-search.mjs:2113"], rollsUpTo: ["connotation_quote_unbound"],
486
486
  why: "The ruled-but-unbound row. Projected as `unbound` in connotationDispositionFail() in verify.mjs, and reported only once nothing is unruled." },
487
- { code: "token_absent", mints: ["driver/connotation-search.mjs:2142"], rollsUpTo: ["connotation_token_absent"],
487
+ { code: "token_absent", mints: ["driver/connotation-search.mjs:2140"], rollsUpTo: ["connotation_token_absent"],
488
488
  why: "#592 split this out of no_ruling. Row-level only — PROGRESS_TOKENS declared in repairs.mjs says so in as many words: `never a top-level token`." },
489
- { code: "cite_absent", mints: ["driver/connotation-search.mjs:2151"], rollsUpTo: ["connotation_cite_absent"],
489
+ { code: "cite_absent", mints: ["driver/connotation-search.mjs:2149"], rollsUpTo: ["connotation_cite_absent"],
490
490
  why: "#592, as token_absent. PROGRESS_TOKENS declared in repairs.mjs: `row-level only — never a top-level token`." },
491
- { code: "no_ruling", mints: ["driver/connotation-search.mjs:2158"], rollsUpTo: ["connotation_no_ruling"],
491
+ { code: "no_ruling", mints: ["driver/connotation-search.mjs:2156"], rollsUpTo: ["connotation_no_ruling"],
492
492
  why: "The residual the other two split off from, and it has representatives — see the mint site. Declared as a corrective token at repairs.mjs:308 and in stages.mjs's E1 rows, always namespaced." },
493
493
  // THE ONE THAT IS RENAMED, NOT NAMESPACED — and it is the reason this table stores the composite as
494
494
  // data rather than deriving it. Every derivation anyone would write is `connotation_` + the code, and
@@ -496,7 +496,7 @@ export const INNER_CODES = Object.freeze([
496
496
  // verify.mjs's block headed "the meaning-sweep failure tokens" states the ruling: a sweep that did not
497
497
  // RUN is a canonical-only decision with its own token and its own remedy, so the projector,
498
498
  // connotationDispositionFail() in verify.mjs, deliberately does not handle it.
499
- { code: "no_recorded_queries", mints: ["driver/connotation-search.mjs:1999"], rollsUpTo: ["connotation_search_missing"],
499
+ { code: "no_recorded_queries", mints: ["driver/connotation-search.mjs:1997"], rollsUpTo: ["connotation_search_missing"],
500
500
  why: "RENAMED, not namespaced: verify.mjs emits `connotation_search_missing`. It is excluded from CONNOTATION_FORM_REASONS at connotation-search.mjs for exactly this reason." },
501
501
 
502
502
  // ── case-law-ledger.mjs — ONE token, the codes as census payload ─────────────────────────────────
@@ -530,9 +530,9 @@ export const INNER_CODES = Object.freeze([
530
530
  // ONE CODE, TWO TOKENS, and the split is by `cause` rather than by code: verify.mjs peels the
531
531
  // `axis_invalid` cause into its own family before counting the rest. A ruling naming only
532
532
  // `coverage_no_status` would be true of most `no_status` records and false of the ones that matter most.
533
- { code: "no_status", mints: ["driver/coverage-form.mjs:894"], rollsUpTo: ["coverage_no_status", "coverage_form_axis_invalid"],
533
+ { code: "no_status", mints: ["driver/coverage-form.mjs:897"], rollsUpTo: ["coverage_no_status", "coverage_form_axis_invalid"],
534
534
  why: "Two composites, split on the record's `cause`: coverageFormFail() in verify.mjs for cause `axis_invalid`, and the same function for the rest. NOT the `@returns` annotation of findCoverageFormViolations() in coverage-form.mjs — that is the annotation, not the mint." },
535
- { code: "engine_vocabulary", mints: ["driver/coverage-form.mjs:884"], rollsUpTo: ["coverage_form_engine_vocabulary"],
535
+ { code: "engine_vocabulary", mints: ["driver/coverage-form.mjs:887"], rollsUpTo: ["coverage_form_engine_vocabulary"],
536
536
  why: "#669 — the seat wrote an engine token into the `reason` sentence that reaches the reader's page. Checked on settled rows too, because a row the seat considers finished is exactly the one whose sentence gets printed. Namespaced by coverageFormFail() in verify.mjs; the bare code names a row, never a stage." },
537
537
 
538
538
  // ── ONE CODE, TWO MODULES ────────────────────────────────────────────────────────────────────────
@@ -544,7 +544,7 @@ export const INNER_CODES = Object.freeze([
544
544
  // failure tokens" already states the rule this row records: `coverage_form_damaged`, never a bare
545
545
  // `form_damaged`.
546
546
  { code: "form_damaged",
547
- mints: ["driver/connotation-search.mjs:2004", "driver/connotation-search.mjs:2080", "driver/coverage-form.mjs:876"],
547
+ mints: ["driver/connotation-search.mjs:2002", "driver/connotation-search.mjs:2078", "driver/coverage-form.mjs:879"],
548
548
  rollsUpTo: ["connotation_form_damaged", "coverage_form_damaged"],
549
549
  why: "Minted in two lanes and namespaced per lane: connotationDispositionFail() in verify.mjs for the meaning sweep, coverageFormFail() in verify.mjs for the register digest. The namespacing is what keeps them apart — see verify.mjs's block headed \"the register coverage-form failure tokens\"." },
550
550
  ]);
@@ -0,0 +1,113 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
+ // corrections-delivered.mjs — whether each reviewer correction is still in the record the report is built from.
4
+ //
5
+ // corrections-applied.json records what the corrective pass changed, flag by flag, compared at the pass. It
6
+ // cannot say whether the change is still there when the report is assembled: later saves re-send the record
7
+ // (the delivery check's redo, the stale repair, a re-emit), the delivery seam folds it, and the report is built
8
+ // after all of them. This answers that question for the typed fields a report prints from the record — a
9
+ // finding's sentence (`net`) and its two positions, a coverage row's note, an action's text, and the mark
10
+ // assessment's distinctiveness and connotation — in two steps:
11
+ //
12
+ // correctionTargets at the corrective pass, where the records before and after it are in hand: what each
13
+ // flag changed in those fields, with the text the pass left (the basis);
14
+ // deliveredOutcomes after an assembly: whether the record the report was built from still carries it.
15
+ //
16
+ // Each flag's outcome:
17
+ // delivered every field it changed reads, in the delivered record, as the pass left it;
18
+ // not-found at least one does not: a later save or the fold did not carry it to the report;
19
+ // not-applicable it changed none of these fields — a narrative or structure correction, a reasoned
20
+ // no-change, a removal, a rating — so there is no typed text to look for. Narrative prose
21
+ // reaches the client only through the overview's own words, and this does not guess at it;
22
+ // not-checkable the records before and after the pass could not both be read, so nothing was compared.
23
+ //
24
+ // THE RECORD DECIDES, report.md IS A SECOND LOOK. Every client surface renders these fields from the delivered
25
+ // record — findings.json as the report was built from it, folded after the delivery seam — so the outcome is
26
+ // read there, joined in the model's numbering through the fold's map. report.md prints two of them itself, the
27
+ // finding sentence on a finding's card and an action's text, and each such change says whether report.md
28
+ // carries it (`inReportMd`, null for a field report.md does not print). It never decides the outcome: whether a
29
+ // finding has a card is the assembly's choice, not the correction's fate.
30
+ //
31
+ // PURE — no node imports. The pipeline reads and writes the files.
32
+ import { reportLines, linesOf } from "./corrections-feedforward.mjs";
33
+
34
+ /** The finding fields a correction can change that a report prints from the record. */
35
+ export const DELIVERED_FINDING_FIELDS = Object.freeze(["net", "legal_position", "practical_position"]);
36
+
37
+ const squash = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
38
+ // The name join `buildCorrectionsApplied` uses: mark first, then owner, first one wins.
39
+ const norm = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
40
+ function byName(doc) {
41
+ const out = new Map();
42
+ for (const f of doc?.findings ?? [])
43
+ for (const name of [f?.mark, f?.owner?.name]) { const n = norm(name); if (n && !out.has(n)) out.set(n, f); }
44
+ return out;
45
+ }
46
+ const byOrdinal = (doc) => new Map((doc?.findings ?? []).filter((f) => Number.isInteger(f?.ordinal)).map((f) => [f.ordinal, f]));
47
+
48
+ /**
49
+ * The basis: for each row `buildCorrectionsApplied` returned, the printed fields the pass changed and the text it
50
+ * left in each. PURE. `preDoc` and `postDoc` are the records before and after the pass, in the model's numbering —
51
+ * the two the applied table was built from. A finding is the one the flag declared by ordinal, else the one it
52
+ * named; a line is the one the flag named or quoted, as the applied table joined it.
53
+ */
54
+ export function correctionTargets(rows, preDoc, postDoc) {
55
+ const checkable = Boolean(preDoc && postDoc);
56
+ const preOrd = byOrdinal(preDoc), postOrd = byOrdinal(postDoc);
57
+ const preName = byName(preDoc), postName = byName(postDoc);
58
+ const preLines = reportLines(preDoc);
59
+ const postLines = new Map(reportLines(postDoc).map((l) => [l.key, l.text]));
60
+ return (Array.isArray(rows) ? rows : []).map((r) => {
61
+ const out = { n: r?.n ?? null, kind: r?.kind ?? null, applied: r?.outcome ?? null, changes: [] };
62
+ if (!checkable || r?.outcome === "not-checkable") return { ...out, why: "not-checkable" };
63
+ const seen = new Set();
64
+ const add = (c, key) => { if (!seen.has(key)) { seen.add(key); out.changes.push(c); } };
65
+ if (r.outcome === "line-changed") {
66
+ for (const l of linesOf(r.text, preLines)) {
67
+ const now = postLines.get(l.key);
68
+ if (now !== undefined && squash(now) !== squash(l.text)) add({ line: l.key, text: now }, l.key);
69
+ }
70
+ } else if (r.outcome === "findings-changed") {
71
+ const declared = (Array.isArray(r.ordinals) ? r.ordinals : []).filter((o) => preOrd.has(o) || postOrd.has(o));
72
+ const pairs = declared.length ? declared.map((o) => [preOrd.get(o), postOrd.get(o)])
73
+ : (Array.isArray(r.targets) ? r.targets : []).map((t) => [preName.get(norm(t)), postName.get(norm(t))]);
74
+ for (const [a, b] of pairs) {
75
+ if (!b || !Number.isInteger(b.ordinal)) continue; // gone after the pass: nothing left to deliver
76
+ for (const field of DELIVERED_FINDING_FIELDS) {
77
+ if (squash(a?.[field]) !== squash(b[field])) add({ ordinal: b.ordinal, field, text: String(b[field] ?? "") }, `${b.ordinal}|${field}`);
78
+ }
79
+ }
80
+ }
81
+ return out.changes.length ? out : { ...out, why: "no-printed-field-changed" };
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Each basis row's outcome against the delivered record. PURE. `deliveredDoc` is the record the report was built
87
+ * from; `foldMap` is the fold's `{modelToFolded}` when a fold is in force, else null; `reportMd` is report.md's
88
+ * text, or null when it could not be read. Returns null when there is no delivered record to read: a caller
89
+ * records that as a failure to look, never as an outcome.
90
+ */
91
+ export function deliveredOutcomes({ basis, deliveredDoc, foldMap = null, reportMd = null } = {}) {
92
+ if (!Array.isArray(basis) || !deliveredDoc || typeof deliveredDoc !== "object") return null;
93
+ const findings = byOrdinal(deliveredDoc);
94
+ const lines = new Map(reportLines(deliveredDoc).map((l) => [l.key, l.text]));
95
+ const md = typeof reportMd === "string" ? squash(reportMd) : null;
96
+ const folded = (o) => { const f = foldMap?.modelToFolded?.[String(o)]; return f == null ? o : Number(f); };
97
+ return basis.map((b) => {
98
+ const head = { n: b?.n ?? null, kind: b?.kind ?? null, applied: b?.applied ?? null };
99
+ if (b?.why === "not-checkable") return { ...head, outcome: "not-checkable", changes: [] };
100
+ const changes = (Array.isArray(b?.changes) ? b.changes : []).map((c) => {
101
+ const onFinding = c?.ordinal != null;
102
+ const where = onFinding ? { ordinal: c.ordinal, deliveredAs: folded(c.ordinal), field: c.field } : { line: c?.line };
103
+ const inRecord = onFinding
104
+ ? findings.has(where.deliveredAs) && squash(findings.get(where.deliveredAs)[c.field]) === squash(c.text)
105
+ : lines.has(c?.line) && squash(lines.get(c.line)) === squash(c.text);
106
+ const printedInReportMd = (onFinding && c.field === "net") || (!onFinding && String(c?.line).startsWith("action:"));
107
+ const inReportMd = !printedInReportMd || md == null || !squash(c?.text) ? null : md.includes(squash(c.text));
108
+ return { ...where, inRecord, inReportMd };
109
+ });
110
+ if (!changes.length) return { ...head, outcome: "not-applicable", why: b?.why ?? "no-printed-field-changed", changes };
111
+ return { ...head, outcome: changes.every((c) => c.inRecord) ? "delivered" : "not-found", changes };
112
+ });
113
+ }
@@ -252,7 +252,10 @@ export function scopeDrift(rows, preDoc, postDoc) {
252
252
  return { scoped: true, named: [...named], moved, unbound };
253
253
  }
254
254
 
255
- export function buildCorrectionsApplied(rows, preDoc, postDoc) {
255
+ // `sections`, optional: the flags whose target is a narrative section, as `{n, label, before, after}` with the
256
+ // section's text before and after the pass. The review after a delivery-check repair names sections as items,
257
+ // so a flag on one can be checked; with no `sections` the output is what it always was.
258
+ export function buildCorrectionsApplied(rows, preDoc, postDoc, { sections = null } = {}) {
256
259
  // THE UNION OF BOTH SIDES, deliberately. Reading the post doc alone loses a finding the pass REMOVED —
257
260
  // and a removal is exactly when the flag would stop naming anything, so the post doc alone cannot even
258
261
  // see the event.: what that removal is EVIDENCE of is the question this module used to get wrong.
@@ -286,12 +289,15 @@ export function buildCorrectionsApplied(rows, preDoc, postDoc) {
286
289
  // — A LINE THAT IS NOT A FINDING, asked before the name match. A flag that names or quotes a coverage
287
290
  // note, an action or the mark assessment is about that line, and a mark or owner named inside the
288
291
  // line it quotes is incidental. Asked only where no ordinal was declared: a declaration still wins.
289
- const hit = preDoc && !declaredOrds.length ? linesOf(r.text, preLines) : [];
292
+ const sec = !declaredOrds.length ? (sections ?? []).find((x) => x.n === r.n) ?? null : null;
293
+ const hit = preDoc && !declaredOrds.length && !sec ? linesOf(r.text, preLines) : [];
290
294
  const targets = declaredOrds.length ? declaredOrds.map(label)
295
+ : sec ? [sec.label]
291
296
  : hit.length ? hit.map((l) => l.label) : targetsOf(r.text, known);
292
297
  let outcome;
293
298
  let removed = [];
294
- if (!preDoc) outcome = "not-checkable";
299
+ if (sec) outcome = squash(sec.after ?? "") !== squash(sec.before ?? "") ? "section-changed" : "section-unchanged";
300
+ else if (!preDoc) outcome = "not-checkable";
295
301
  else if (hit.length) {
296
302
  // The same three answers a finding gets, asked of the line's own text. REMOVAL WINS, as it does
297
303
  // for a finding: a line gone after the pass was answered by deletion, and that is the question.
@@ -374,7 +380,7 @@ export function buildCorrectionsApplied(rows, preDoc, postDoc) {
374
380
  *
375
381
  * PURE.
376
382
  */
377
- export const RESOLVED_OUTCOMES = Object.freeze(["findings-changed", "line-changed"]);
383
+ export const RESOLVED_OUTCOMES = Object.freeze(["findings-changed", "line-changed", "section-changed"]);
378
384
 
379
385
  /** The rows to print. Anything not positively resolved, including a row of an outcome nobody has met. */
380
386
  export const unresolvedFlags = (rows) =>
@@ -387,7 +393,7 @@ export const unresolvedFlags = (rows) =>
387
393
  * twelve mixed lines has to re-derive that grouping for itself every time. The raw review still rides
388
394
  * below it in the followup: this is a better index of the same evidence, not a replacement for it. PURE.
389
395
  */
390
- export function correctionsWorklist(rows) {
396
+ export function correctionsWorklist(rows, { afterRepair = false } = {}) {
391
397
  if (!rows?.length) return "";
392
398
  const byKind = new Map();
393
399
  for (const r of rows) { if (!byKind.has(r.kind)) byKind.set(r.kind, []); byKind.get(r.kind).push(r); }
@@ -400,9 +406,13 @@ export function correctionsWorklist(rows) {
400
406
  // it rather than correcting it: the flag went away and the report did not become true. Stated as a
401
407
  // constraint on the worklist itself, where the moves are chosen, rather than left to the recheck to
402
408
  // catch afterwards — the recheck that caught it did so once, and once is luck.
409
+ //
410
+ // AFTER A DELIVERY-CHECK REPAIR NO REVIEW FOLLOWS THE CORRECTION, so "standing for the reviewer" would be
411
+ // false there: a point left standing is recorded for the reviewing lawyer instead.
403
412
  out.push("",
404
413
  "REMOVING A FLAGGED FINDING DOES NOT ANSWER ITS FLAG. Correct the fact, or say why it cannot be corrected "
405
- + "and leave it standing for the reviewer. A finding you believe should not be there is WITHDRAWN with its "
414
+ + (afterRepair ? "and leave it standing; the point is recorded for the reviewing lawyer. " : "and leave it standing for the reviewer. ")
415
+ + "A finding you believe should not be there is WITHDRAWN with its "
406
416
  + "reason recorded, never deleted — a flag that stops naming anything reads as resolved, and the driver "
407
417
  + "reports the deletion by name either way.");
408
418
  return out.join("\n");
@@ -9,17 +9,20 @@
9
9
  //
10
10
  // ── WHY THIS IS NOT SIMPLY "ALLOW THE WORD" ─────────────────────────────────────────────────────────
11
11
  //
12
- // The production corrective pass is a WARM followup: it resumes the synthesis session and sends only
13
- // `correctionsExtra`'s blocks. An experiment arm always builds a fresh key and dispatches into a sandbox,
14
- // so it cannot resume anything — and resuming the REAL session from a sandbox was rejected outright,
15
- // because "canonical run untouched" is the arm's whole contract and an arm that breaks its contract to
16
- // gain fidelity is measuring with a broken instrument.
12
+ // The production corrective pass is a FOLLOWUP dispatch of the synthesis stage, on the run's own key with
13
+ // the winning attempt's model and thinking, and its message is `correctionsExtra`'s blocks. It does not
14
+ // resume the synthesis session: no engine resumes by session key (stages-followup.test.mjs pins it), so
15
+ // the followup's first attempt is a fresh turn handed the corrections and the stage's inputs to read. An
16
+ // experiment arm always builds a fresh key and dispatches into a sandbox — and dispatching on the REAL run's
17
+ // key from a sandbox was rejected outright, because "canonical run untouched" is the arm's whole contract and
18
+ // an arm that breaks its contract to gain fidelity is measuring with a broken instrument.
17
19
  //
18
20
  // So an arm carries the corrective pass's INPUTS on a COLD turn, composed by the same function production
19
- // uses. That reaches the seam nothing else reaches without a two-hour run. It is NOT the warm pass, and
20
- // the receipt says so in a word rather than leaving it to be inferred:
21
+ // uses. That reaches the seam nothing else reaches without a two-hour run. It is NOT production's
22
+ // dispatch, and the receipt says so in a word rather than leaving it to be inferred. The production word
23
+ // is a recorded value and stays as it is, though the followup it names never resumes the synthesis session:
21
24
  //
22
- // dispatched-warm production only — the resumed followup
25
+ // dispatched-warm production only — the followup on the run's own key
23
26
  // dispatched-cold the arm — the same blocks, a fresh turn
24
27
  // refused-no-corrective-state neither — this run has no corrective pass to reproduce
25
28
  //