clearotron 0.3.1-beta.2 → 0.3.1-beta.4
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.
- package/bin/grant.mjs +21 -22
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +33 -0
- package/driver/citation-census.json +3 -3
- package/driver/common-law-receipts.mjs +11 -1
- package/driver/connotation-search.mjs +56 -5
- package/driver/contract-e3-backlog.mjs +31 -31
- package/driver/contract-vocabulary.mjs +59 -58
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/gateway.mjs +29 -2
- package/driver/matter-frame-record.mjs +54 -1
- package/driver/package.json +1 -1
- package/driver/portal-config-view.mjs +26 -1
- package/driver/portal-report.mjs +16 -2
- package/driver/portal-service.mjs +327 -8
- package/driver/publish/render.mjs +0 -45
- package/driver/publish/templates/report.css +1 -30
- package/driver/skills/knockout-assess/SKILL.md +35 -6
- package/driver/skills/knockout-frame/SKILL.md +2 -1
- package/driver/skills/prelim-search/risk-framework-triage.md +3 -3
- package/driver/stages.mjs +19 -5
- package/driver/suite-census.json +112 -16
- package/driver/verify-knockout.mjs +58 -0
- package/driver/verify.mjs +83 -7
- package/mcp-server/CHANGELOG.md +11 -0
- package/mcp-server/lib/audit.mjs +96 -3
- package/mcp-server/lib/http-handler.mjs +76 -3
- package/mcp-server/lib/runs.mjs +42 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/client/CONNECT.md +10 -6
- package/mcp-server/server.mjs +37 -3
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-D8ITW-aD.js → index-ChIQsMYp.js} +1375 -817
- package/portal-ui/dist/assets/{index-D5WAoLZI.css → index-DBIs21e4.css} +37 -0
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/ask-ai-render-check.mjs +359 -0
- package/scripts/citation-line-check.mjs +53 -2
- package/scripts/deprecate-below.mjs +146 -0
- package/scripts/test-run.mjs +24 -1
- package/shared/connect-clients.mjs +52 -16
- package/shared/driver-dir.mjs +1 -1
- package/shared/grants-edit.mjs +99 -2
|
@@ -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:
|
|
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:
|
|
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
|
|
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
|
|
76
|
-
{ token: "profile_unparseable", stages: ["common-law"], site: "driver/verify.mjs
|
|
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
|
|
81
|
-
{ token: "receipts_short", stages: ["common-law"], site: "driver/verify.mjs
|
|
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
|
|
88
|
-
{ token: "connotation_", stages: CL, site: "driver/verify.mjs
|
|
89
|
-
{ token: "connotation_quote_unbound", stages: CL, site: "driver/verify.mjs
|
|
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
|
|
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: "
|
|
100
|
-
{ token: "
|
|
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
|
|
105
|
-
{ token: "coverage_form_engine_vocabulary", stages: ["register-digest"], site: "driver/verify.mjs
|
|
106
|
-
{ token: "coverage_form_axis_invalid", stages: ["register-digest"], site: "driver/verify.mjs
|
|
107
|
-
{ token: "coverage_no_status", stages: ["register-digest"], site: "driver/verify.mjs
|
|
108
|
-
{ token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs
|
|
109
|
-
{ token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs
|
|
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
|
|
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
|
|
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
|
|
128
|
-
{ token: "variantmodel_family_incomplete", stages: ["prelim-variants"], site: "driver/verify.mjs
|
|
129
|
-
{ token: "variantmodel_term_markup", stages: ["prelim-variants"], site: "driver/verify.mjs
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
174
|
-
{ token: "named_band_invalid", stages: ["register-unit"], site: "driver/verify.mjs
|
|
175
|
-
{ token: "named_band_collapsed", stages: ["register-unit"], site: "driver/verify.mjs
|
|
176
|
-
{ token: "band_block_unplanned", stages: ["register-unit"], site: "driver/verify.mjs
|
|
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
|
|
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
|
|
188
|
-
{ token: "plan_audit_missing", stages: ["narrative-refutation"], site: "driver/verify.mjs
|
|
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
|
|
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
|
|
303
|
-
{ validator: "report", site: "driver/verify.mjs
|
|
304
|
-
{ validator: "audit", site: "driver/verify.mjs
|
|
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
|
-
//
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
431
|
-
why: "The ruled-but-unbound row. Projected at verify.mjs:
|
|
432
|
-
{ code: "token_absent", mints: ["driver/connotation-search.mjs:
|
|
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:
|
|
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:
|
|
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:
|
|
444
|
-
why: "RENAMED, not namespaced: verify.mjs
|
|
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
|
|
475
|
-
// `axis_invalid` cause into its own family before
|
|
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:
|
|
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
|
|
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
|
//
|
package/driver/gateway.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.1-beta.
|
|
5
|
+
"version": "0.3.1-beta.4",
|
|
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": {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
import { statSync, openSync, readSync, closeSync } from "node:fs";
|
|
45
45
|
import { namedPoint } from "./portal-access.mjs";
|
|
46
|
+
import { recordedKeysFor } from "../shared/client-door.mjs"; // — how many issued keys a person holds, counted by the module that revokes them
|
|
46
47
|
|
|
47
48
|
import { readFlagSnapshot, engineFor, providersFor, postureDisagreement } from "./flag-snapshot.mjs";
|
|
48
49
|
// `isStale` is deliberately NOT imported any more: the age banner is retired (ruling,
|
|
@@ -282,7 +283,8 @@ export function authView({ mode = "", oidcIssuer = "", team = "", jwksUrl = "",
|
|
|
282
283
|
* `companies` maps each company key the profile store holds to its name. `localSignIn` says this install
|
|
283
284
|
* cannot hold a second person at all, which is what disables Add.
|
|
284
285
|
*/
|
|
285
|
-
export function accessView({ grants, viewer = null, companies = {}, grantsFile = null, localSignIn = false
|
|
286
|
+
export function accessView({ grants, viewer = null, companies = {}, grantsFile = null, localSignIn = false,
|
|
287
|
+
keysRevocable = false }) {
|
|
286
288
|
const tenants = grants?.tenants ?? {};
|
|
287
289
|
const everything = viewer?.everything === true;
|
|
288
290
|
const viewerOrgs = viewer?.genericOrgs ?? [];
|
|
@@ -330,6 +332,23 @@ export function accessView({ grants, viewer = null, companies = {}, grantsFile =
|
|
|
330
332
|
// whose permissions were considered and set to none.
|
|
331
333
|
listed: key !== undefined,
|
|
332
334
|
access: shown.map((p) => namedPoint(p, grants, companies)),
|
|
335
|
+
// WHETHER THIS ROW IS THE WHOLE OF THIS PERSON, from where the viewer stands. Every other field
|
|
336
|
+
// here is narrowed silently — a manager of one organisation sees that organisation's half of
|
|
337
|
+
// somebody and nothing says it is a half — and that was serviceable while the page could only
|
|
338
|
+
// ADD. It is not serviceable for a page that changes and removes: permissions belong to the
|
|
339
|
+
// person and not to a point, so a narrowed viewer must be told they cannot change them, and a
|
|
340
|
+
// removal they order must say it takes away their organisation rather than the install.
|
|
341
|
+
//
|
|
342
|
+
// The same test the write routes apply (`reachCovers`), computed here from the same `inside`
|
|
343
|
+
// that narrowed the list, so the sentence the page draws and the refusal the server would give
|
|
344
|
+
// cannot disagree.
|
|
345
|
+
covered: everything || all.every(inside),
|
|
346
|
+
// HOW MANY ISSUED KEYS THIS PERSON HOLDS — counted by the module that revokes them rather than by
|
|
347
|
+
// reading `connectKeys` here, so the count and the revocation cannot come to different answers
|
|
348
|
+
// about who a key belongs to. The removal confirmation needs it BEFORE the press: what happens to
|
|
349
|
+
// somebody's assistant is part of what the reader is agreeing to, and a page that found out
|
|
350
|
+
// afterwards would be telling them about it too late to matter.
|
|
351
|
+
keys: recordedKeysFor(grants, email).length,
|
|
333
352
|
dangling: r.dangling,
|
|
334
353
|
});
|
|
335
354
|
}
|
|
@@ -339,6 +358,12 @@ export function accessView({ grants, viewer = null, companies = {}, grantsFile =
|
|
|
339
358
|
// Add is offered to a manager, and never where local sign-in holds the install to one person.
|
|
340
359
|
canAdd: viewer?.permissions?.manage === true && !localSignIn,
|
|
341
360
|
localSignIn,
|
|
361
|
+
// Can an issued key be withdrawn on this installation at all? A connector started without a
|
|
362
|
+
// revocation list never loaded one, so a key minted through it cannot be called back and dies at its
|
|
363
|
+
// own expiry — `disablePlan` calls that state `lateArm` and refuses to strike the record for it.
|
|
364
|
+
// Stated here so the confirmation can say which of the two removals this would be before it happens,
|
|
365
|
+
// rather than correcting itself in the answer.
|
|
366
|
+
keysRevocable,
|
|
342
367
|
// Companies named in grants that no profile matches — the other typo direction. Install-wide, so it
|
|
343
368
|
// is shown to a person who sees everything and to nobody else.
|
|
344
369
|
unknownAccounts: everything ? [...unknownAccounts].sort() : [],
|
package/driver/portal-report.mjs
CHANGED
|
@@ -799,14 +799,28 @@ export function prepareReportForEmbed(html, { staff = false, poolRoot = null, fe
|
|
|
799
799
|
let ratedUnderDropped = 0;
|
|
800
800
|
out = out.replace(RATED_UNDER_RE, () => { ratedUnderDropped += 1; return ""; });
|
|
801
801
|
|
|
802
|
-
//
|
|
802
|
+
// ── THE ASK-AI BAND COMES OUT FOR EVERY READER ──────────────────────────────────────────────────
|
|
803
|
+
//
|
|
804
|
+
// It used to come out for clients only, on the ground that the band names the STAFF host and staff may
|
|
805
|
+
// see it. That left two Ask-AI controls on one staff screen — this band with the staff address and its
|
|
806
|
+
// setup steps, and the shell's own header button — and the owner ruled on 2026-09-15 that there is one.
|
|
807
|
+
// The renderer stopped drawing it in that same change.
|
|
808
|
+
//
|
|
809
|
+
// SO THIS STRIP IS NOT DEAD CODE, AND THAT IS THE REASON IT RUNS UNCONDITIONALLY. Every report
|
|
810
|
+
// rendered before that commit is served from its baked bytes and still carries a band naming the staff
|
|
811
|
+
// host. Removing the strip on the argument that nothing emits one any more would put that host back in
|
|
812
|
+
// front of whoever opens an archived run, staff and client alike, and the archive is where most of the
|
|
813
|
+
// reports are.
|
|
803
814
|
let mcpLeaks = 0;
|
|
804
815
|
let internalTailsDropped = 0;
|
|
805
816
|
let reviewerCodesDropped = 0;
|
|
817
|
+
out = stripBalanced(out, ASKAI_RE, "details", note).html;
|
|
806
818
|
if (!staff) {
|
|
807
|
-
out = stripBalanced(out, ASKAI_RE, "details", note).html;
|
|
808
819
|
// Assert, do not assume. `.askband` markup is nested, so a non-greedy match could stop early and
|
|
809
820
|
// leave the host behind in a sibling node. Anything surviving is redacted and counted.
|
|
821
|
+
//
|
|
822
|
+
// CLIENT-ONLY, and deliberately: this catches an MCP host anywhere in the document, not only inside
|
|
823
|
+
// the band, and the staff bytes elsewhere in a report are the designed rendering rather than a leak.
|
|
810
824
|
out = out.replace(MCP_HOST_RE, () => {
|
|
811
825
|
mcpLeaks += 1;
|
|
812
826
|
return "";
|