clearotron 0.3.1-beta.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,9 +15,9 @@
15
15
  // several times that, because EIGHT sites build the token dynamically and hand back a string the
16
16
  // extractor never sees:
17
17
  //
18
- // D1 verify.mjs:898 fail(`connotation_${reason}:…`) — reason iterates the table at line 894
18
+ // D1 verify.mjs:940 fail(`connotation_${reason}:…`) — reason iterates the table at line 894
19
19
  // D2 verify.mjs:704 fail(String(e.message)) — parseFindingsJson throws token-first
20
- // D3 verify.mjs:1123 checkJson: fail(String(e.message)) — FIVE parsers reach this one site
20
+ // D3 verify.mjs:1165 checkJson: fail(String(e.message)) — FIVE parsers reach this one site
21
21
  // D4 verify.mjs:1603 parseCoverageLedgerJson, same shape
22
22
  // D5 verify.mjs:1504 fail(`${unaccounted[0].token}:…`) — token minted in a DATA ROW
23
23
  // D6 verify.mjs:1567 fail(`${violations[0].token}…`) — validatePlanFeasibility in register-plan.mjs
@@ -68,51 +68,52 @@ export const VOCABULARY = [
68
68
  { token: "missing", stages: ["matter-frame", "prelim-variants", "common-law", "common-law-half", "placement-inquiry", "register-digest", "doubt-closure", "report-overview", "report-card"], site: "driver/verify.mjs:164" },
69
69
 
70
70
  // ── common-law / common-law-half ───────────────────────────────────────────────────────────────────
71
- { token: "declared_unavailable", stages: CL, site: "driver/verify.mjs:231" },
71
+ { token: "declared_unavailable", stages: CL, site: "driver/verify.mjs" },
72
72
  { token: "grid_spec_unreadable", stages: CL, site: "driver/verify.mjs:257" },
73
73
  { token: "grid_ledger_missing", stages: CL, site: "driver/verify.mjs:297" },
74
74
  { token: "grid_ledger_unparseable", stages: CL, site: "driver/verify.mjs:266" },
75
- { token: "connotation_query_unrecorded", stages: ["common-law-half"], site: "driver/verify.mjs:269" },
76
- { token: "profile_unparseable", stages: ["common-law"], site: "driver/verify.mjs:301" },
75
+ { token: "connotation_query_unrecorded", stages: ["common-law-half"], site: "driver/verify.mjs" },
76
+ { token: "profile_unparseable", stages: ["common-law"], site: "driver/verify.mjs" },
77
77
  { token: "grid_join_missing", stages: CL, site: "driver/verify.mjs:318" },
78
78
  { token: "platform_identity_error", stages: CL, site: "driver/verify.mjs:323" },
79
79
  { token: "platforms_missing", stages: CL, site: "driver/verify.mjs:334" },
80
- { token: "connotation_search_missing", stages: ["common-law"], site: "driver/verify.mjs:362" },
81
- { token: "receipts_short", stages: ["common-law"], site: "driver/verify.mjs:388" },
80
+ { token: "connotation_search_missing", stages: ["common-law"], site: "driver/verify.mjs" },
81
+ { token: "receipts_short", stages: ["common-law"], site: "driver/verify.mjs" },
82
82
  { token: "half_path_unrecognized", stages: ["common-law-half"], site: "driver/verify.mjs:395" },
83
83
  { token: "no_coverage_status_row", stages: [...CL, "register-digest"], site: "driver/verify.mjs:452, 1416" },
84
84
  // B — `connotation_form_unparseable` and `connotation_form_untouched` are GONE: they
85
85
  // were states only a hand-authored document could be in, and the form path is deleted (ruling
86
86
  // 2026-08-17). Their subject matter is carried by the call tokens under the D1 family row below.
87
- { token: "connotation_form_damaged", stages: CL, site: "driver/verify.mjs:889" },
88
- { token: "connotation_", stages: CL, site: "driver/verify.mjs:898", family: "driver/connotation-search.mjs:894 (CONNOTATION_FORM_REASONS, exported at :819)", dynamic: "D1" },
89
- { token: "connotation_quote_unbound", stages: CL, site: "driver/verify.mjs:914" },
87
+ { token: "connotation_form_damaged", stages: CL, site: "driver/verify.mjs" },
88
+ { token: "connotation_", stages: CL, site: "driver/verify.mjs", family: "driver/connotation-search.mjs CONNOTATION_FORM_REASONS", dynamic: "D1" },
89
+ { token: "connotation_quote_unbound", stages: CL, site: "driver/verify.mjs" },
90
90
 
91
91
  // ── synthesis ──────────────────────────────────────────────────────────────────────────────────────
92
92
  { token: "framework_manifest_unreadable", stages: ["synthesis"], site: "driver/verify.mjs:691" },
93
93
  { token: "framework_manifest_missing_for_v4", stages: ["synthesis"], site: "driver/verify.mjs:706" },
94
- { token: "finding_use_check_source_missing", stages: ["synthesis"], site: "driver/verify.mjs:713" },
95
- { token: "finding_own_rights_source_missing", stages: ["synthesis"], site: "driver/verify.mjs:715" },
96
- { token: "finding_basis_source_missing", stages: ["synthesis"], site: "driver/verify.mjs:779" },
97
- { token: "finding_use_check_missing", stages: ["synthesis"], site: "driver/verify.mjs:729" },
98
- { token: "intake_ask_unanswered", stages: ["synthesis"], site: "driver/verify.mjs:1636" },
99
- { token: "coverage_recommendation", stages: ["synthesis"], site: "driver/verify.mjs:1648" },
100
- { token: "coverage_gap_unexplained", stages: ["synthesis"], site: "driver/verify.mjs:1649" },
94
+ { token: "finding_use_check_source_missing", stages: ["synthesis"], site: "driver/verify.mjs" },
95
+ { token: "finding_own_rights_source_missing", stages: ["synthesis"], site: "driver/verify.mjs" },
96
+ { token: "finding_basis_source_missing", stages: ["synthesis"], site: "driver/verify.mjs" },
97
+ { token: "finding_use_check_missing", stages: ["synthesis"], site: "driver/verify.mjs" },
98
+ { token: "intake_ask_unanswered", stages: ["synthesis"], site: "driver/verify.mjs" },
99
+ { token: "ratified_form_unread", stages: ["synthesis"], site: "driver/verify.mjs" },
100
+ { token: "coverage_recommendation", stages: ["synthesis"], site: "driver/verify.mjs" },
101
+ { token: "coverage_gap_unexplained", stages: ["synthesis"], site: "driver/verify.mjs" },
101
102
  { token: "finding", stages: ["synthesis"], site: "driver/verify.mjs:704", family: "driver/findings-model.mjs (token-first throws; `finding_*` and `findings_*`)", dynamic: "D2" },
102
103
 
103
104
  // ── register-digest ────────────────────────────────────────────────────────────────────────────────
104
- { token: "coverage_form_damaged", stages: ["register-digest"], site: "driver/verify.mjs:976" },
105
- { token: "coverage_form_engine_vocabulary", stages: ["register-digest"], site: "driver/verify.mjs:1021" },
106
- { token: "coverage_form_axis_invalid", stages: ["register-digest"], site: "driver/verify.mjs:1029" },
107
- { token: "coverage_no_status", stages: ["register-digest"], site: "driver/verify.mjs:1062" },
108
- { token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs:1445" },
109
- { token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs:1449" },
105
+ { token: "coverage_form_damaged", stages: ["register-digest"], site: "driver/verify.mjs" },
106
+ { token: "coverage_form_engine_vocabulary", stages: ["register-digest"], site: "driver/verify.mjs" },
107
+ { token: "coverage_form_axis_invalid", stages: ["register-digest"], site: "driver/verify.mjs" },
108
+ { token: "coverage_no_status", stages: ["register-digest"], site: "driver/verify.mjs" },
109
+ { token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs" },
110
+ { token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs" },
110
111
  { token: "coverage_status_offenum", stages: ["register-digest"], site: "driver/verify.mjs:2050" },
111
112
  { token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs:1504 coverageFormFail", family: "driver/register-plan.mjs:1642 PROVIDER_HARD_ERROR_PREFIX — token on a data row", dynamic: "D5" },
112
113
  { token: "coverage_clean_unexecuted", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1417 validatePlanFeasibility", dynamic: "D6" },
113
114
  { token: "coverage_clean_skipped", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1766 searchedJurisdictionsFromPlan", dynamic: "D6" },
114
115
  { token: "coverage_clean_unverified_incomplete", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:2018 disclosureTextByAxis", dynamic: "D7" },
115
- { token: "coverage_clean_tainted", stages: ["register-digest"], site: "driver/verify.mjs:1578" },
116
+ { token: "coverage_clean_tainted", stages: ["register-digest"], site: "driver/verify.mjs" },
116
117
  { token: "coverage_ledger_", stages: ["register-digest"], site: "driver/verify.mjs:1603", family: "driver/coverage-ledger.mjs (parseCoverageLedgerJson token-first throws)", dynamic: "D4" },
117
118
  { token: "coverage_key_unknown", stages: ["register-digest"], site: "driver/verify.mjs:1603", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
118
119
  { token: "coverage_axis_", stages: ["register-digest"], site: "driver/verify.mjs:1603", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
@@ -122,14 +123,14 @@ export const VOCABULARY = [
122
123
 
123
124
  // ── matter-frame / prelim-variants / blind-frame / frame-diff ──────────────────────────────────────
124
125
  { token: "stagecontracts_invalid", stages: ["matter-frame", "prelim-variants", "placement-inquiry", "case-law"], site: "driver/verify.mjs:1539, 1170, 1366, 1677" },
125
- { token: "meaning_angles_missing", stages: ["matter-frame"], site: "driver/verify.mjs:1103" },
126
+ { token: "meaning_angles_missing", stages: ["matter-frame"], site: "driver/verify.mjs" },
126
127
  { token: "frame_scope_missing", stages: ["matter-frame"], site: "driver/verify.mjs:1119" },
127
- { token: "variantmodel_romanization_missing", stages: ["prelim-variants"], site: "driver/verify.mjs:1237" },
128
- { token: "variantmodel_family_incomplete", stages: ["prelim-variants"], site: "driver/verify.mjs:1196" },
129
- { token: "variantmodel_term_markup", stages: ["prelim-variants"], site: "driver/verify.mjs:1263" },
128
+ { token: "variantmodel_romanization_missing", stages: ["prelim-variants"], site: "driver/verify.mjs" },
129
+ { token: "variantmodel_family_incomplete", stages: ["prelim-variants"], site: "driver/verify.mjs" },
130
+ { token: "variantmodel_term_markup", stages: ["prelim-variants"], site: "driver/verify.mjs" },
130
131
  { token: "variantmodel_missing", stages: ["prelim-variants"], site: "driver/verify.mjs:1153, 1212" },
131
132
  // Recovered during E2 authoring, absent from the draft census: variant-manifest.json is strict-parsed
132
- // through checkSiblingJson (verify.mjs:1136) → checkJson (:742), so the WHOLE variantmodel_* family
133
+ // through checkSiblingJson (verify.mjs:1178) → checkJson (:742), so the WHOLE variantmodel_* family
133
134
  // reaches prelim-variants, not just the four literal tokens above.
134
135
  // CONVERSION 3 widened this family's SOURCE without widening its prefix. `acceptPrelimVariants` raises
135
136
  // `variantmodel_scope_layer_invalid`, `_scope_status_invalid`, `_scope_item_missing` and `_scope_pipe`
@@ -142,7 +143,7 @@ export const VOCABULARY = [
142
143
  // `{ok: false, reason}` instead — measured, all four extract ZERO tokens. So this row is authored, not
143
144
  // extracted, and nothing re-derives it if the module grows a member. Filed as.
144
145
  { token: "variantmodel_", stages: ["prelim-variants"], site: "driver/verify.mjs:1153 → 742 (JSON family); driver/prelim-variants-record.mjs acceptPrelimVariants (scope-ledger transport family)", family: "driver/variant-manifest-model.mjs (token-first throws) + driver/prelim-variants-record.mjs", dynamic: "D3" },
145
- { token: "blindframe_", stages: ["blind-frame"], site: "driver/verify.mjs:1221 → 742", family: "driver/blind-frame-model.mjs", dynamic: "D3" },
146
+ { token: "blindframe_", stages: ["blind-frame"], site: "driver/verify.mjs blindFrame → checkJson", family: "driver/blind-frame-model.mjs", dynamic: "D3" },
146
147
  // — THE SKEPTIC TRANSPORT FAMILY, WHICH HAD NO ROW AT ALL. Nine tokens minted by acceptSkeptic
147
148
  // and not one of them was covered here: the conversion that moved them to the acceptance boundary moved
148
149
  // them out of a census that could not read that boundary, so nothing went red. Two of the nine are
@@ -164,16 +165,16 @@ export const VOCABULARY = [
164
165
  // the frame never reaches disk. They are declared here for the same reason the two families below are —
165
166
  // verify.mjs reaches the module, so a family that could grow a member no row covers must be readable.
166
167
  { token: "matterframe_", stages: ["matter-frame"], site: "driver/matter-frame-record.mjs acceptMatterFrame", family: "driver/matter-frame-record.mjs", dynamic: "D3" },
167
- { token: "framediff_model_missing", stages: ["frame-diff"], site: "driver/verify.mjs:1224" },
168
+ { token: "framediff_model_missing", stages: ["frame-diff"], site: "driver/verify.mjs" },
168
169
  { token: "framediff_", stages: ["frame-diff"], site: "driver/verify.mjs:1224 → 742", family: "driver/frame-diff-model.mjs", dynamic: "D3" },
169
170
 
170
171
  // ── register-unit ──────────────────────────────────────────────────────────────────────────────────
171
- { token: "declared_not_executed", stages: ["register-unit"], site: "driver/verify.mjs:1236" },
172
+ { token: "declared_not_executed", stages: ["register-unit"], site: "driver/verify.mjs" },
172
173
  { token: "tool_timeout", stages: ["register-unit"], site: "driver/verify.mjs:1788" },
173
- { token: "named_band_missing", stages: ["register-unit"], site: "driver/verify.mjs:1731" },
174
- { token: "named_band_invalid", stages: ["register-unit"], site: "driver/verify.mjs:1736" },
175
- { token: "named_band_collapsed", stages: ["register-unit"], site: "driver/verify.mjs:1747" },
176
- { token: "band_block_unplanned", stages: ["register-unit"], site: "driver/verify.mjs:1761" },
174
+ { token: "named_band_missing", stages: ["register-unit"], site: "driver/verify.mjs" },
175
+ { token: "named_band_invalid", stages: ["register-unit"], site: "driver/verify.mjs" },
176
+ { token: "named_band_collapsed", stages: ["register-unit"], site: "driver/verify.mjs" },
177
+ { token: "band_block_unplanned", stages: ["register-unit"], site: "driver/verify.mjs" },
177
178
  { token: "named_band_", stages: ["register-unit"], site: "driver/verify.mjs:1736 → 742", family: "driver/named-band.mjs", dynamic: "D3" },
178
179
 
179
180
  // ── placement-inquiry ──────────────────────────────────────────────────────────────────────────────
@@ -182,10 +183,10 @@ export const VOCABULARY = [
182
183
 
183
184
  // ── case-law / narrative-refutation ────────────────────────────────────────────────────────────────
184
185
  { token: "caselaw_ledger_missing", stages: ["case-law"], site: "driver/verify.mjs:1682, 1685" },
185
- { token: "caselaw_ledger_unparseable", stages: ["case-law"], site: "driver/verify.mjs:1745" },
186
+ { token: "caselaw_ledger_unparseable", stages: ["case-law"], site: "driver/verify.mjs" },
186
187
  { token: "caselaw_ledger", stages: ["case-law"], site: "driver/verify.mjs:1692", family: "driver/case-law-ledger.mjs:204 (census reasons)", dynamic: "D8" },
187
- { token: "no_verdict_line", stages: ["narrative-refutation"], site: "driver/verify.mjs:1754" },
188
- { token: "plan_audit_missing", stages: ["narrative-refutation"], site: "driver/verify.mjs:1771" },
188
+ { token: "no_verdict_line", stages: ["narrative-refutation"], site: "driver/verify.mjs" },
189
+ { token: "plan_audit_missing", stages: ["narrative-refutation"], site: "driver/verify.mjs" },
189
190
 
190
191
  // ── delivery ───────────────────────────────────────────────────────────────────────────────────────
191
192
  // EMPTY SINCE, and that is the finding rather than an omission: the only delivery-side token was
@@ -193,7 +194,7 @@ export const VOCABULARY = [
193
194
  // its own send. Delivery is code and a packet now, so there is no stage to emit a delivery token.
194
195
 
195
196
  // ── the gateway wrapper, emittable by EVERY stage and not in verify.mjs at all ─────────────────────
196
- { token: "missing_file", stages: ALL_STAGES, site: "driver/gateway.mjs:1037" },
197
+ { token: "missing_file", stages: ALL_STAGES, site: "driver/gateway.mjs" },
197
198
  ];
198
199
 
199
200
  /**
@@ -299,9 +300,9 @@ export const ARM1_EXEMPTIONS = [
299
300
  * than silently enjoyed, and so a validator that LATER gains a stage is noticed.
300
301
  */
301
302
  export const STAGE_UNREACHABLE_VALIDATORS = [
302
- { validator: "findings", site: "driver/verify.mjs:1661", reason: "no STAGES entry names it; synthesis validates findings.json through validators.narrative's sibling checks instead" },
303
- { validator: "report", site: "driver/verify.mjs:1719", reason: "report.md is ASSEMBLED by the driver (assembleReportMd); the validator survives as a post-assembly structural gate outside the stage table" },
304
- { validator: "audit", site: "driver/verify.mjs:1728", reason: "reached only through the MCP server's dynamic validators[key] lookup, which declares it required" },
303
+ { validator: "findings", site: "driver/verify.mjs validators.findings", reason: "no STAGES entry names it; synthesis validates findings.json through validators.narrative's sibling checks instead" },
304
+ { validator: "report", site: "driver/verify.mjs validators.report", reason: "report.md is ASSEMBLED by the driver (assembleReportMd); the validator survives as a post-assembly structural gate outside the stage table" },
305
+ { validator: "audit", site: "driver/verify.mjs validators.audit", reason: "reached only through the MCP server's dynamic validators[key] lookup, which declares it required" },
305
306
  { validator: "clientSummary", site: "driver/verify.mjs:1736", reason: "the client-summary STAGE was deleted 2026-08-01 (no line to cite: the stage is gone); the validator was not" },
306
307
  ];
307
308
 
@@ -415,33 +416,33 @@ export const TRIPWIRE_OUT_OF_SCOPE = [
415
416
  export const INNER_CODES = Object.freeze([
416
417
  // ── connotation-search.mjs — namespaced one-for-one by verify.mjs ────────────────────────────────
417
418
  //
418
- // `CONNOTATION_FORM_REASONS` (connotation-search.mjs:1599) is `CONNOTATION_REASONS` minus
419
+ // CONNOTATION_FORM_REASONS at connotation-search.mjs:1731 is `CONNOTATION_REASONS` minus
419
420
  // `no_recorded_queries`, and the `connotation_` family row (dynamic D1) is declared against exactly
420
421
  // that list. The four call codes are folded by a template — `connotation_${callFail.reason}` at
421
422
  // verify.mjs:1013 — so they are namespaced by construction rather than one branch at a time.
422
- { code: "call_never_made", mints: ["driver/connotation-search.mjs:1956"], rollsUpTo: ["connotation_call_never_made"],
423
+ { code: "call_never_made", mints: ["driver/connotation-search.mjs:2007"], rollsUpTo: ["connotation_call_never_made"],
423
424
  why: "CALL_AUDIT_ROWS. The typed transport's four call states, handed in by disposition-call-audit.mjs and namespaced at verify.mjs:1013." },
424
- { code: "call_truncated", mints: ["driver/connotation-search.mjs:1957"], rollsUpTo: ["connotation_call_truncated"],
425
+ { code: "call_truncated", mints: ["driver/connotation-search.mjs:2008"], rollsUpTo: ["connotation_call_truncated"],
425
426
  why: "As call_never_made — same table, same projection." },
426
- { code: "call_schema_violation", mints: ["driver/connotation-search.mjs:1958"], rollsUpTo: ["connotation_call_schema_violation"],
427
+ { code: "call_schema_violation", mints: ["driver/connotation-search.mjs:2009"], rollsUpTo: ["connotation_call_schema_violation"],
427
428
  why: "As call_never_made — same table, same projection." },
428
- { code: "call_partial", mints: ["driver/connotation-search.mjs:1959"], rollsUpTo: ["connotation_call_partial"],
429
+ { code: "call_partial", mints: ["driver/connotation-search.mjs:2010"], rollsUpTo: ["connotation_call_partial"],
429
430
  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." },
430
- { code: "quote_unbound", mints: ["driver/connotation-search.mjs:2090"], rollsUpTo: ["connotation_quote_unbound"],
431
- why: "The ruled-but-unbound row. Projected at verify.mjs:1057, and reported only once nothing is unruled." },
432
- { code: "token_absent", mints: ["driver/connotation-search.mjs:2117"], rollsUpTo: ["connotation_token_absent"],
431
+ { code: "quote_unbound", mints: ["driver/connotation-search.mjs:2141"], rollsUpTo: ["connotation_quote_unbound"],
432
+ why: "The ruled-but-unbound row. Projected at verify.mjs:1099, and reported only once nothing is unruled." },
433
+ { code: "token_absent", mints: ["driver/connotation-search.mjs:2168"], rollsUpTo: ["connotation_token_absent"],
433
434
  why: "#592 split this out of no_ruling. Row-level only — repairs.mjs:352 says so in as many words: `never a top-level token`." },
434
- { code: "cite_absent", mints: ["driver/connotation-search.mjs:2126"], rollsUpTo: ["connotation_cite_absent"],
435
+ { code: "cite_absent", mints: ["driver/connotation-search.mjs:2177"], rollsUpTo: ["connotation_cite_absent"],
435
436
  why: "#592, as token_absent. repairs.mjs:353: `row-level only — never a top-level token`." },
436
- { code: "no_ruling", mints: ["driver/connotation-search.mjs:2133"], rollsUpTo: ["connotation_no_ruling"],
437
+ { code: "no_ruling", mints: ["driver/connotation-search.mjs:2184"], rollsUpTo: ["connotation_no_ruling"],
437
438
  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." },
438
439
  // THE ONE THAT IS RENAMED, NOT NAMESPACED — and it is the reason this table stores the composite as
439
440
  // data rather than deriving it. Every derivation anyone would write is `connotation_` + the code, and
440
441
  // for this row that produces `connotation_no_recorded_queries`, which nothing mints and nothing covers.
441
442
  // verify.mjs:912-913 states the ruling: a sweep that did not RUN is a canonical-only decision with its
442
443
  // own token and its own remedy, so the projector at verify.mjs:909 deliberately does not handle it.
443
- { code: "no_recorded_queries", mints: ["driver/connotation-search.mjs:1974"], rollsUpTo: ["connotation_search_missing"],
444
- why: "RENAMED, not namespaced: verify.mjs:398 emits `connotation_search_missing`. It is excluded from CONNOTATION_FORM_REASONS at connotation-search.mjs:1680 for exactly this reason." },
444
+ { code: "no_recorded_queries", mints: ["driver/connotation-search.mjs:2025"], rollsUpTo: ["connotation_search_missing"],
445
+ why: "RENAMED, not namespaced: verify.mjs emits `connotation_search_missing`. It is excluded from CONNOTATION_FORM_REASONS at connotation-search.mjs:1731 for exactly this reason." },
445
446
 
446
447
  // ── case-law-ledger.mjs — ONE token, the codes as census payload ─────────────────────────────────
447
448
  //
@@ -471,8 +472,8 @@ export const INNER_CODES = Object.freeze([
471
472
 
472
473
  // ── coverage-form.mjs ─────────────────────────────────────────────────────────────────────────────
473
474
  //
474
- // ONE CODE, TWO TOKENS, and the split is by `cause` rather than by code: verify.mjs:1169 peels the
475
- // `axis_invalid` cause into its own family before verify.mjs:1236 counts the rest. A ruling naming only
475
+ // ONE CODE, TWO TOKENS, and the split is by `cause` rather than by code: verify.mjs peels the
476
+ // `axis_invalid` cause into its own family before counting the rest. A ruling naming only
476
477
  // `coverage_no_status` would be true of most `no_status` records and false of the ones that matter most.
477
478
  { code: "no_status", mints: ["driver/coverage-form.mjs:836"], rollsUpTo: ["coverage_no_status", "coverage_form_axis_invalid"],
478
479
  why: "Two composites, split on the record's `cause`: verify.mjs:1172 for cause `axis_invalid`, verify.mjs:1263 for the rest. NOT cited at coverage-form.mjs:810 — that is the JSDoc @returns annotation, not the mint." },
@@ -487,7 +488,7 @@ export const INNER_CODES = Object.freeze([
487
488
  // the census output would have been half a ruling. verify.mjs:1075 already states the rule this row
488
489
  // records: `coverage_form_damaged`, never a bare `form_damaged`.
489
490
  { code: "form_damaged",
490
- mints: ["driver/connotation-search.mjs:1979", "driver/connotation-search.mjs:2055", "driver/coverage-form.mjs:818"],
491
+ mints: ["driver/connotation-search.mjs:2030", "driver/connotation-search.mjs:2106", "driver/coverage-form.mjs:818"],
491
492
  rollsUpTo: ["connotation_form_damaged", "coverage_form_damaged"],
492
493
  why: "Minted in two lanes and namespaced per lane: verify.mjs:1019 for the meaning sweep, verify.mjs:1119 for the register digest. The namespacing is what keeps them apart — see verify.mjs:1075." },
493
494
  ]);
@@ -89,7 +89,7 @@ export const REGISTER_SERVERS = {
89
89
  // mint is shared (mcp/supplemental.mjs); the only per-provider parts are doExecutePlan, which this
90
90
  // server already binds for the dictated plan, and the capability contract, which the mint reads to
91
91
  // decide what defers. Nothing new reaches the wire. While it was unwired the driver's composed prose
92
- // ordered the mint UNCONDITIONALLY — `stages.mjs:2095` and `:2131` — so a signa deploy was told to
92
+ // ordered the mint UNCONDITIONALLY — `stages.mjs` (the unconditional mint the register-units digest orders) — so a signa deploy was told to
93
93
  // make a call no server offered. Measured on over a real signa run: the order reached three
94
94
  // register-unit stages, no call was recorded against it, and the run still read `delivered`.
95
95
  //
@@ -2571,6 +2571,15 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
2571
2571
  // — THE TOKEN THAT HAD NO HINT, and the absence is what made the failure
2572
2572
  // permanent rather than merely wrong.
2573
2573
  //
2574
+ // AND IT IS ONLY REACHABLE NOW WHEN A QUERY IS GENUINELY ABSENT. It used to be reachable when the
2575
+ // query was recorded in a different spelling, and then this hint asked for the one thing that could
2576
+ // not help: re-running a query that had already run and been recorded. Measured on a production
2577
+ // clearance, 2026-09-16 — one query of sixty-one carried a typographic apostrophe where the driver
2578
+ // wrote a straight one, the stage failed four times byte-identically, and the clearance stopped. The
2579
+ // join compares on `queryKey` now, so a re-run is the right remedy again; the closing sentence below
2580
+ // asks for the dictated spelling, which is what stops the ledger drifting from the list a second
2581
+ // time.
2582
+ //
2574
2583
  // Measured on the owner's own demo clearance: 61 dictated meaning queries, 60 recorded, ONE dropped
2575
2584
  // — the single query in the whole sweep that found nothing. Four attempts, the same refusal string
2576
2585
  // byte for byte, because a retry with no guidance can only repeat itself.
@@ -2585,13 +2594,31 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
2585
2594
  // gate cannot tell "ran, found nothing" from "never ran", and it is not being asked to. What
2586
2595
  // changes is that the seat is now told how to say the first one.
2587
2596
  const dropped = (lastFail.match(/connotation_query_unrecorded:(.+)$/s) || [])[1] || "";
2588
- hint = `every dictated meaning query owes a row, including the ones that find nothing — these are ` +
2597
+ // ── TWO FAULTS, TWO REMEDIES, and sending the wrong one is what made this permanent ────────────
2598
+ //
2599
+ // The gate marks each dropped query `[absent from the ledger]` or `[unmatched; nearest recorded: …]`.
2600
+ // Absent means the search did not run and must. Unmatched means it DID run and the ledger's spelling
2601
+ // differs beyond punctuation — re-running it changes nothing, and telling a seat to re-run is how a
2602
+ // stage fails four times with the same string. Where both appear, both sentences are sent.
2603
+ const anyUnmatched = /\[unmatched; nearest recorded:/.test(dropped);
2604
+ const anyAbsent = /\[absent from the ledger\]/.test(dropped);
2605
+ hint = anyUnmatched && !anyAbsent
2606
+ ? `these dictated meaning queries ARE recorded in ${gridLedgerName} extras.pr_risk[] under a ` +
2607
+ `different wording, which is why the driver cannot match them: ${dropped}. Do NOT re-run them — ` +
2608
+ `the search already ran and its results are already in the ledger. EDIT each row's \`query\` ` +
2609
+ `field to the query text EXACTLY as the task message dictates it, character for character, and ` +
2610
+ `leave its results untouched. The driver matches your rows to its list by that text`
2611
+ : `every dictated meaning query owes a row, including the ones that find nothing — these are ` +
2589
2612
  `missing from ${gridLedgerName} extras.pr_risk[]: ${dropped}. A query that returned NO results is ` +
2590
2613
  `not an excuse to omit it: record it with an empty results array, which is the receipt that the ` +
2591
2614
  `search RAN and came back clean. That is the whole point of the sweep — on an "offensive meaning" ` +
2592
2615
  `query the empty answer IS the good news, and a missing row is indistinguishable from a search ` +
2593
2616
  `nobody performed. Re-run ONLY the listed queries, append a row per query to extras.pr_risk[] ` +
2594
- `whether or not it has hits, and leave every row already recorded exactly as it is`;
2617
+ `whether or not it has hits, and leave every row already recorded exactly as it is. Record each ` +
2618
+ `query's text EXACTLY as the task message dictates it — the driver matches your rows to its list by ` +
2619
+ `the query text, so a reworded or re-punctuated query reads as one you never ran. Where a query is ` +
2620
+ `marked \`[unmatched; nearest recorded: …]\` it is already in the ledger under that wording: edit ` +
2621
+ `that row's \`query\` to the dictated text rather than running the search again`;
2595
2622
  } else if (/connotation_search_missing/.test(lastFail)) {
2596
2623
  // — was "your PR / reputational section claims a clean meaning … but the ledger recorded ZERO
2597
2624
  // searches", which under the `ensure` prefix instructed the model to make the unbacked claim.
@@ -135,6 +135,12 @@ export function renderMatterFrame(model) {
135
135
  // to find extra classes, so finding none is the ordinary case and not an answer worth a row.
136
136
  for (const c of (model.identified_classes ?? []))
137
137
  out.push(`- **Class ${c.class} — identified by the frame:** ${c.reason}`);
138
+ // THE FORMS THE CLIENT RATIFIED, when there is more than one. One form is the ordinary case and
139
+ // renders nothing: a row saying the name is itself tells a reader nothing they did not have from the
140
+ // heading. Two or more is a fact about what was bought, and it is what the per-form read downstream
141
+ // is owed against — so the frame states it where a reader can see it rather than only in a field.
142
+ if ((model.ratified_forms ?? []).length > 1)
143
+ out.push(`- **Ratified forms:** ${model.ratified_forms.join(", ")}`);
138
144
  out.push("");
139
145
 
140
146
  // `Search channels:` — domains only; the grid site-restricts to them and the general web is always
@@ -166,7 +172,7 @@ export function renderMatterFrame(model) {
166
172
  */
167
173
  /** The shape this tool declares, at every depth — what the ACCEPTOR enforces. */
168
174
  const DECLARED = Object.freeze({
169
- "": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks", "identified_classes"],
175
+ "": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks", "identified_classes", "ratified_forms"],
170
176
  intake_asks: ["ask", "owner"],
171
177
  identified_classes: ["class", "reason"],
172
178
  });
@@ -194,6 +200,24 @@ export function frameIdentifiedClasses(runDir) {
194
200
  return (Array.isArray(rows) ? rows : []).map((r) => String(r?.class ?? "").trim()).filter(Boolean);
195
201
  }
196
202
 
203
+ /**
204
+ * The forms of the name this run's client ratified, as strings. IMPURE (reads the run's own accepted
205
+ * call). Empty or one form is the ordinary answer.
206
+ *
207
+ * WHAT READS IT IS A FLOOR, NOT A FEATURE. `validators.narrative` uses this to decide whether the
208
+ * narrative owes a per-form read, and it owes one only when the frame froze MORE THAN ONE. A run with
209
+ * no frame, a replayed or archived run whose accepted call predates the field, and every ordinary
210
+ * single-form run all return a list the floor ignores — so none of them changes behaviour and every one
211
+ * of them re-verifies byte-identically.
212
+ *
213
+ * THE TRIM IS NOT COSMETIC. A form is compared against the narrative's own text downstream, so a
214
+ * trailing space here would be a form the narrative can never be found to have named.
215
+ */
216
+ export function frameRatifiedForms(runDir) {
217
+ const rows = lastAcceptedMatterFrame(runDir)?.ratified_forms;
218
+ return (Array.isArray(rows) ? rows : []).map((r) => String(r ?? "").trim()).filter(Boolean);
219
+ }
220
+
197
221
  /** The last ACCEPTED call for this run, or null. */
198
222
  export function lastAcceptedMatterFrame(runDir) {
199
223
  return lastAccepted(matterFrameCallPaths(String(runDir ?? "")).accepted, readFileSync);
@@ -227,6 +251,7 @@ export function mergeMatterFrameCall(stored, received) {
227
251
  // NARROW THE SEARCH on the next compile, silently and in the direction that misses rights. An
228
252
  // omission here is a repair that did not mention them, never a decision to withdraw them.
229
253
  identified_classes: keepIfAbsent(received?.identified_classes, base.identified_classes),
254
+ ratified_forms: keepIfAbsent(received?.ratified_forms, base.ratified_forms),
230
255
  };
231
256
  }
232
257
 
@@ -294,6 +319,33 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
294
319
  identified_classes.push({ class: String(n), reason });
295
320
  }
296
321
 
322
+ // ── THE RATIFIED FORMS OF THE NAME, TYPED ─────────────────────────────────────────────────────────
323
+ //
324
+ // A client ratifies two forms of one candidate — the name and a slight twist on it — and both are
325
+ // searched. Until now the second form arrived only in the instruction PROSE, and nothing downstream
326
+ // could tell "two forms of one name" from "one name": `job.marks` with more than one entry means a
327
+ // BATCH, which is a different product. So the synthesis dictation asking for a per-form read had no
328
+ // deterministic floor under it: its arms matched the wording, which reds when the prose moves and
329
+ // cannot red when a model ignores the ask.
330
+ //
331
+ // TYPED RATHER THAN PARSED, for the reason the field above states and measures: a list derived from
332
+ // judgment prose dropped the PRIMARY entry in 19 of 21 runs. A field the frame fills in is a
333
+ // decision; a name scraped out of a sentence is a guess.
334
+ //
335
+ // ABSENT OR ONE FORM IS THE ORDINARY CASE and stays free: the floor that reads this is off unless
336
+ // there is more than one, so every archived run and every single-form run verifies exactly as before.
337
+ const ratified_forms = [];
338
+ for (const f of (Array.isArray(params?.ratified_forms) ? params.ratified_forms : [])) {
339
+ const form = str(f);
340
+ if (!form)
341
+ return { ok: false, reason: "matterframe_ratified_form_empty — a ratified form is the name as the client ratified it, never a blank row" };
342
+ // CASE-INSENSITIVE, because two rows differing only in case are one form written twice and the
343
+ // per-form read below would then be asked for a distinction the client never made.
344
+ if (ratified_forms.some((k) => k.toLowerCase() === form.toLowerCase()))
345
+ return { ok: false, reason: `matterframe_ratified_form_duplicate:${form} — one row per form` };
346
+ ratified_forms.push(form);
347
+ }
348
+
297
349
  const model = {
298
350
  schema_version: SCHEMA_VERSION,
299
351
  instructed_scope: instructedScope ?? null,
@@ -305,6 +357,7 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
305
357
  meaning_angles, meaning_angles_none,
306
358
  intake_asks,
307
359
  identified_classes,
360
+ ratified_forms,
308
361
  };
309
362
  return { ok: true, model, content: renderMatterFrame(model) };
310
363
  }
@@ -2,7 +2,7 @@
2
2
  "name": "clearotron-driver",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.1-beta.3",
5
+ "version": "0.3.1",
6
6
  "license": "AGPL-3.0-only",
7
7
  "description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
8
8
  "engines": {
@@ -3198,7 +3198,10 @@ async function connectorDoorKind(url) {
3198
3198
  // as they were, and the answer says which happened. Nobody sets their own switches, and nobody
3199
3199
  // gives Run without holding it.
3200
3200
  if (parts[2] === "people" && parts.length === 3 && method === "POST") {
3201
- if (localSignIn) return { status: 409, json: { error: "local_sign_in" } };
3201
+ // CODE AS ITS OWN FIELD. `error` carries a sentence on most routes here and a token on a few, so
3202
+ // a client cannot tell the two apart by looking at it — and the one that reached the page as page
3203
+ // copy was a token. A code the client reads as a FIELD is decidable; prose never is.
3204
+ if (localSignIn) return { status: 409, json: { error: "local_sign_in", code: "local_sign_in" } };
3202
3205
  if (!writeGrants) return { status: 503, json: { error: "cannot_write_grants" } };
3203
3206
  const email = String(body?.email ?? "").trim().toLowerCase();
3204
3207
  if (!email || email.indexOf("@") <= 0 || email.indexOf("@") !== email.lastIndexOf("@"))
@@ -3257,7 +3260,10 @@ async function connectorDoorKind(url) {
3257
3260
  // refuses the whole act, because a manager who can take their own Manage away can lock the
3258
3261
  // install's last manager out of it with one press, and the way back is a text editor on the box.
3259
3262
  if (parts[2] === "people" && parts[3] === "change" && parts.length === 4 && method === "POST") {
3260
- if (localSignIn) return { status: 409, json: { error: "local_sign_in" } };
3263
+ // CODE AS ITS OWN FIELD. `error` carries a sentence on most routes here and a token on a few, so
3264
+ // a client cannot tell the two apart by looking at it — and the one that reached the page as page
3265
+ // copy was a token. A code the client reads as a FIELD is decidable; prose never is.
3266
+ if (localSignIn) return { status: 409, json: { error: "local_sign_in", code: "local_sign_in" } };
3261
3267
  if (!writeGrants) return { status: 503, json: { error: "cannot_write_grants" } };
3262
3268
  const email = String(body?.email ?? "").trim().toLowerCase();
3263
3269
  if (email === principal.email) return { status: 400, json: { error: "You cannot change your own access. Somebody else who manages this install can." } };
@@ -3322,7 +3328,10 @@ async function connectorDoorKind(url) {
3322
3328
  // — it reads the same `covered` the view computes — but the request carries no scope to get
3323
3329
  // wrong, so a stale page cannot ask for more than the person pressing it can see.
3324
3330
  if (parts[2] === "people" && parts[3] === "remove" && parts.length === 4 && method === "POST") {
3325
- if (localSignIn) return { status: 409, json: { error: "local_sign_in" } };
3331
+ // CODE AS ITS OWN FIELD. `error` carries a sentence on most routes here and a token on a few, so
3332
+ // a client cannot tell the two apart by looking at it — and the one that reached the page as page
3333
+ // copy was a token. A code the client reads as a FIELD is decidable; prose never is.
3334
+ if (localSignIn) return { status: 409, json: { error: "local_sign_in", code: "local_sign_in" } };
3326
3335
  if (!writeGrants) return { status: 503, json: { error: "cannot_write_grants" } };
3327
3336
  const email = String(body?.email ?? "").trim().toLowerCase();
3328
3337
  if (email === principal.email) return { status: 400, json: { error: "You cannot remove your own access. Somebody else who manages this install can." } };
@@ -4029,9 +4038,39 @@ export function makeHttpHandler({ verify, limiter, service, log = () => {}, devI
4029
4038
  // `extra` exists so a response can carry a CSP. Before this the content-type was hardcoded and there
4030
4039
  // was no way to attach one — which is why the portal shipped without a policy rather than with a
4031
4040
  // permissive one.
4041
+ // ── EVERY JSON RESPONSE SAYS HOW IT MAY BE CACHED, because leaving it unsaid is the defect ────────
4042
+ //
4043
+ // This writer used to send `content-type` and `content-length` and nothing else — no `Cache-Control`,
4044
+ // no `Vary`, no validator — so what a browser did with a signed-in response carrying another
4045
+ // company's material was the browser's decision and not ours. With no `Last-Modified` to work from a
4046
+ // heuristic cache may well store nothing; the defect is not a demonstrated leak, it is that the answer
4047
+ // was never stated for a response behind a session.
4048
+ //
4049
+ // THE ARGUMENT WAS ALREADY MADE HERE, FOR ONE ROUTE. `/portal/api/connect-key` has always set
4050
+ // `no-store` because "a credential sitting in a proxy or a disk cache is the 'outlives the moment'
4051
+ // failure ... arriving by a route the page cannot see". An access list is not a credential and has the
4052
+ // same property, so the rule belongs to the class rather than to the one response that carried a
4053
+ // token.
4054
+ //
4055
+ // `Vary: Accept` IS NOT ABOUT PRIVACY. `/portal/admin/*` is one address served two ways: the app
4056
+ // fetches JSON there, and a browser navigation gets the app document instead (portal-static.mjs
4057
+ // decides on `Accept`, above this router). Nothing told a cache those were different responses, so a
4058
+ // stored JSON body could answer a later navigation and render raw data in a window — with this server
4059
+ // never asked, which is why the negotiation above could not save it.
4060
+ //
4061
+ // A ROUTE'S OWN HEADERS STILL WIN. `extra` is spread last, so connect-key keeps its stricter set
4062
+ // (`no-cache, must-revalidate, private` and the HTTP/1.0 `pragma`) rather than being flattened to this
4063
+ // default. The arm below pins that, because a default that quietly relaxed a stricter route would be
4064
+ // this change making things worse while reading as an improvement.
4032
4065
  const send = (res, status, obj, extra = {}) => {
4033
4066
  const b = JSON.stringify(obj);
4034
- res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(b), ...extra });
4067
+ res.writeHead(status, {
4068
+ "content-type": "application/json",
4069
+ "content-length": Buffer.byteLength(b),
4070
+ "cache-control": "no-store",
4071
+ "vary": "accept",
4072
+ ...extra,
4073
+ });
4035
4074
  res.end(b);
4036
4075
  };
4037
4076
  return async function handler(req, res) {
package/driver/stages.mjs CHANGED
@@ -1363,6 +1363,16 @@ export const STAGES = {
1363
1363
  // The driver STAMPS the instructed-scope section from _driver/instructed-scope.json, so the seat is
1364
1364
  // not asked to quote back values the driver wrote at intake. That retyping was the stage's
1365
1365
  // `frame_scope_missing` loop and it is gone; see matter-frame-record.mjs.
1366
+ // RATIFIED FORMS CARRY THEIR OWN IMPERATIVE, for the reason stated above this block: a field
1367
+ // phrased outside one was written 0 of 9 times against 74 of 74 when imperative-carried. Without
1368
+ // this sentence the typed field is never filled, and the floor under the per-form read in
1369
+ // validators.narrative is green because it is inert — which is the exact defect that floor exists
1370
+ // to end, arriving one layer up.
1371
+ //
1372
+ // ONLY WHEN THE CLIENT RATIFIED MORE THAN ONE. The ordinary matter is one name and must stay
1373
+ // free: a seat asked to list forms for a single-form matter will invent a second one, and the
1374
+ // floor would then demand a read of a form nobody bought.
1375
+ `Send \`ratified_forms\` ONLY if the instruction ratifies more than one form of the same name — the name and a variant of it, both to be searched as one matter. Send each form exactly as the client wrote it. Omit the field entirely for an ordinary single-name matter; do not invent a second form.`,
1366
1376
  `You are NOT asked to quote the request's marks, classes, territories or goods back — the driver stamps that section from its own intake record, so it cannot drift from what was asked.`,
1367
1377
  `Do NOT write or edit any file. There is no path for you to write to: the driver renders the frame from what you send, and nothing you hand-write is read.`,
1368
1378
  `When the tool accepts your call, return ONLY a 2-3 line summary of the frame.`,
@@ -2089,7 +2099,7 @@ export const STAGES = {
2089
2099
  },
2090
2100
  "enumerated block records carried verbatim — record_id, mark_text, classes, status, owner_name, owner_country, application_date, registration_date, expiry_date, jurisdictions, screen_verdict": {
2091
2101
  class: "mechanical:tool-written", tokens: ["named_band_collapsed"],
2092
- why: "register_enumerate already returns each record batch-screened; carrying it is transcription. `named_band_collapsed` (verify.mjs:1283, findCollapsedBands) is exactly the recall loss that transcription produces — a slice claiming total_hits with zero records reaching the band. [citation unverified]",
2102
+ why: "register_enumerate already returns each record batch-screened; carrying it is transcription. `named_band_collapsed` (verify.mjs:1325, findCollapsedBands) is exactly the recall loss that transcription produces — a slice claiming total_hits with zero records reaching the band. [citation unverified]",
2093
2103
  },
2094
2104
  "per-axis prose digest — the SHORT AUDIT NOTE at register-units/<axis>.md": {
2095
2105
  class: "judgment", tokens: ["too_short"],
@@ -2655,7 +2665,7 @@ export const STAGES = {
2655
2665
  },
2656
2666
  "the escalation decision — which register axes carry a material, unresolved, genuinely closeable gap, and the one-line reason for each": {
2657
2667
  class: "judgment", tokens: [],
2658
- why: "Whether a documented coverage-limited row, a capability-gap deferral or a fresh concern on a confirmed-clean row warrants spending a re-run. The driver hands the coverage/execution truth in as a computed table (stages.mjs:1466; skepticDeferralExtra) precisely so this is a call over data rather than a re-derivation — but the call itself is nobody else's. [citation unverified]",
2668
+ why: "Whether a documented coverage-limited row, a capability-gap deferral or a fresh concern on a confirmed-clean row warrants spending a re-run. The driver hands the coverage/execution truth in as a computed table (stages.mjs:1476; skepticDeferralExtra) precisely so this is a call over data rather than a re-derivation — but the call itself is nobody else's. [citation unverified]",
2659
2669
  },
2660
2670
  "escalation decisions — one {axis, reason} per axis that must be re-run, sent through record_skeptic": {
2661
2671
  class: "mechanical:code-rendered", tokens: [],
@@ -2860,6 +2870,10 @@ export const STAGES = {
2860
2870
  class: "mechanical:code-rendered", tokens: ["intake_ask_unanswered"],
2861
2871
  why: "the driver already code-builds the report's section from the ask_answers register (assembleReportMd · buildAskAnswersSection, named in stages.mjs) — the narrative section is a second authoring of a register the driver renders anyway",
2862
2872
  },
2873
+ "narrative per-form read — each ratified form reasoned through the framework, or the reads stated alike": {
2874
+ class: "judgment", tokens: ["ratified_form_unread"],
2875
+ why: "a client who ratified two forms is choosing between them; which conflicts move between the forms is the read they bought, and the token is a floor on whether the question was answered at all rather than on the reasoning",
2876
+ },
2863
2877
  "narrative coverage prose — how a coverage gap is stated": {
2864
2878
  class: "judgment", tokens: ["coverage_recommendation", "coverage_gap_unexplained"],
2865
2879
  why: "what the run could and could not clear is the lawyer's honest statement; the token polices only the forbidden 'commission a re-run' form",
@@ -2886,7 +2900,7 @@ export const STAGES = {
2886
2900
  },
2887
2901
  "findings[].meters.use.token / meters.enforcer.token — the two strengths with no quadrant axis": {
2888
2902
  class: "judgment", tokens: ["finding_meter_missing", "finding_meter_token_invalid", "finding_meter_unknown"],
2889
- why: "Judgment, and S1 does not reach them: quadrant plots goods proximity and mark similarity only (stages.mjs:1733), so nothing derives use (confirmed/not-confirmed/unknown) or enforcer strength. Deriving these from the quadrant would invent a reading the position does not carry. [citation unverified]",
2903
+ why: "Judgment, and S1 does not reach them: quadrant plots goods proximity and mark similarity only (stages.mjs, the quadrant element), so nothing derives use (confirmed/not-confirmed/unknown) or enforcer strength. Deriving these from the quadrant would invent a reading the position does not carry.",
2890
2904
  },
2891
2905
  "findings[].meters.mark_similarity.token / meters.goods_proximity.token — the coarse 3-pip restatement of the quadrant position": {
2892
2906
  class: "mechanical:code-assigned", tokens: ["finding_meter_missing", "finding_meter_token_invalid", "finding_meter_unknown"],