clearotron 0.3.2-beta.8 → 0.3.2-beta.9
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/.env.example +34 -3
- package/CONTRIBUTING.md +8 -8
- package/INSTALL.md +6 -6
- package/SECURITY.md +3 -3
- package/bin/brandowner.mjs +3 -3
- package/bin/framework-preflight.mjs +1 -1
- package/bin/start.mjs +18 -4
- package/build-info.json +2 -2
- package/docs/DELIVERY.md +2 -1
- package/docs/INTAKE.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/architecture/03-run-lifecycle.md +6 -6
- package/docs/architecture/04-configuration-reference.md +6 -3
- package/docs/architecture/05-config-governance.md +6 -1
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/architecture/06-operations-runbook.md +3 -3
- package/docs/architecture/08-development-guide.md +6 -6
- package/docs/configuration.md +5 -5
- package/docs/decisions/0003-credential-model.md +1 -1
- package/docs/writing-standard.md +4 -0
- package/driver/CHANGELOG.md +48 -0
- package/driver/README.md +3 -3
- package/driver/binding-layers.mjs +1 -1
- package/driver/citation-census.json +3 -3
- package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/company-bundle.mjs +3 -3
- package/driver/compose-read.mjs +8 -14
- package/driver/consumption-ledger.mjs +2 -2
- package/driver/contract-arm2-baseline.json +2 -3
- package/driver/contract-dictation-registry.mjs +19 -19
- package/driver/contract-e3-backlog.mjs +19 -19
- package/driver/contract-e3-baseline.json +14 -14
- package/driver/contract-vocabulary.mjs +24 -17
- package/driver/deliver-trigger.sh +16 -16
- package/driver/demo-container.mjs +3 -3
- package/driver/dev-portal.mjs +3 -3
- package/driver/disposition-call.mjs +1 -1
- package/driver/doubt-ledger.mjs +2 -2
- package/driver/drainer-identity.mjs +34 -8
- package/driver/driver.config.mjs +95 -45
- package/driver/engine/mcp/README.md +1 -1
- package/driver/engine/mcp/dispositions-server.mjs +3 -3
- package/driver/engine/mcp/gather-config.mjs +9 -9
- package/driver/engine/mcp/perplexity-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +5 -5
- package/driver/enqueue-schema.mjs +6 -2
- package/driver/findings-model.mjs +5 -2
- package/driver/flag-snapshot.mjs +6 -3
- package/driver/form-neighbourhood.mjs +54 -7
- package/driver/framework.mjs +4 -4
- package/driver/gateway.mjs +12 -6
- package/driver/jx-lanes.mjs +2 -2
- package/driver/jx-units.mjs +1 -1
- package/driver/jx.mjs +30 -2
- package/driver/knockout-review-record.mjs +56 -4
- package/driver/known-conflicts.mjs +1 -1
- package/driver/named-band.mjs +1 -33
- package/driver/ordinary-words.mjs +51 -0
- package/driver/outbox-backoff.mjs +31 -16
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +2 -2
- package/driver/phase0.mjs +3 -3
- package/driver/pipeline-knockout.mjs +5 -5
- package/driver/pipeline.mjs +206 -68
- package/driver/placement-form.mjs +77 -1
- package/driver/placement-model.mjs +1 -1
- package/driver/portal-report.mjs +92 -5
- package/driver/portal-service.mjs +34 -8
- package/driver/portal-upstream.mjs +1 -1
- package/driver/preserve-merge.mjs +3 -3
- package/driver/product-rows.mjs +2 -2
- package/driver/products.mjs +1 -1
- package/driver/profiles/README.md +3 -3
- package/driver/profiles/demo-brand-owner.json +2 -2
- package/driver/profiles.mjs +55 -17
- package/driver/progress.mjs +18 -8
- package/driver/provider-usage.mjs +8 -8
- package/driver/publish/index.mjs +110 -5
- package/driver/publish/knockout.mjs +29 -4
- package/driver/publish/pool-admin.mjs +1 -1
- package/driver/publish/publish-inputs.mjs +18 -2
- package/driver/publish/render-knockout.mjs +115 -24
- package/driver/publish/render.mjs +153 -34
- package/driver/publish/search-depth.mjs +133 -4
- package/driver/publish/templates/report.css +60 -3
- package/driver/publish/xlsx.mjs +8 -1
- package/driver/queue-order.mjs +2 -2
- package/driver/recording-agreement.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/register-count.mjs +50 -5
- package/driver/register-coverage.mjs +67 -0
- package/driver/register-grant-vocabulary.mjs +1 -1
- package/driver/register-plan.mjs +19 -2
- package/driver/registry-fidelity.mjs +3 -3
- package/driver/repair-composers.mjs +1 -1
- package/driver/repair-contract.mjs +1 -1
- package/driver/replay-archive.mjs +6 -6
- package/driver/report-overview-record.mjs +2 -2
- package/driver/run-requirements.mjs +3 -3
- package/driver/runner.mjs +2 -2
- package/driver/scope-facts.mjs +20 -5
- package/driver/scope-ledger.mjs +5 -5
- package/driver/search-policy.mjs +22 -12
- package/driver/skills/README.md +15 -15
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/case-law-citation/SKILL.md +4 -4
- package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
- package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
- package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
- package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
- package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
- package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
- package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
- package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
- package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
- package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
- package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
- package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
- package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
- package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
- package/driver/skills/frame-diff/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +10 -7
- package/driver/skills/matter-frame/SKILL.md +3 -3
- package/driver/skills/narrative-refutation/SKILL.md +9 -9
- package/driver/skills/placement-inquiry/SKILL.md +5 -5
- package/driver/stage-context.mjs +1 -1
- package/driver/stages-knockout.mjs +4 -4
- package/driver/stages.mjs +53 -53
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +218 -92
- package/driver/surface-exit-verdict.mjs +58 -0
- package/driver/systemd/README.md +2 -2
- package/driver/systemd/clearotron-worker.service +1 -1
- package/driver/terminal-clamp.mjs +2 -0
- package/driver/usage-ledger.mjs +1 -1
- package/driver/variant-manifest-model.mjs +4 -4
- package/driver/verify-knockout.mjs +27 -0
- package/driver/verify.mjs +67 -6
- package/driver/whatif-queue.mjs +1 -1
- package/driver/wordlists/en.txt +63906 -0
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/README.md +1 -1
- package/mcp-server/lib/options.mjs +8 -7
- package/mcp-server/lib/plan.mjs +18 -2
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/lib/usage.mjs +3 -3
- package/mcp-server/lib/whatif.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -0
- package/package.json +12 -11
- package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
- package/portal-ui/dist/assets/{index-6jzO9HiX.js → index-EVaSo5-g.js} +1459 -482
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/README.md +1 -1
- package/providers/_shared/enumerate.mjs +6 -6
- package/providers/_shared/execute-plan.mjs +3 -3
- package/providers/_shared/ledger.mjs +119 -5
- package/providers/_shared/provider-text.mjs +2 -2
- package/providers/_shared/screen.mjs +2 -2
- package/providers/_shared/script-form.mjs +3 -3
- package/providers/_shared/territory-codes.mjs +23 -3
- package/providers/clarivate/README.md +1 -1
- package/providers/clarivate/src/capabilities.js +12 -12
- package/providers/clarivate/src/core.js +37 -43
- package/providers/corsearch/README.md +1 -1
- package/providers/corsearch/src/capabilities.js +5 -5
- package/providers/corsearch/src/core.js +3 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +1 -1
- package/providers/signa/README.md +1 -1
- package/providers/signa/src/capabilities.js +42 -49
- package/providers/signa/src/core.js +106 -29
- package/providers/uspto-local/src/sync.js +1 -1
- package/scripts/README.md +1 -0
- package/scripts/ask-ai-render-check.mjs +127 -1
- package/scripts/authority-boundary-probe.mjs +4 -4
- package/scripts/backfill-started-at.mjs +2 -2
- package/scripts/census-merge-driver.mjs +33 -2
- package/scripts/citation-anchor-report.mjs +181 -0
- package/scripts/dead-names.mjs +1 -1
- package/scripts/deprecate-below.mjs +66 -8
- package/scripts/drain-preflight.mjs +1 -1
- package/scripts/e2e.mjs +174 -0
- package/scripts/env-audit.mjs +27 -0
- package/scripts/env-classify.mjs +20 -2
- package/scripts/freeze-example-run.mjs +20 -10
- package/scripts/live-surface-check.mjs +124 -41
- package/scripts/markdown-link-check.mjs +1 -1
- package/scripts/merge-shape-check.mjs +242 -0
- package/scripts/mint-names-in-force.mjs +5 -5
- package/scripts/mint-offered-territories.mjs +72 -0
- package/scripts/mint-public-residue.mjs +2 -2
- package/scripts/mint-reference-strip-backlog.mjs +2 -2
- package/scripts/mint-suite-census.mjs +75 -2
- package/scripts/mint-writing-standard-backlog.mjs +2 -2
- package/scripts/purge-runs.mjs +7 -7
- package/scripts/reconcile-runs.mjs +2 -2
- package/scripts/release-approve-parked.mjs +20 -2
- package/scripts/release-await-cut.mjs +120 -1
- package/scripts/release-note-required.mjs +76 -8
- package/scripts/report-header-render-check.mjs +164 -0
- package/shared/brand.mjs +27 -0
- package/shared/connect-clients.mjs +39 -11
- package/shared/env-aliases.mjs +1 -1
- package/shared/identifier-scan.mjs +65 -9
- package/shared/identifier-sentinels.mjs +22 -0
- package/shared/names-in-force.mjs +3 -1
- package/shared/offered-territories.json +738 -0
- package/shared/pre-rename-spellings.mjs +53 -0
- package/shared/reference-guard-classes.mjs +40 -2
- package/shared/stdio-connect.mjs +39 -4
- package/shared/tree-commit.mjs +48 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples.md +0 -0
|
@@ -92,7 +92,29 @@ export function clearedNames(auditMd, recordIndex = {}) {
|
|
|
92
92
|
uri,
|
|
93
93
|
});
|
|
94
94
|
} else if (!/^NR\d+/.test(title) && /common-law/i.test(layer) && !/^\(none/i.test(title)) {
|
|
95
|
-
|
|
95
|
+
// ── A NAME IS A NAME AT A PLACE. THE READINGS ARE NOT NAMES ─────────────────────────────────
|
|
96
|
+
//
|
|
97
|
+
// The common-law layer carries two kinds of block under one heading style: a name somebody is
|
|
98
|
+
// trading under, and a reading of what the mark MEANS. The first has a `url` — it is a thing at a
|
|
99
|
+
// place a reader can go and look at. The second has none, because there is nothing to open: it is
|
|
100
|
+
// an etymology, a sensitivity, a piece of context.
|
|
101
|
+
//
|
|
102
|
+
// Both were listed as "Web and marketplace names", and a reading is a sentence, so the mark chip
|
|
103
|
+
// built for a name truncated it with an ellipsis and its right-hand column rendered empty.
|
|
104
|
+
// Measured on the delivered full country report: one such chip held 416px of content in a 200px
|
|
105
|
+
// box. Across every demo product, 7 of 31 blocks carried no url and every one of the 7 was a
|
|
106
|
+
// reading rather than a name.
|
|
107
|
+
//
|
|
108
|
+
// NOTHING IS LOST BY LEAVING THEM OUT, and that was measured rather than assumed: the report
|
|
109
|
+
// already carries each of those readings in the section written for them — the Quechua one appears
|
|
110
|
+
// five more times in the same document, under "Connotation & meaning" and again in the decision it
|
|
111
|
+
// asks the reader to take — and the audit workbook carries every block whatever this does.
|
|
112
|
+
//
|
|
113
|
+
// WHAT IT COSTS, stated rather than hidden: a marketplace name sighted with no URL recorded would
|
|
114
|
+
// not be listed here. None exists in any demo, and the row such a block produced was already a
|
|
115
|
+
// name beside an empty column. If one appears, the fix is to record where it was seen.
|
|
116
|
+
const url = field(block, "url");
|
|
117
|
+
if (url) out.web.push({ title, url, type: field(block, "type") });
|
|
96
118
|
}
|
|
97
119
|
}
|
|
98
120
|
return out;
|
|
@@ -104,9 +126,18 @@ export function clearedNames(auditMd, recordIndex = {}) {
|
|
|
104
126
|
* EVERY COUNTRY THE RUN READ, including the ones that came back clean — those are the whole point. A
|
|
105
127
|
* count keyed off the findings would list only countries with a conflict, which is the gap this closes.
|
|
106
128
|
*
|
|
107
|
-
*
|
|
129
|
+
* THREE-VALUED, in the house pattern outputMeta already uses for a stage's output: `null` in means the
|
|
130
|
+
* run has NO `_records/` store, and `null` comes back out — we cannot say how many records were read.
|
|
131
|
+
* An empty ARRAY is the other thing entirely: the store is there and holds nothing, which is a real zero
|
|
132
|
+
* and renders as one. Collapsing the two is what this fixes; they arrived here as the same `[]` and the
|
|
133
|
+
* renderer could only drop the section, so a register that archives nothing read as a register nobody
|
|
134
|
+
* searched.
|
|
135
|
+
*
|
|
136
|
+
* @param {string[]|null} recordFileNames the `_records/` listing, named `<cc>-<id>.json`; null = no store
|
|
137
|
+
* @returns {object|null} counts by country code, or null when the run cannot say
|
|
108
138
|
*/
|
|
109
139
|
export function recordsByCountry(recordFileNames = []) {
|
|
140
|
+
if (recordFileNames === null) return null;
|
|
110
141
|
const out = {};
|
|
111
142
|
for (const name of recordFileNames) {
|
|
112
143
|
const cc = (String(name).match(/^([a-z]{2})-/i) || [])[1];
|
|
@@ -115,6 +146,16 @@ export function recordsByCountry(recordFileNames = []) {
|
|
|
115
146
|
return out;
|
|
116
147
|
}
|
|
117
148
|
|
|
149
|
+
/** Band record ids → `<office>-<id>` names, one per distinct record, in the archive's own naming. PURE. */
|
|
150
|
+
export function recordNamesFromIds(ids) {
|
|
151
|
+
const out = new Set();
|
|
152
|
+
for (const id of ids ?? []) {
|
|
153
|
+
const m = /^\/mark\/([a-z]{2,4})\/(.+)$/i.exec(String(id ?? ""));
|
|
154
|
+
if (m) out.add(`${m[1].toLowerCase()}-${m[2]}`);
|
|
155
|
+
}
|
|
156
|
+
return [...out];
|
|
157
|
+
}
|
|
158
|
+
|
|
118
159
|
/** Marketplace, web, reputation and meaning checks, from the deterministic grid the tools wrote. PURE. */
|
|
119
160
|
export function sweepCounts(commonLawGrid, auditMd = "") {
|
|
120
161
|
const cells = Array.isArray(commonLawGrid?.cells) ? commonLawGrid.cells : [];
|
|
@@ -148,6 +189,81 @@ export function courtDecisionsState(caseLawText) {
|
|
|
148
189
|
return "found";
|
|
149
190
|
}
|
|
150
191
|
|
|
192
|
+
/**
|
|
193
|
+
* WHICH TERRITORIES THE RUN SEARCHED, AND WHICH IT COULD NOT REACH — from the register PLAN. PURE.
|
|
194
|
+
*
|
|
195
|
+
* The plan is the authority on what was asked of the register; the `_records/` archive is only the
|
|
196
|
+
* authority on what came back and was kept. Reading "what was searched" off the archive is why a
|
|
197
|
+
* provider that keeps no records read as a provider nobody asked: no records, no countries, no section.
|
|
198
|
+
* Both halves are on the plan whether or not anything is archived — `entries[].regions` is what it will
|
|
199
|
+
* query, and `deferred_coverage` is what this provider does not cover, carrying the reason for each.
|
|
200
|
+
*
|
|
201
|
+
* Null for a run with no plan to read, which is an archived or legacy run: that is "cannot say", and it
|
|
202
|
+
* is not the same answer as a plan that named nothing.
|
|
203
|
+
*
|
|
204
|
+
* @param {object|null} plan the parsed `register-plan.json`, or null when there is none
|
|
205
|
+
*/
|
|
206
|
+
export function planTerritoriesOf(plan) {
|
|
207
|
+
if (!plan || typeof plan !== "object") return null;
|
|
208
|
+
const entryRegions = (plan.entries ?? []).flatMap((e) => (Array.isArray(e?.regions) ? e.regions : []));
|
|
209
|
+
// `plan.regions` is the older shape and is the fallback, not a second source: a plan carrying entries
|
|
210
|
+
// has already said which regions it will query, and unioning the two would report a region the
|
|
211
|
+
// compiler moved OUT of `regions` into the deferral list as though it had been searched.
|
|
212
|
+
const searched = entryRegions.length ? [...new Set(entryRegions.map(String))]
|
|
213
|
+
: [...new Set((Array.isArray(plan.regions) ? plan.regions : []).map(String))];
|
|
214
|
+
// A WORLDWIDE PLAN THAT NAMES NO REGION CANNOT SAY WHERE IT REACHED. On a provider that takes no region
|
|
215
|
+
// list, worldwide compiles to queries with no jurisdiction clause at all — the whole database — so the
|
|
216
|
+
// plan names nothing, and read as "searched these" that is "searched nowhere": the section that says
|
|
217
|
+
// where a search reached disappeared on exactly the searches that reached furthest. Null is the answer
|
|
218
|
+
// the plan can actually give, and the page then reads the countries off what the register returned.
|
|
219
|
+
// Only when nothing was deferred: a worldwide order with deferrals is not an unrestricted sweep.
|
|
220
|
+
if (plan.scope_basis === "worldwide" && !searched.length
|
|
221
|
+
&& !(Array.isArray(plan.deferred_coverage) && plan.deferred_coverage.length)) return null;
|
|
222
|
+
const unreached = (Array.isArray(plan.deferred_coverage) ? plan.deferred_coverage : [])
|
|
223
|
+
.map((d) => ({ jurisdiction: String(d?.jurisdiction ?? "").trim(), reason: String(d?.reason ?? "").trim() }))
|
|
224
|
+
.filter((d) => d.jurisdiction);
|
|
225
|
+
return { searched, unreached };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* HOW DEEP THE LOCAL-LANGUAGE INVESTIGATION ACTUALLY WENT, against what the matter configured. PURE.
|
|
230
|
+
*
|
|
231
|
+
* The engine can run this investigation shallower than the account asked for, and until now it said so
|
|
232
|
+
* in exactly one place: a sentence a model wrote in the Methodology paragraph. The redesigned report
|
|
233
|
+
* replaces that paragraph with counts and named rows, so a run that went shallow said so on no page at
|
|
234
|
+
* all. This is the field behind that row.
|
|
235
|
+
*
|
|
236
|
+
* DERIVED FROM THE RUN'S OWN RECORD, NEVER FROM PROSE, and not derived here either: the caller hands in
|
|
237
|
+
* what `deriveLaneDepthVerdicts` produced, which is the one author of asked-versus-ran and reads the
|
|
238
|
+
* frozen lane sidecar against the slices that executed. A second opinion computed in the publish path
|
|
239
|
+
* would be a second answer to a question the engine has already answered.
|
|
240
|
+
*
|
|
241
|
+
* THE FOUR STATES, and the order they are decided in matters:
|
|
242
|
+
* not-in-scope no lane was asked for anything — a plain clearance, or every lane switched off
|
|
243
|
+
* not-run lanes were asked and none of them ran
|
|
244
|
+
* ran-shallow a lane fell short of its ask, or was asked and did not run while another did
|
|
245
|
+
* ran every lane that was asked ran at the depth it was asked for
|
|
246
|
+
*
|
|
247
|
+
* `ran: null` IS NOT `candidates`. A lane whose slices settle to nothing readable cannot say what it
|
|
248
|
+
* delivered, and the jx verdicts are careful to report that as unestablished rather than as the lesser
|
|
249
|
+
* depth. Folding it to `ran` here would put that claim back on a client's page, so it counts as short.
|
|
250
|
+
*
|
|
251
|
+
* @param {object|null} verdicts per-lane `{asked, ran, shortfall}` from deriveLaneDepthVerdicts
|
|
252
|
+
*/
|
|
253
|
+
export function localLanguageDepth(verdicts) {
|
|
254
|
+
if (!verdicts || typeof verdicts !== "object") return { state: "not-in-scope", lanes: {} };
|
|
255
|
+
const lanes = {};
|
|
256
|
+
for (const [lane, v] of Object.entries(verdicts)) {
|
|
257
|
+
lanes[lane] = { configured: v?.asked ?? null, achieved: v?.ran ?? null };
|
|
258
|
+
}
|
|
259
|
+
const asked = Object.entries(verdicts).filter(([, v]) => v?.asked && v.asked !== "off");
|
|
260
|
+
if (!asked.length) return { state: "not-in-scope", lanes };
|
|
261
|
+
const ran = asked.filter(([, v]) => v?.ran);
|
|
262
|
+
if (!ran.length) return { state: "not-run", lanes };
|
|
263
|
+
const short = asked.some(([, v]) => v?.shortfall === true || !v?.ran);
|
|
264
|
+
return { state: short ? "ran-shallow" : "ran", lanes };
|
|
265
|
+
}
|
|
266
|
+
|
|
151
267
|
/** Was the name searched in a non-Latin script? Read off the plan's own terms, never asserted. PURE. */
|
|
152
268
|
export function localScriptSearched(registerPlan) {
|
|
153
269
|
const entries = Array.isArray(registerPlan?.entries) ? registerPlan.entries : [];
|
|
@@ -159,7 +275,16 @@ export function localScriptSearched(registerPlan) {
|
|
|
159
275
|
*
|
|
160
276
|
* @returns {{schemaVersion: number, cleared: object, counts: object}}
|
|
161
277
|
*/
|
|
162
|
-
export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNames = [], commonLawGrid = null, caseLawText = "", registerPlan = null } = {}) {
|
|
278
|
+
export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNames = [], bandRecordIds = null, commonLawGrid = null, caseLawText = "", registerPlan = null, laneDepthVerdicts = null } = {}) {
|
|
279
|
+
// `recordFileNames: null` travels all the way to the page — see recordsByCountry. The default stays `[]`
|
|
280
|
+
// because that is "the caller said nothing", not "the store is absent"; only the publish path knows the
|
|
281
|
+
// difference and it is the one producer.
|
|
282
|
+
//
|
|
283
|
+
// A PROVIDER THAT ARCHIVES NO RECORDS STILL RETURNED THEM. Its search answer is the band, and every
|
|
284
|
+
// record in it carries its office in its own id (`/mark/<office>/<id>`). So where there is no archive
|
|
285
|
+
// the band is what was read: counted by record, filed by office. Without it a run that read 785 register
|
|
286
|
+
// records reported "cannot say" and the report carried no register row and no country at all.
|
|
287
|
+
if (recordFileNames === null && Array.isArray(bandRecordIds)) recordFileNames = recordNamesFromIds(bandRecordIds);
|
|
163
288
|
const cleared = clearedNames(auditMd, recordIndex);
|
|
164
289
|
const groups = {};
|
|
165
290
|
for (const key of CLEARED_GROUPS) groups[key] = 0;
|
|
@@ -169,10 +294,14 @@ export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNa
|
|
|
169
294
|
cleared: { register: cleared.register, web: cleared.web, groups },
|
|
170
295
|
counts: {
|
|
171
296
|
recordsByCountry: recordsByCountry(recordFileNames),
|
|
172
|
-
recordsRead: recordFileNames.length,
|
|
297
|
+
recordsRead: recordFileNames === null ? null : recordFileNames.length,
|
|
173
298
|
sweep: sweepCounts(commonLawGrid, auditMd),
|
|
174
299
|
localScriptSearched: localScriptSearched(registerPlan),
|
|
175
300
|
courtDecisions: courtDecisionsState(caseLawText),
|
|
301
|
+
// `localScriptSearched` above answers whether the spellings were searched; this answers how deep
|
|
302
|
+
// the investigation went against what was configured. Two different facts, and the row the report
|
|
303
|
+
// reserves is for the second.
|
|
304
|
+
localLanguage: localLanguageDepth(laneDepthVerdicts),
|
|
176
305
|
},
|
|
177
306
|
};
|
|
178
307
|
}
|
|
@@ -113,6 +113,39 @@
|
|
|
113
113
|
.panel.actions p,.panel.actions li{font-size:13.5px;color:var(--slate);line-height:1.55}
|
|
114
114
|
.panel.actions ul{margin:0;padding-left:20px} .panel.actions li{margin:3px 0}
|
|
115
115
|
|
|
116
|
+
/* ── THE BOARD'S OWN SECTION STRIP, lifted from the approved mock rather than written here ────────
|
|
117
|
+
Five entries and the board's own labels, marked not to print because the board marks it so: a strip
|
|
118
|
+
that follows the reader down the page is a control, and paper has nothing for it to follow. Every
|
|
119
|
+
anchor it names is drawn by the renderer above it.
|
|
120
|
+
|
|
121
|
+
THE SECTION NUMBER IS DRAWN AND HIDDEN, and that is the board's doing, not an oversight here: the
|
|
122
|
+
mock emits `<span class="num"></span>` on every section and then sets `display:none` on it further
|
|
123
|
+
down its own stylesheet. Carried faithfully, so the delivered document and the approved one hold the
|
|
124
|
+
same elements — this changes no pixel, and a reader looking for a visible number will not find one
|
|
125
|
+
in the board either. */
|
|
126
|
+
/* THE BREADCRUMB IS A ROW OF THE HEADER, not a bar under it (owner, 2026-09-18).
|
|
127
|
+
It used to be a SIBLING of .rep-stickyhead, pinned on its own at top:var(--tb-h,52px) — and
|
|
128
|
+
--tb-h is set nowhere in this product, so the fallback was a guess at a bar that measures ~46px:
|
|
129
|
+
content showed through the slit between the two, and z-index:20 put the strip UNDER the header's
|
|
130
|
+
100 whenever the guess was wrong. The strip is now emitted inside .rep-stickyhead, so the header
|
|
131
|
+
and the breadcrumb are one sticky surface that pins and unpins together and can never gap or
|
|
132
|
+
overlap, whatever the bar measures or how it wraps.
|
|
133
|
+
No background and no bottom hairline of its own: the wrapper carries the blurred surface and the
|
|
134
|
+
single edge, and the border-top here is the divider between the two rows. It is on .strip rather
|
|
135
|
+
than on any wrapper because sectionStrip() emits NOTHING when fewer than two of its sections are
|
|
136
|
+
live — a wrapper would leave a stray line on those reports.
|
|
137
|
+
The gutter matches the topbar's, so the first entry lines up under the back button. */
|
|
138
|
+
.strip{display:flex;gap:4px;align-items:center;padding:4px max(26px,calc((100% - 1120px)/2)) 6px;
|
|
139
|
+
border-top:1px solid var(--line);font:600 12px/1 'Satoshi','Helvetica Neue',Arial,sans-serif;
|
|
140
|
+
letter-spacing:.04em;overflow-x:auto;scrollbar-width:none}
|
|
141
|
+
.strip::-webkit-scrollbar{display:none}
|
|
142
|
+
.strip a{display:inline-flex;align-items:center;gap:7px;padding:7px 10px;border-radius:999px;color:#6b5d50;text-decoration:none;white-space:nowrap}
|
|
143
|
+
.strip a i{width:8px;height:8px;border-radius:50%;border:1.5px solid currentColor;box-sizing:border-box}
|
|
144
|
+
.strip a.done{color:#4c7a4c}
|
|
145
|
+
.strip a.done i{background:#4c7a4c;border-color:#4c7a4c}
|
|
146
|
+
.strip a.now{color:#250902;background:rgba(0,0,0,.06)}
|
|
147
|
+
.strip a.now i{border-color:#860F09;border-width:3px}
|
|
148
|
+
.sec .num{display:none}
|
|
116
149
|
.sec{margin:56px 0 18px;display:flex;align-items:baseline;gap:15px;border-top:2px solid var(--ink);padding-top:14px}
|
|
117
150
|
.sec .num{font-family:var(--mono);font-size:13px;color:var(--crimson);font-weight:600}
|
|
118
151
|
.sec h2{font-weight:900;font-size:25px;margin:0;letter-spacing:-.015em}
|
|
@@ -280,7 +313,26 @@
|
|
|
280
313
|
|
|
281
314
|
|
|
282
315
|
/* cross-region rights-holder rows (.rrow) + secondary region groups (.rgroup) */
|
|
316
|
+
/* A PANEL THAT SCROLLS SIDEWAYS SAYS SO, the same way the knockout's tables do.
|
|
317
|
+
`overflow-y:auto` alone makes the OTHER axis compute to auto as well, so this panel has scrolled
|
|
318
|
+
horizontally for as long as it has scrolled vertically — and drawn nothing to say it. Measured on a
|
|
319
|
+
delivered global preliminary at 390px: 377px of rows in a 336px box, 41px unreachable, no bar.
|
|
320
|
+
A reader who cannot see that it drags does not drag it, and a holder's jurisdiction sits in the part
|
|
321
|
+
that is off the edge.
|
|
322
|
+
|
|
323
|
+
THE TWO INSTRUCTIONS CANNOT SIT TOGETHER, and that was measured rather than reasoned on the
|
|
324
|
+
knockout: setting the standard scrollbar-width alongside the pseudo-elements makes the engine take
|
|
325
|
+
the standard path and ignore them, and on the engine that publishes these reports the standard path
|
|
326
|
+
draws an overlay bar with no layout height at all — both together 0px, the pseudo-elements alone
|
|
327
|
+
9px, the standard properties alone 0px. So the standard ones sit behind a support query the engines
|
|
328
|
+
carrying the pseudo-elements never enter, and each engine gets exactly one instruction. Written the
|
|
329
|
+
way anyone would write it — belt and braces — it renders nothing and the stylesheet gives no sign. */
|
|
283
330
|
.keypanel{overflow-y:auto}
|
|
331
|
+
.keypanel::-webkit-scrollbar{height:9px;-webkit-appearance:none}
|
|
332
|
+
.keypanel::-webkit-scrollbar-track{background:transparent}
|
|
333
|
+
.keypanel::-webkit-scrollbar-thumb{background:var(--faint);border-radius:5px}
|
|
334
|
+
@supports not selector(::-webkit-scrollbar){
|
|
335
|
+
.keypanel{scrollbar-width:thin;scrollbar-color:var(--faint) transparent}}
|
|
284
336
|
.rrow{border-top:1px solid var(--line)}
|
|
285
337
|
.rrow:first-of-type{border-top:0}
|
|
286
338
|
.rrow>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px;padding:8px 4px}
|
|
@@ -344,9 +396,11 @@
|
|
|
344
396
|
and internal notes (.int-note, hidden above) are the only two things print ever drops. */
|
|
345
397
|
details>*:not(summary){display:block!important}
|
|
346
398
|
details>summary{list-style:none;cursor:default}
|
|
347
|
-
/* `details.searched`
|
|
348
|
-
|
|
349
|
-
|
|
399
|
+
/* `details.searched` keeps its summary in print like the region rows and the secondary groups. The
|
|
400
|
+
reason has CHANGED and the behaviour has not: the summary used to carry the section's own heading,
|
|
401
|
+
so dropping it in print lost the heading over the counts. The heading is now an <h2> in a section
|
|
402
|
+
of its own, as the approved board draws it, and print cannot lose it. What the summary still
|
|
403
|
+
carries is the sub-label over the counts, which is worth printing on its own. What it must not print is the ▸/▾ toggle, which on paper is a
|
|
350
404
|
right-pointing arrow over content that is already fully expanded. Found by the print check, which
|
|
351
405
|
walks a rendered page and refuses any disclosure that is in neither list; the public suite does
|
|
352
406
|
not run it, so the commit that added this fold could ship the half it did not think about. */
|
|
@@ -519,6 +573,9 @@
|
|
|
519
573
|
details.searched[open]>summary::before{content:'▾'}
|
|
520
574
|
details.searched .gcount{margin-left:auto;color:var(--slate);font-weight:500;font-size:12.5px}
|
|
521
575
|
details.searched .gbody{padding:0 18px 12px}
|
|
576
|
+
.wchips{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
|
577
|
+
details.searched .wstate{font-weight:600}
|
|
578
|
+
details.searched .wnote{display:block;margin-top:4px;font-size:12.5px;line-height:1.45;color:var(--slate)}
|
|
522
579
|
details.searched .row{display:grid;grid-template-columns:200px 1fr;gap:12px;padding:7px 0;border-top:1px solid var(--line);font-size:13.5px}
|
|
523
580
|
details.searched .k{font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);padding-top:3px}
|
|
524
581
|
.openrows,.provwrap{margin-top:12px;border-top:1px solid var(--line);padding-top:10px}
|
package/driver/publish/xlsx.mjs
CHANGED
|
@@ -629,8 +629,15 @@ export async function buildAudit(contract, auditParsed, outPath, mark = '', fm =
|
|
|
629
629
|
// THE ROW IS AN ORDINARY COVERAGE ROW, built by the same function as every other, so it carries the
|
|
630
630
|
// same four columns and takes the same State colour. It is not a second shape and not a new sheet.
|
|
631
631
|
// The words in it are the run record's own: nothing here composes prose.
|
|
632
|
+
//
|
|
633
|
+
// AND THE PROBES THE RUN DECIDED ON AND DID NOT MAKE, on the same footing. The recall net's owner
|
|
634
|
+
// budget drops its excess with the party and the probe id recorded; until this row the excess reached
|
|
635
|
+
// no reader, so a search that decided on nineteen ownership checks and made five read as one that
|
|
636
|
+
// made the checks it wanted. Same builder, same four columns, same State colour: this is not a second
|
|
637
|
+
// shape and not a new sheet, and the words are the run receipt's own.
|
|
632
638
|
addSheet(wb, 'Coverage & gaps', COVERAGE_COLS,
|
|
633
|
-
[...coverageRows(coverage), ...coverageRows(contract?.droppedConditions || [])
|
|
639
|
+
[...coverageRows(coverage), ...coverageRows(contract?.droppedConditions || []),
|
|
640
|
+
...coverageRows(contract?.undispatchedProbes || [])], (row, _d, kept) => {
|
|
634
641
|
if (!kept.has('State')) return;
|
|
635
642
|
const st = row.getCell('State'); const f = STATE_FILL[String(st.value).trim()];
|
|
636
643
|
if (f) { st.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF' + f } }; st.font = { bold: true }; }
|
package/driver/queue-order.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// parse set, or prelim-driver.path's watch — and reordering must NOT wake the runner.
|
|
22
22
|
|
|
23
23
|
import { readFileSync, writeFileSync, renameSync, mkdirSync, readdirSync } from "node:fs";
|
|
24
|
-
import { join, dirname } from "node:path";
|
|
24
|
+
import { join, dirname } from "node:path"; import { studioDirFor } from "../shared/pre-rename-spellings.mjs";
|
|
25
25
|
|
|
26
26
|
export function queueOrderPath(qdir) { return join(dirname(qdir), ".queue-order.json"); }
|
|
27
27
|
|
|
@@ -69,7 +69,7 @@ export function queueDirsUnder(workspaceRoot) {
|
|
|
69
69
|
try { names = readdirSync(workspaceRoot); } catch { return out; }
|
|
70
70
|
for (const n of names) {
|
|
71
71
|
if (!n.startsWith("workspace-")) continue;
|
|
72
|
-
const q = join(workspaceRoot, n, "
|
|
72
|
+
const q = join(studioDirFor(join(workspaceRoot, n)), "queue");
|
|
73
73
|
try { readdirSync(q); out.push(q); } catch { /* no queue in this workspace */ }
|
|
74
74
|
}
|
|
75
75
|
return out.sort();
|
|
@@ -70,7 +70,7 @@ export const SURFACE = "surface";
|
|
|
70
70
|
* performs anyway. Since the warm and cold rungs DERIVE the tool name from `TOOL_WRITTEN_ARTIFACTS`,
|
|
71
71
|
* so adding that row — step 1 of every conversion — makes both rungs name the record tool, and (a) went
|
|
72
72
|
* quiet for it whatever the dispatch said. Measured on conversion 3 before its dispatch was touched: the
|
|
73
|
-
* grant carried `
|
|
73
|
+
* grant carried `record_clearance_variants`, the dispatch did not mention it, both repair rungs did, and (a)
|
|
74
74
|
* was silent. The guard asked a question the conversion's own bookkeeping answered — the tautology shape
|
|
75
75
|
* `b04d6d58` and the `RECORDING_TOOLS` non-derivation both exist to remove.
|
|
76
76
|
*
|
|
@@ -1107,7 +1107,7 @@ export function planSubQueries({ plan = null, execution = null, scopeTerritories
|
|
|
1107
1107
|
// ── THE IN-SCOPE SWEEP'S OWN STATE — what decides whether a narrow was OWED at all ───────────────
|
|
1108
1108
|
//
|
|
1109
1109
|
// `subQueryState` returns `none` for a territory with no entry of its own, and that one value covers
|
|
1110
|
-
// two opposite situations. The doctrine (`skills/
|
|
1110
|
+
// two opposite situations. The doctrine (`skills/clearance-register/SKILL.md`, Recipe 1 §2b) says a slice
|
|
1111
1111
|
// gets its own `register_enumerate` ONLY on the guarded crowd-narrow path — when Step 2, the
|
|
1112
1112
|
// region-scoped in-scope sweep, returned `incomplete` and a major may sit in the un-paged remainder.
|
|
1113
1113
|
// When Step 2 returns `enumerated` the complete set provably contains every in-scope slice and the
|
|
@@ -336,10 +336,24 @@ export async function countRegisterHits({
|
|
|
336
336
|
try { r = await counter(term, p, { classes: scoped, regions }); }
|
|
337
337
|
catch (e) { r = { ok: false, total: null, reason: `count threw: ${String(e?.message ?? e).slice(0, 200)}` }; }
|
|
338
338
|
const ok = Boolean(r?.ok) && Number.isFinite(r?.total);
|
|
339
|
+
// A DISCLOSED APPROXIMATION IS AN ANSWER, AND IT WAS BEING READ AS A FAILURE. When the register
|
|
340
|
+
// says "more than ten thousand", it has answered — it has answered with a floor instead of a
|
|
341
|
+
// count, which is a different thing from not answering at all. `ok` is false for it because the
|
|
342
|
+
// total is deliberately not finite (an approximation must never become a number), so everything
|
|
343
|
+
// downstream saw a dead probe and the client was told the count was not available: the same words
|
|
344
|
+
// the report uses when the register could not be reached. The direction was inverted, too —
|
|
345
|
+
// saturation is a finding about the mark, and the denser the crowd the more certainly it was
|
|
346
|
+
// suppressed.
|
|
347
|
+
const approximated = !ok && Boolean(r?.ok) && r?.approximate === true && Number.isFinite(r?.floor);
|
|
348
|
+
// THE REFUSAL FIELD IS `cause` ON THIS PROVIDER AND `reason` ON OTHERS, and reading only one of
|
|
349
|
+
// them is why an honest refusal arrived as the fallback string with the receipts ledger recording
|
|
350
|
+
// "unknown". The same mismatch is described above `listRecords`, where it was fixed; this is the
|
|
351
|
+
// other half of it. Both are read, in the order a caller would expect.
|
|
352
|
+
const refusal = r?.reason ?? r?.cause ?? null;
|
|
339
353
|
// A CLIENT-SIDE refusal (the provider's query language cannot express this question — a term
|
|
340
354
|
// carrying parentheses, an office outside its vocabulary) is
|
|
341
355
|
// deterministic: no retry and no resume can change it, so it settles rather than re-billing.
|
|
342
|
-
const deterministic = !ok && (isCapabilityGap(
|
|
356
|
+
const deterministic = !ok && !approximated && (isCapabilityGap(refusal) || Boolean(r?.unsupported));
|
|
343
357
|
if (ledgerPath) {
|
|
344
358
|
try {
|
|
345
359
|
appendFileSync(ledgerPath, JSON.stringify({
|
|
@@ -350,13 +364,18 @@ export async function countRegisterHits({
|
|
|
350
364
|
...(form ? { term, variant_form: form } : {}),
|
|
351
365
|
classes: scoped, regions, provider, probe: r?.probe ?? null,
|
|
352
366
|
ok, total: ok ? r.total : null, took_ms: Date.now() - started,
|
|
353
|
-
|
|
367
|
+
// An approximation is recorded as what it is. It billed and it answered, so a receipt
|
|
368
|
+
// calling it a failure with cause "unknown" misreports both halves.
|
|
369
|
+
...(approximated ? { approximate: true, floor: r.floor } : {}),
|
|
370
|
+
...(ok || approximated ? {} : { cause: String(refusal ?? "unknown").slice(0, 300) }),
|
|
354
371
|
}) + "\n");
|
|
355
372
|
} catch { /* receipts are best-effort, never fatal — same as the sweep ledger */ }
|
|
356
373
|
}
|
|
357
|
-
return
|
|
358
|
-
|
|
359
|
-
|
|
374
|
+
if (ok) return { total: r.total };
|
|
375
|
+
// `total` stays null for an approximation and that is the rule, not an oversight: the floor is not
|
|
376
|
+
// a count and may never be filled in as one. What travels beside it is the disclosure.
|
|
377
|
+
if (approximated) return { total: null, approximate: true, floor: r.floor };
|
|
378
|
+
return { total: null, unavailable: String(refusal ?? "the count could not be taken").slice(0, 300), ...(deterministic ? { deterministic: true } : {}) };
|
|
360
379
|
};
|
|
361
380
|
|
|
362
381
|
// The aggregate: the provider's EXACT predicate, once per generated form, summed.
|
|
@@ -503,12 +522,38 @@ export function countsForMark(doc, name) {
|
|
|
503
522
|
* Office CODES, not names: `scope.regions` on the same artifact is already codes, and the alternative
|
|
504
523
|
* is inventing a display layer that has to stay in step with the office vocabulary of six providers.
|
|
505
524
|
*/
|
|
525
|
+
/**
|
|
526
|
+
* The register's own floor for a count it stopped taking, or null.
|
|
527
|
+
*
|
|
528
|
+
* ONE READING FOR EVERY PAGE THAT SHOWS A COUNT. The glance line printed the floor and the counts table,
|
|
529
|
+
* the coverage clause and the workbook beside it still printed "not available", so one report said two
|
|
530
|
+
* different things about the same cell. A floor counts only with the register's flag AND the number —
|
|
531
|
+
* a flag with no number says no more than "unknown" does.
|
|
532
|
+
*/
|
|
533
|
+
export function disclosedFloor(c) {
|
|
534
|
+
return c?.approximate === true && Number.isFinite(c?.floor) ? c.floor : null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** A floor as every page prints it: the register's own figure, and no sentence around it. */
|
|
538
|
+
export const moreThan = (floor) => `more than ${floor.toLocaleString("en-US")}`;
|
|
539
|
+
|
|
506
540
|
export function countLine(entry) {
|
|
507
541
|
if (!entry?.counts) return null;
|
|
508
542
|
const parts = COUNT_PREDICATES.map((p) => {
|
|
509
543
|
const c = entry.counts[p.key];
|
|
510
544
|
const word = p.glance ?? p.label.toLowerCase();
|
|
511
545
|
if (Number.isFinite(c?.total)) return `${c.total} ${word}`;
|
|
546
|
+
// A REGISTER THAT ANSWERS WITH A FLOOR HAS ANSWERED. Some registers stop counting and report
|
|
547
|
+
// "more than ten thousand" rather than a total; that is the register's own figure and it is what
|
|
548
|
+
// the client is shown, as a number. Rendering it as "not available" beside a register that could
|
|
549
|
+
// not be reached at all tells a reader the same thing about two different facts, and the one the
|
|
550
|
+
// reader would act on — go and look elsewhere — is wrong for this one.
|
|
551
|
+
//
|
|
552
|
+
// No sentence, no adjective: the rest of this line is figures and what they counted, and a
|
|
553
|
+
// qualification written here would be the only prose on it.
|
|
554
|
+
if (disclosedFloor(c) !== null) return `${word}: ${moreThan(disclosedFloor(c))}`;
|
|
555
|
+
// Left for a register this deployment could not reach or could not ask. Those have no figure at
|
|
556
|
+
// all, which is what this phrase now means and the only thing it means.
|
|
512
557
|
return `${word}: not available`;
|
|
513
558
|
});
|
|
514
559
|
const scope = entry.classScope === "all-classes"
|
|
@@ -250,3 +250,70 @@ export function registerReachRefusal(uncovered, registerLabel = null) {
|
|
|
250
250
|
return `${names} ${uncovered.length === 1 ? "is" : "are"} not available with ${where}`
|
|
251
251
|
+ ` — remove ${uncovered.length === 1 ? "it" : "them"} to run this search.`;
|
|
252
252
|
}
|
|
253
|
+
|
|
254
|
+
// ── WHAT THE FORM SHOULD OFFER: EVERY PLACE A REGISTER THIS PRODUCT SUPPORTS CAN SEARCH ────────────────
|
|
255
|
+
//
|
|
256
|
+
// The form offered 37 places with no rule behind them: Bulgaria and Greece, but not Denmark, Portugal,
|
|
257
|
+
// Vietnam or Colombia, while a worldwide search on the wider register already swept every one of its 186
|
|
258
|
+
// offices. The rule is the registers' own reach: a place is offered when at least one supported register
|
|
259
|
+
// provider can search it, decided by the SAME resolution the plan compiler runs (`resolveRegions`, one code
|
|
260
|
+
// at a time), so the offer cannot promise a place the compiler would defer everywhere. What THIS install's
|
|
261
|
+
// register does not reach is marked per deployment by `coveredTerritoryNames` and the door's refusal — the
|
|
262
|
+
// offer is the product's; the marking is the install's.
|
|
263
|
+
//
|
|
264
|
+
// A provider with no enumerable coverage (a global aggregator declaring `covered: null`) widens nothing:
|
|
265
|
+
// "everywhere" is not a list, and letting it in would offer every code the engine holds, including places
|
|
266
|
+
// no register answers for.
|
|
267
|
+
//
|
|
268
|
+
// NAMES ARE NEVER WRITTEN HERE. A place the form already offers keeps its shipped label; any other is named
|
|
269
|
+
// by the runtime's standard English region names (CLDR), the same data the territory vocabulary already
|
|
270
|
+
// resolves typed names against. A code with neither — the regional systems no label exists for — is left
|
|
271
|
+
// out and listed, never given a name composed in code. Provider extension codes (X-, ZZ) are not places.
|
|
272
|
+
//
|
|
273
|
+
// @returns {Promise<{ offered: {code: string, name: string}[], unnamed: string[] }>} regions first, then
|
|
274
|
+
// countries by name.
|
|
275
|
+
export async function searchableTerritories() {
|
|
276
|
+
const [{ KNOWN_JURISDICTION_CODES, canonicalJurisdictionCode }, { normalizeTerritory }, { PROVIDER_CAPABILITIES }, { resolveRegions }]
|
|
277
|
+
= await Promise.all([import("./jurisdiction-codes.mjs"), import("../providers/_shared/territory-codes.mjs"),
|
|
278
|
+
import("./register-capabilities.mjs"), import("./register-plan.mjs")]);
|
|
279
|
+
const enumerable = Object.values(PROVIDER_CAPABILITIES).filter((caps) => Array.isArray(caps?.offices?.covered));
|
|
280
|
+
const shippedName = new Map(LABELS_OFFERED_BEFORE_THE_RULE.map((n) => [canonicalJurisdictionCode(normalizeTerritory(n) ?? ""), n]));
|
|
281
|
+
let cldr = null;
|
|
282
|
+
try { cldr = new Intl.DisplayNames(["en"], { type: "region" }); } catch { cldr = null; }
|
|
283
|
+
const offered = [], unnamed = [];
|
|
284
|
+
const seen = new Set();
|
|
285
|
+
for (const raw of KNOWN_JURISDICTION_CODES) {
|
|
286
|
+
const code = canonicalJurisdictionCode(raw);
|
|
287
|
+
if (!code || seen.has(code) || /^(X.|ZZ)$/.test(code)) continue;
|
|
288
|
+
seen.add(code);
|
|
289
|
+
const searched = enumerable.some((caps) => {
|
|
290
|
+
const { regions, deferred } = resolveRegions([code], caps);
|
|
291
|
+
return deferred.length === 0 && regions.length > 0;
|
|
292
|
+
});
|
|
293
|
+
if (!searched) continue;
|
|
294
|
+
let name = shippedName.get(code) ?? null;
|
|
295
|
+
if (!name && cldr) { try { const n = cldr.of(code); if (n && n !== code && !/^unknown/i.test(n)) name = n; } catch { /* no name */ } }
|
|
296
|
+
if (!name) { unnamed.push(code); continue; }
|
|
297
|
+
offered.push({ code, name });
|
|
298
|
+
}
|
|
299
|
+
// Regions first, in the order the form already showed them; then countries by name.
|
|
300
|
+
const regional = new Set(["EU", "BX", "AP", "OA", "EA", "WO"]);
|
|
301
|
+
const shippedAt = (t) => { const i = LABELS_OFFERED_BEFORE_THE_RULE.indexOf(t.name); return i < 0 ? Infinity : i; };
|
|
302
|
+
offered.sort((a, b) => (regional.has(b.code) - regional.has(a.code))
|
|
303
|
+
|| (regional.has(a.code) ? shippedAt(a) - shippedAt(b) : 0) || a.name.localeCompare(b.name, "en"));
|
|
304
|
+
return { offered, unnamed: unnamed.sort() };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// THE 37 LABELS THE FORM CARRIED BEFORE THIS RULE, frozen. They are shipped strings, so each place keeps
|
|
308
|
+
// the label a client already reads — "Hong Kong", "Macau", "Turkey", where the runtime's standard names
|
|
309
|
+
// differ — and the two regional systems whose only names are these. Frozen HERE rather than read from the
|
|
310
|
+
// form's live list, because that list is now minted FROM this function, and a rule that read its own
|
|
311
|
+
// output would lose its labels the first time the file was regenerated from scratch.
|
|
312
|
+
export const LABELS_OFFERED_BEFORE_THE_RULE = Object.freeze([
|
|
313
|
+
"European Union", "Benelux", "African Regional (ARIPO)",
|
|
314
|
+
"United States", "United Kingdom", "Ireland", "France", "Germany", "Spain", "Italy", "Netherlands",
|
|
315
|
+
"Switzerland", "Austria", "Sweden", "Norway", "Poland", "Bulgaria", "Greece", "Turkey", "Canada",
|
|
316
|
+
"Mexico", "Brazil", "Argentina", "China", "Hong Kong", "Taiwan", "Macau", "Japan", "South Korea",
|
|
317
|
+
"Singapore", "India", "Thailand", "Australia", "New Zealand", "United Arab Emirates", "Saudi Arabia",
|
|
318
|
+
"South Africa",
|
|
319
|
+
]);
|
|
@@ -69,5 +69,5 @@ export function grantVocabularySentence(provider = null) {
|
|
|
69
69
|
+ `whole, and the frozen plan's entries are fetched by the executor, not by you. So reaching for one `
|
|
70
70
|
+ `of these means the query is wrong, not that a capability is missing: go back to the sweep and fix `
|
|
71
71
|
+ `its scope. This list is the tools THIS provider serves; another deployment's differs, and `
|
|
72
|
-
+ `\`skills/
|
|
72
|
+
+ `\`skills/clearance-register/providers/<name>.md\` is where the provider-specific vocabulary lives.`;
|
|
73
73
|
}
|
package/driver/register-plan.mjs
CHANGED
|
@@ -743,7 +743,24 @@ export function excludeHouseElement(manifest, house) {
|
|
|
743
743
|
return { manifest: next, confirmation, refused: null };
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
-
export function compileRegisterPlan({ manifest, job, form = null, skillVersion = "", capabilities = null, unavailableOffices = [] }) {
|
|
746
|
+
export function compileRegisterPlan({ manifest, job, form = null, skillVersion = "", capabilities = null, unavailableOffices = [], houseElement = null }) {
|
|
747
|
+
// ── AN EXCLUDED ELEMENT'S FORM BAND MUST BE UNREACHABLE, NOT MERELY UNASKED-FOR ─────────────────
|
|
748
|
+
//
|
|
749
|
+
// THE DEFECT THIS CLOSES, found by following the seam rather than by a failing arm. `bandFor` falls
|
|
750
|
+
// back to `elements[0]` when it cannot find the element it was asked for. The house-element exclusion
|
|
751
|
+
// changes `dominant_element` to the remainder's dominant word — a word the form neighbourhood, derived
|
|
752
|
+
// earlier from the original manifest, may carry no band for. The lookup would then MISS and the
|
|
753
|
+
// fallback would hand back the first element's band, which is the house element's: the exclusion would
|
|
754
|
+
// appear to work, `dominant_element` would read correctly on the plan, and the one-letter mutation
|
|
755
|
+
// floor of the very element being excluded would compile anyway. Silently, and it is the whole flood.
|
|
756
|
+
//
|
|
757
|
+
// So the element is removed from the form document here, where every `bandFor` call in this compile
|
|
758
|
+
// reads it. Unreachable beats un-asked-for: a fallback cannot select what is not there.
|
|
759
|
+
if (houseElement && form?.elements) {
|
|
760
|
+
const lcHouse = String(houseElement).trim().toLowerCase();
|
|
761
|
+
const kept = form.elements.filter((e) => String(e?.element ?? "").trim().toLowerCase() !== lcHouse);
|
|
762
|
+
form = { ...form, elements: kept };
|
|
763
|
+
}
|
|
747
764
|
const classes = (job?.classes ?? []).map(String).filter(Boolean);
|
|
748
765
|
if (!classes.length) throw new Error("register_plan_classes_missing: a plan is always class-scoped — compile with the matter's in-scope Nice classes");
|
|
749
766
|
const caps = capabilities ?? null;
|
|
@@ -962,7 +979,7 @@ export function compileRegisterPlan({ manifest, job, form = null, skillVersion =
|
|
|
962
979
|
// visible from the definition rather than inferred from an absence.
|
|
963
980
|
const STRIPPED_CATEGORIES = new Set(["phonetic", "transliteration", "visual"]);
|
|
964
981
|
// — THE DOCTRINE'S OWN DISPATCH TABLE, NOW BINDABLE. The universal-categories table
|
|
965
|
-
// (
|
|
982
|
+
// (clearance-variants SKILL.md) states the mode per tag: `exact-element` sweeps default, `plural-root`
|
|
966
983
|
// is a root (the contains match is its whole purpose), and `formative-family` is "never exact-only".
|
|
967
984
|
// Until the enum accepted these tags the mandate bound to nothing — measured: three root-shaped
|
|
968
985
|
// strings dispatched exact, 4/2/4 records, the family they exist to reach retrieved zero times.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// ordering, and renewal/expiry cycle arithmetic (renewals fall at year 10/20/… from registration — the
|
|
16
16
|
// check that catches "registration 2013 … renewed 2025" from the document alone).
|
|
17
17
|
|
|
18
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, openSync, readSync, closeSync } from "node:fs";
|
|
18
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, openSync, readSync, closeSync } from "node:fs"; import { runPrefixSpellings } from "../shared/pre-rename-spellings.mjs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { driverDir, ensureDriverDir } from "../shared/driver-dir.mjs"; // — one definition of where `_driver/` is
|
|
21
21
|
import { ledgerPath, runRecordLogPath, ledgerDeprecationNotice, retiredGlobalRecordLogNotice }
|
|
@@ -34,8 +34,8 @@ function stripGatewayNs(s) {
|
|
|
34
34
|
return typeof s === "string" ? s.replace(/^agent:[^:]+:/, "") : "";
|
|
35
35
|
}
|
|
36
36
|
function rowMatchesRun(row, runPrefix) {
|
|
37
|
-
return stripGatewayNs(row.sessionKey).startsWith(
|
|
38
|
-
|| stripGatewayNs(row.sessionId ?? "").startsWith(
|
|
37
|
+
return runPrefixSpellings(runPrefix).some((rp) => stripGatewayNs(row.sessionKey).startsWith(rp)
|
|
38
|
+
|| stripGatewayNs(row.sessionId ?? "").startsWith(rp)); // either spelling: a run resumed across the rename
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
|
|
@@ -586,7 +586,7 @@ export const REPAIR_COMPOSERS = [
|
|
|
586
586
|
//
|
|
587
587
|
// `samplesForStage` IS REQUIRED, and for the reason `*:lint-repair`'s note above gives rather than the
|
|
588
588
|
// one it looks like: a `stage: "*"` composer is walked for EVERY recording stage, so the fixed sample's
|
|
589
|
-
// `record_frame_diff` is read as an order handed to blind-frame, matter-frame,
|
|
589
|
+
// `record_frame_diff` is read as an order handed to blind-frame, matter-frame, clearance-variants,
|
|
590
590
|
// report-overview, report-card and doubt-closure — six ordered-but-not-granted findings that are
|
|
591
591
|
// artifacts of the SAMPLE, not of the tree. At dispatch the tool is always the walking stage's own,
|
|
592
592
|
// because it is derived from `out`. Removing this hook reproduces all six.
|
|
@@ -116,7 +116,7 @@ export function failingTarget(lastFail, files = []) {
|
|
|
116
116
|
if (!named) return null;
|
|
117
117
|
const list = (Array.isArray(files) ? files : [files]).filter(Boolean).map(String);
|
|
118
118
|
if (list.length <= 1) return list[0] ?? null;
|
|
119
|
-
// longest suffix match wins: "x/register-findings.md" identifies ".../
|
|
119
|
+
// longest suffix match wins: "x/register-findings.md" identifies ".../clearance-search/x/register-findings.md"
|
|
120
120
|
const hit = list.find((f) => f === named || f.endsWith(`/${named}`) || named.endsWith(`/${f}`));
|
|
121
121
|
return hit ?? null;
|
|
122
122
|
}
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
// an INTENDED fix (then --update on the merged result).
|
|
23
23
|
//
|
|
24
24
|
// Env: CLEAROTRON_REPLAY_ROOTS colon-separated corpus roots
|
|
25
|
-
// (default: <workspaceRoot>/workspace-*/studio/
|
|
26
|
-
// CLEAROTRON_REPLAY_SNAPSHOT snapshot path (default: ~/.
|
|
25
|
+
// (default: <workspaceRoot>/workspace-*/studio/clearance-search — live slugs + archive/)
|
|
26
|
+
// CLEAROTRON_REPLAY_SNAPSHOT snapshot path (default: ~/.clearance-replay-snapshot.json)
|
|
27
27
|
|
|
28
28
|
import "../shared/env-local.mjs"; // — FIRST: the CLEAROTRON_* translation must land before any
|
|
29
29
|
// module-top capture below it evaluates. A call in this file's BODY
|
|
30
30
|
// would run too late — that was the repair that left this open.
|
|
31
31
|
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
32
|
-
import { join, basename } from "node:path";
|
|
32
|
+
import { join, basename } from "node:path"; import { studioDirFor } from "../shared/pre-rename-spellings.mjs";
|
|
33
33
|
import { DRIVER_DIR, driverDir } from "../shared/driver-dir.mjs"; //
|
|
34
34
|
import { homedir } from "node:os";
|
|
35
35
|
import { validators } from "./verify.mjs";
|
|
@@ -62,7 +62,7 @@ function looksLikeRunDir(p) {
|
|
|
62
62
|
return n.includes(DRIVER_DIR) || n.some((f) => FILE_CHECKS[f]);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Corpus roots → sorted run dirs. Layout per root (a workspace's studio/
|
|
65
|
+
// Corpus roots → sorted run dirs. Layout per root (a workspace's studio/clearance-search):
|
|
66
66
|
// <slug>/<date>-<codename>/ (live slugs)
|
|
67
67
|
// archive/<YYYY-MM>/<slug>/<date>-<codename>/ (archived)
|
|
68
68
|
export function discoverRuns(roots) {
|
|
@@ -219,12 +219,12 @@ function main() {
|
|
|
219
219
|
const args = new Set(process.argv.slice(2));
|
|
220
220
|
// ON-DISK NAME, NOT A PRODUCT NAME: an install that never set the variable already has this file, so
|
|
221
221
|
// renaming the default points the reader at one that does not exist. Ruling.
|
|
222
|
-
const snapshotPath = process.env.CLEAROTRON_REPLAY_SNAPSHOT || join(homedir(), ".prelim-replay-snapshot.json");
|
|
222
|
+
const snapshotPath = process.env.CLEAROTRON_REPLAY_SNAPSHOT || [join(homedir(), ".prelim-replay-snapshot.json"), join(homedir(), ".clearance-replay-snapshot.json")].find((f, i) => i === 1 || existsSync(f)); // the file the install already has wins
|
|
223
223
|
const roots = process.env.CLEAROTRON_REPLAY_ROOTS
|
|
224
224
|
? process.env.CLEAROTRON_REPLAY_ROOTS.split(":").filter(Boolean)
|
|
225
225
|
: names(config.workspaceRoot)
|
|
226
226
|
.filter((d) => config.agentIdFromWorkspaceName(d) != null)
|
|
227
|
-
.map((d) => join(config.workspaceRoot, d
|
|
227
|
+
.map((d) => studioDirFor(join(config.workspaceRoot, d)));
|
|
228
228
|
|
|
229
229
|
const runDirs = discoverRuns(roots);
|
|
230
230
|
if (!runDirs.length) {
|