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
package/scripts/e2e.mjs
CHANGED
|
@@ -68,6 +68,10 @@ import { doorGates, resolveForDoor } from "../driver/door-gates.mjs";
|
|
|
68
68
|
// — the plan-row term screen, shared with the driver rather than restated here, so the harness
|
|
69
69
|
// can never drift from what the plan freeze and the executor enforce. PURE (no node imports).
|
|
70
70
|
import { entryTermIssues } from "../providers/_shared/term-shape.mjs";
|
|
71
|
+
// — the mechanical tier vocabulary, imported from the module that classifies into it rather than
|
|
72
|
+
// restated here: a floor naming a tier that does not exist must fail loudly rather than compare against
|
|
73
|
+
// a missing key, and the list must not drift if a tier is ever added. PURE (no node imports) too.
|
|
74
|
+
import { SHAPE_TIERS } from "../driver/band-shape.mjs";
|
|
71
75
|
// The owner's turnaround benchmarks. The BAND owns the number and a scenario file cannot carry
|
|
72
76
|
// one — see the lint rule below. Another pure leaf with zero imports of its own, for the same reason
|
|
73
77
|
// queue-markers.mjs is: nothing in this file may reach driver.config.mjs.
|
|
@@ -1256,6 +1260,70 @@ export function pathsAnOpDoesNotRead(scenarios) {
|
|
|
1256
1260
|
return out;
|
|
1257
1261
|
}
|
|
1258
1262
|
|
|
1263
|
+
/**
|
|
1264
|
+
* The office a band-shape floor row belongs to, from the row's own fields and never from a wider table.
|
|
1265
|
+
*
|
|
1266
|
+
* TWO SOURCES, IN THE ORDER band-shape.mjs POPULATES THEM. `registry` is the projection's own field and
|
|
1267
|
+
* it is derived, so it can read `unknown`; the record id carries the office as its second path segment.
|
|
1268
|
+
* Measured on a preserved dense clearance run, 2026-09-18: 289 of 289 rows carried `registry`, and every
|
|
1269
|
+
* one agreed with its id's segment. The fallback exists for the row that does not, and a row that
|
|
1270
|
+
* answers neither way is counted separately by the caller rather than dropped.
|
|
1271
|
+
*/
|
|
1272
|
+
export const bandRowOffice = (r) => {
|
|
1273
|
+
const reg = String(r?.registry ?? "").trim().toLowerCase();
|
|
1274
|
+
if (reg && reg !== "unknown") return reg;
|
|
1275
|
+
const m = String(r?.record_id ?? "").match(/^\/[^/]+\/([^/]+)\//);
|
|
1276
|
+
return m ? m[1].toLowerCase() : "";
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Open a clearance run's band shape for a floor op: the document, the mark check, and the two coverage
|
|
1281
|
+
* qualifications — or ONE sentence saying why no floor under it can be read.
|
|
1282
|
+
*
|
|
1283
|
+
* THE MARK IS CHECKED AGAINST `targets`, NOT AGAINST `targets[0]`. The driver shapes a band for the mark
|
|
1284
|
+
* under search PLUS its manifest variants, and it puts the job's own mark in that list unconditionally;
|
|
1285
|
+
* the first entry is the variant lane's to order. Membership is therefore the property that holds while
|
|
1286
|
+
* the variant lane moves, and it still catches the case this check is for — a shape derived for some
|
|
1287
|
+
* other mark entirely. A mismatch says so in its own words rather than folding into a missed floor,
|
|
1288
|
+
* because "the store's mark moved" and "the register thinned" need opposite answers.
|
|
1289
|
+
*/
|
|
1290
|
+
function bandShapeUnder(full, file, field) {
|
|
1291
|
+
const bad = (saw) => ({ bad: saw });
|
|
1292
|
+
if (!existsSync(full)) return bad(`${file} absent — the clearance lane derives this after every register band re-merge, so an absent `
|
|
1293
|
+
+ `shape is a derivation that did not happen or a lane that never merged a band. It is not a register that holds nothing.`);
|
|
1294
|
+
const doc = readJson(full);
|
|
1295
|
+
if (!doc) return bad(`${file} present but unparseable`);
|
|
1296
|
+
const want = field ? String(field).trim().toLowerCase() : null;
|
|
1297
|
+
if (!want) return bad("no mark given (path must be <file>:<MARK NAME>)");
|
|
1298
|
+
const targets = Array.isArray(doc.targets) ? doc.targets.filter((t) => String(t ?? "").trim()) : [];
|
|
1299
|
+
if (!targets.length) return bad(`${file} carries no targets — nothing in it says which mark the band was shaped for, so no floor under a mark can be read from it`);
|
|
1300
|
+
if (!targets.some((t) => String(t).trim().toLowerCase() === want)) {
|
|
1301
|
+
const shown = targets.slice(0, 6).map((t) => JSON.stringify(String(t))).join(", ");
|
|
1302
|
+
return bad(`this shape was derived for targets that do not include ${JSON.stringify(field)} — it was shaped for ${shown}`
|
|
1303
|
+
+ `${targets.length > 6 ? `, and ${targets.length - 6} more` : ""}. The store's mark moved, or this run is another mark's; either way no floor here is about the mark the scenario named.`);
|
|
1304
|
+
}
|
|
1305
|
+
const spots = Array.isArray(doc.blind_spots) ? doc.blind_spots : [];
|
|
1306
|
+
// The register would not answer: a refused slice, or one it would not state a total for. These make a
|
|
1307
|
+
// shortfall genuinely ambiguous, so they qualify a pass and may be the reason for a miss.
|
|
1308
|
+
const unread = spots.filter((b) => b?.kind === "refused-slice" || b?.kind === "uncountable-slice")
|
|
1309
|
+
.map((b) => `${b.count ?? "?"} ${b.kind}`);
|
|
1310
|
+
// The register DID answer, with a number larger than what was fetched. That is evidence of size, not a
|
|
1311
|
+
// gap in it — see the block at the ops — so it is named with the depth read and excuses nothing.
|
|
1312
|
+
const shallow = spots.filter((b) => b?.kind === "unenumerated-crowd")
|
|
1313
|
+
.map((b) => `${b.count ?? "?"} ${b.kind}${Array.isArray(b.read_depth) && b.read_depth.length ? ` (read ${b.read_depth.join("/")} deep)` : ""}`);
|
|
1314
|
+
const verdict = (ok, passBody, missBody) => {
|
|
1315
|
+
const parts = [ok ? passBody : missBody];
|
|
1316
|
+
if (unread.length) parts.push(ok
|
|
1317
|
+
? `floor met, but the register refused or would not total ${unread.join(", ")}, so this run covered less than it asked for`
|
|
1318
|
+
: `and the register refused or would not total ${unread.join(", ")}, so the shortfall may be what it would not answer rather than what it holds`);
|
|
1319
|
+
if (shallow.length) parts.push(ok
|
|
1320
|
+
? `${shallow.join(", ")} — the register stated more than the funnel fetched there`
|
|
1321
|
+
: `and ${shallow.join(", ")} — the register stated more than the funnel fetched there, which is a finding about the read depth and NOT a reason to lower the floor`);
|
|
1322
|
+
return { ok, saw: parts.join(" — ") };
|
|
1323
|
+
};
|
|
1324
|
+
return { doc, unread, shallow, verdict };
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1259
1327
|
function evalAssertion(a, runDir) {
|
|
1260
1328
|
const [file, field] = String(a.path ?? "").split(":");
|
|
1261
1329
|
const full = join(runDir, file || "");
|
|
@@ -1637,6 +1705,112 @@ function evalAssertion(a, runDir) {
|
|
|
1637
1705
|
saw: failed.length ? `${body}, and ${failed.length} term(s) FAILED to fetch, so the shortfall may be the fetch and not the register: ${failed.join(" · ")}`
|
|
1638
1706
|
: `${body} — the register returned fewer rows than the hit path needs, so screening, hydration and citation ran on less than this scenario exists to give them` };
|
|
1639
1707
|
}
|
|
1708
|
+
// ── · THE SAME TWO QUESTIONS, ASKED OF THE CLEARANCE LANE'S OWN ARTIFACT ──────────────────────
|
|
1709
|
+
//
|
|
1710
|
+
// WHY A SECOND PAIR RATHER THAN A SECOND PATH ON THE PAIR ABOVE. `register-counts.json` and
|
|
1711
|
+
// `register-records.json` are written by the KNOCKOUT lane and by nothing else. A clearance scenario
|
|
1712
|
+
// asserting the floors above therefore gets `absent` on both, and the sentence those absences print —
|
|
1713
|
+
// "a run whose register lane wrote nothing is not a run that found nothing" — is true of the file and
|
|
1714
|
+
// false of the run: the clearance lane read the band and recorded it under other names. The record it
|
|
1715
|
+
// keeps is `_driver/band-shape.json`, derived after every named-band re-merge, and it is a different
|
|
1716
|
+
// shape entirely rather than the same shape in another place, so it needs its own two ops.
|
|
1717
|
+
//
|
|
1718
|
+
// THREE POPULATIONS, THREE DIFFERENT NUMBERS, AND CONFLATING THEM IS THE TRAP THIS PAIR SITS IN.
|
|
1719
|
+
// Measured on a preserved dense clearance run, 2026-09-18, all three true of one band at one moment:
|
|
1720
|
+
//
|
|
1721
|
+
// totals.records 1554 every in-scope record the merged band holds
|
|
1722
|
+
// totals.by_tier.identical 245 tiered across the WHOLE band, class filter and all
|
|
1723
|
+
// floors.in_class_identical_or_near 289 identical AND near-identical, live, in-class only
|
|
1724
|
+
//
|
|
1725
|
+
// The band's own delivered depth record counted 2098, because it counts everything READ rather than
|
|
1726
|
+
// what stayed in scope. A floor written against one of these and read against another drifts with
|
|
1727
|
+
// nobody noticing, which is the failure the floors exist to catch happening to the floors themselves.
|
|
1728
|
+
// So every number these ops print names its population in words, and neither op mixes two.
|
|
1729
|
+
//
|
|
1730
|
+
// BOTH KEEP THE TWO RULES THE PAIR ABOVE IS BUILT ON. A size that was never taken is never a small
|
|
1731
|
+
// one: an absent shape, a shape with no `totals`, no `by_tier` or no floors array is reported as a
|
|
1732
|
+
// derivation that did not happen, never as a band that holds nothing. And a slice the register refused
|
|
1733
|
+
// or would not total is reduced coverage, named whichever way the floor goes.
|
|
1734
|
+
//
|
|
1735
|
+
// A CROWD THE FUNNEL COULD NOT ENUMERATE IS NOT THAT, AND MUST NEVER EXCUSE A SHORTFALL. Its
|
|
1736
|
+
// `total_hits` is LARGER than what was fetched, which is the register stating that the band is at
|
|
1737
|
+
// least that big — positive evidence of size, the opposite of a coverage gap. A dense band carries
|
|
1738
|
+
// these by construction (19 of them on the run measured above), so letting them soften a miss would
|
|
1739
|
+
// make every genuine thinning report as "the register may not have answered" for ever. They are named
|
|
1740
|
+
// with the depth actually read, and they change no verdict.
|
|
1741
|
+
if (a.op === "band-count-floor" || a.op === "band-records-floor") {
|
|
1742
|
+
const shape = bandShapeUnder(full, file, field);
|
|
1743
|
+
if (shape.bad) return { ok: false, saw: shape.bad };
|
|
1744
|
+
const value = a.value && typeof a.value === "object" && !Array.isArray(a.value) ? a.value : null;
|
|
1745
|
+
|
|
1746
|
+
if (a.op === "band-count-floor") {
|
|
1747
|
+
if (!value) return { ok: false, saw: "value must be an object of floors, e.g. {\"records\": 1000, \"by_tier\": {\"identical\": 120}}" };
|
|
1748
|
+
const unknown = Object.keys(value).filter((k) => k !== "records" && k !== "by_tier");
|
|
1749
|
+
if (unknown.length) return { ok: false, saw: `unknown floor key(s) ${unknown.map((k) => JSON.stringify(k)).join(", ")} — this op takes `
|
|
1750
|
+
+ `"records" (the whole in-scope band) and "by_tier" (one floor per mechanical tier). A key nothing reads is a floor nobody asserts.` };
|
|
1751
|
+
const totals = shape.doc.totals && typeof shape.doc.totals === "object" ? shape.doc.totals : null;
|
|
1752
|
+
if (!totals) return { ok: false, saw: `${file} carries no totals — the band was never sized, which is not a band whose size is zero` };
|
|
1753
|
+
|
|
1754
|
+
const saw = [], short = [], untaken = [];
|
|
1755
|
+
if (Object.hasOwn(value, "records")) {
|
|
1756
|
+
const want = value.records;
|
|
1757
|
+
if (!Number.isFinite(want)) return { ok: false, saw: `"records" must be a number, not ${JSON.stringify(want)}` };
|
|
1758
|
+
if (!Number.isFinite(totals.records)) untaken.push(`records: NOT TAKEN — totals.records is ${JSON.stringify(totals.records ?? null)}`);
|
|
1759
|
+
else {
|
|
1760
|
+
saw.push(`${totals.records} record(s) in the whole in-scope band (floor ${want})`);
|
|
1761
|
+
if (!(totals.records >= want)) short.push(`the in-scope band is ${totals.records} record(s), below the floor of ${want}`);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
if (Object.hasOwn(value, "by_tier")) {
|
|
1765
|
+
const tiers = value.by_tier && typeof value.by_tier === "object" && !Array.isArray(value.by_tier) ? value.by_tier : null;
|
|
1766
|
+
if (!tiers) return { ok: false, saw: "\"by_tier\" must be an object of tier floors, e.g. {\"identical\": 120}" };
|
|
1767
|
+
const byTier = totals.by_tier && typeof totals.by_tier === "object" ? totals.by_tier : null;
|
|
1768
|
+
if (!byTier) return { ok: false, saw: `${file} carries no totals.by_tier — the band was never tiered, which is not a band holding no identical marks` };
|
|
1769
|
+
for (const [tier, want] of Object.entries(tiers)) {
|
|
1770
|
+
if (!SHAPE_TIERS.includes(tier)) { untaken.push(`${tier}: NO SUCH TIER — the mechanical tiers are ${SHAPE_TIERS.join(", ")}`); continue; }
|
|
1771
|
+
if (!Number.isFinite(want)) return { ok: false, saw: `the floor for ${tier} must be a number, not ${JSON.stringify(want)}` };
|
|
1772
|
+
const n = byTier[tier];
|
|
1773
|
+
if (!Number.isFinite(n)) { untaken.push(`${tier}: NOT TAKEN — totals.by_tier.${tier} is ${JSON.stringify(n ?? null)}`); continue; }
|
|
1774
|
+
saw.push(`${n} record(s) tiered ${tier} across the whole band (floor ${want})`);
|
|
1775
|
+
if (!(n >= want)) short.push(`${tier} is ${n}, below the floor of ${want}`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (!saw.length && !short.length && !untaken.length)
|
|
1779
|
+
return { ok: false, saw: "no floor stated — give \"records\", \"by_tier\" or both; an op asserting nothing is an assert that looked at nothing" };
|
|
1780
|
+
if (untaken.length) return { ok: false, saw: `the band was never sized that way, so this scenario proved nothing about the size it exists for — ${untaken.join(" · ")}` };
|
|
1781
|
+
return shape.verdict(short.length === 0, saw.join(" · "),
|
|
1782
|
+
`${short.join(" · ")} — either the register has thinned out under this mark or the query narrowed; re-measure before moving the floor`);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// The population behind the count, and the two properties the size-dependent paths need from it:
|
|
1786
|
+
// enough rows to work on, and rows from more than one office.
|
|
1787
|
+
//
|
|
1788
|
+
// BOTH NUMBERS COME OUT OF THE SAME LIST, DELIBERATELY. The shape also carries a `by_registry` table
|
|
1789
|
+
// over the whole band, and taking the office span from there while taking the row count from the
|
|
1790
|
+
// floor list would report a multi-office band whose every floor row was one office — which is the
|
|
1791
|
+
// exact fail-open the knockout records floor names: a total-only floor is met while the property it
|
|
1792
|
+
// exists for lapses with every assert green. One list, both properties, or neither is asserted.
|
|
1793
|
+
const floors = value ?? {};
|
|
1794
|
+
const unknown = Object.keys(floors).filter((k) => k !== "records" && k !== "offices");
|
|
1795
|
+
if (unknown.length) return { ok: false, saw: `unknown floor key(s) ${unknown.map((k) => JSON.stringify(k)).join(", ")} — this op takes `
|
|
1796
|
+
+ `"records" (rows on the floor list) and "offices" (how many the list spans).` };
|
|
1797
|
+
const minRecords = Number.isFinite(floors.records) ? floors.records : 1;
|
|
1798
|
+
const minOffices = Number.isFinite(floors.offices) ? floors.offices : 1;
|
|
1799
|
+
const rows = shape.doc.floors?.in_class_identical_or_near;
|
|
1800
|
+
if (!Array.isArray(rows)) return { ok: false, saw: `${file} carries no floors.in_class_identical_or_near array — the one list that is `
|
|
1801
|
+
+ `complete by construction was never written, which is not a band holding no identical marks` };
|
|
1802
|
+
const offices = [...new Set(rows.map(bandRowOffice).filter(Boolean))].sort();
|
|
1803
|
+
// A row whose office cannot be read is counted and named rather than dropped: it narrows the span
|
|
1804
|
+
// this op can see, and a span that is narrow because the field is missing is not a narrow band.
|
|
1805
|
+
const officeless = rows.filter((r) => !bandRowOffice(r)).length;
|
|
1806
|
+
const met = rows.length >= minRecords && offices.length >= minOffices;
|
|
1807
|
+
const body = `${rows.length} live in-class identical/near-identical floor row(s) (floor ${minRecords}) across `
|
|
1808
|
+
+ `${offices.length} office(s) [${offices.join(", ") || "none"}] (floor ${minOffices})`
|
|
1809
|
+
+ (officeless ? `, and ${officeless} row(s) carry no office, so the span read here is a floor on the span, not a count of it` : "");
|
|
1810
|
+
return shape.verdict(met, body,
|
|
1811
|
+
`${body} — the band the clearance lane merged is smaller or narrower than this scenario exists to hand the size-dependent paths`);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1640
1814
|
// field ops
|
|
1641
1815
|
const fn = OPS[a.op];
|
|
1642
1816
|
if (!fn) return { ok: false, saw: `UNIMPLEMENTED op "${a.op}" — not passing by omission`, unimplemented: true };
|
package/scripts/env-audit.mjs
CHANGED
|
@@ -203,6 +203,22 @@ export function mergeEnvNameBindings(perFile) {
|
|
|
203
203
|
// read as `env[X]`, for the reason READ_RE gives.
|
|
204
204
|
const CONST_READ_RE = /(?<![.\w$])(?:process\??\.)?env(?:\?\.)?\[\s*([A-Z][A-Z0-9_]*)\s*\](?!\s*=[^=])/g;
|
|
205
205
|
|
|
206
|
+
// THE NAME HELD IN A TABLE AS A VALUE, which is a read the three readers above cannot see. A generic
|
|
207
|
+
// resolver takes the name out of a row and reads it — `{ env: "CLEAROTRON_CODEX_PATH", fallback: "codex" }`
|
|
208
|
+
// — so the variable is read on every run and spelled nowhere the audit was looking. Thirteen names are
|
|
209
|
+
// read exactly this way, and two of them are live deployment settings that no rule could ask for a row
|
|
210
|
+
// for, because every ratchet that would is keyed on the name being seen.
|
|
211
|
+
//
|
|
212
|
+
// MATCHED ON THE PROPERTY NAME, NEVER ON THE VALUE'S SHAPE, and that is the whole reason this is safe.
|
|
213
|
+
// A rule that took any uppercase string literal would take every message key, token and enum member in
|
|
214
|
+
// the tree and drown the audit in names nothing reads. The property names are the closed set the
|
|
215
|
+
// resolvers actually use; a new resolver that invents a fourth spelling is invisible again, which is a
|
|
216
|
+
// known limit of this shape rather than a defect in the rule — the same limit the accessor reader has.
|
|
217
|
+
//
|
|
218
|
+
// This is the third time this shape has hidden a read here: the accessor family was the first, `envFrom`
|
|
219
|
+
// the second, and a name held as a value is those two one remove further on.
|
|
220
|
+
const TABLE_NAME_RE = /(?<![.\w$])(?:env|envName|tokenEnv)\s*:\s*["']([A-Z][A-Z0-9_]*)["']/g;
|
|
221
|
+
|
|
206
222
|
export function namesRead(text, bindings = null) {
|
|
207
223
|
const found = new Set();
|
|
208
224
|
const stripped = stripCommentLines(text);
|
|
@@ -210,6 +226,8 @@ export function namesRead(text, bindings = null) {
|
|
|
210
226
|
for (let m; (m = READ_RE.exec(stripped));) found.add(m[1] || m[2]);
|
|
211
227
|
ACCESSOR_RE.lastIndex = 0;
|
|
212
228
|
for (let m; (m = ACCESSOR_RE.exec(stripped));) found.add(m[1] || m[2]);
|
|
229
|
+
TABLE_NAME_RE.lastIndex = 0;
|
|
230
|
+
for (let m; (m = TABLE_NAME_RE.exec(stripped));) found.add(m[1]);
|
|
213
231
|
// Resolved through the corpus map when one is supplied. Absent map ⇒ this half is simply off, which
|
|
214
232
|
// is what keeps the function pure and drivable on a single string.
|
|
215
233
|
if (bindings) {
|
|
@@ -357,6 +375,15 @@ export const EFFECT_CLASSES = Object.freeze({
|
|
|
357
375
|
credential: "authentication material; absent, the run refuses at preflight by name",
|
|
358
376
|
deployment: "where input and output live; the conclusion a run reaches is unchanged",
|
|
359
377
|
tuning: "how long or how hard a run tries; the conclusion a run reaches is unchanged",
|
|
378
|
+
// WHAT THE INSTALL WIZARD WROTE WHEN IT SET THIS MACHINE UP. Added because the classifier had computed
|
|
379
|
+
// this class for years and no declaration could say it: five names computed `setup` against a
|
|
380
|
+
// vocabulary with no word for it, so each was either undeclared or declared as the nearest wrong
|
|
381
|
+
// thing. A declaration that cannot be true is worse than none — it reads as considered.
|
|
382
|
+
//
|
|
383
|
+
// It is its own class rather than a flavour of `deployment` because the two answer different
|
|
384
|
+
// questions. `deployment` says where this machine keeps things; `setup` says what the installer
|
|
385
|
+
// found or made here, which is the fact an operator needs when a program has moved.
|
|
386
|
+
setup: "what the install wizard found or wrote when it set this machine up; the conclusion a run reaches is unchanged",
|
|
360
387
|
harness: "read only on a fixture, replay or self-test path; no production run reaches it",
|
|
361
388
|
});
|
|
362
389
|
|
package/scripts/env-classify.mjs
CHANGED
|
@@ -355,6 +355,19 @@ export const DEPLOYMENT_NAMES = new Set([
|
|
|
355
355
|
// Set by the install on the copy it starts after moving out of npx's cache, never by an operator. Its
|
|
356
356
|
// row declares `deployment`, and no shape matches it, so unlisted it would fall through to `tuning`.
|
|
357
357
|
"CLEAROTRON_RELOCATED",
|
|
358
|
+
// The two register ledgers, listed the day the scanner learned to read a name held in a table as a
|
|
359
|
+
// value — the same shape as the optional-chaining three below, one scanner widening later. Both name
|
|
360
|
+
// WHERE a file lives and resolve by an existence ladder over several directories, which is the
|
|
361
|
+
// definition `CLEAROTRON_JX_SUBCLASS_DB` was moved here on: a row whose own words name a place is
|
|
362
|
+
// `deployment`, and it reaches `tuning` only because `tuning` is the residual.
|
|
363
|
+
//
|
|
364
|
+
// GETTING THIS WRONG IS WORSE THAN THE BLINDNESS IT REPLACES. No deployed box sets either — the
|
|
365
|
+
// ledger module says so and explains why the default is resolved by existence rather than by name —
|
|
366
|
+
// so `tuning` with an empty set-site list is precisely the population step 3 deletes from. Making
|
|
367
|
+
// them visible without listing them here would move the billing-grade call ledger and the record
|
|
368
|
+
// ledger a "verified from the record" claim joins against from unseen to proposed for deletion.
|
|
369
|
+
"CLEAROTRON_REGISTER_CALL_LOG",
|
|
370
|
+
"CLEAROTRON_REGISTER_RECORD_LOG",
|
|
358
371
|
// Three names read only through optional chaining, so no catalogue check saw them until the scanner
|
|
359
372
|
// learned `?.`. Each row declares `deployment`, and no shape matches them (`_PORT$` does not take
|
|
360
373
|
// `_PORTS`), so unlisted each fell through to `tuning`, the bucket step 3 deletes from. The first is set
|
|
@@ -502,7 +515,12 @@ export function classify({ catalogue, sources, setup = setupNames(), readSites =
|
|
|
502
515
|
};
|
|
503
516
|
|
|
504
517
|
const cls = (name) => OVERRIDES[name] ?? (setup.has(name) ? "setup"
|
|
505
|
-
|
|
518
|
+
// `credential`, the word the declared vocabulary uses, NOT a second word for the same thing. This
|
|
519
|
+
// computed `vendor-credential` while no row could declare it, which is the same gap that left the
|
|
520
|
+
// install wizard's names undeclarable — one side of the contract saying a word the other side has
|
|
521
|
+
// no way to say. The rule settled with that one holds here: the classifier and the vocabulary say
|
|
522
|
+
// the SAME word, and a disagreement is fixed rather than frozen as an exception.
|
|
523
|
+
: VENDOR_RE.test(name) ? "credential"
|
|
506
524
|
// The listed audience is consulted BEFORE the shapes, so a renamed name keeps the class a human gave
|
|
507
525
|
// it rather than the one its new spelling happens to match.
|
|
508
526
|
: deploymentNames.has(name) ? "deployment"
|
|
@@ -678,7 +696,7 @@ function build() {
|
|
|
678
696
|
_what: "#1838 step 1 — every catalogued variable classified, and for every TUNING name the environments that have ever set it.",
|
|
679
697
|
_how: "Regenerate with: node scripts/env-classify.mjs --check (the production half is read from docs/architecture/env-set-in-production.txt, which needs `--gather-prod` on the production box to refresh).",
|
|
680
698
|
_values: "NO VALUE from any environment is read into this artifact. Names only.",
|
|
681
|
-
_counts: { rows: rows.length, setup: by("setup"), deployment: by("deployment"),
|
|
699
|
+
_counts: { rows: rows.length, setup: by("setup"), deployment: by("deployment"), credential: by("credential"), tuning: by("tuning") },
|
|
682
700
|
_stepThreePopulation: {
|
|
683
701
|
_what: "What step 3 may act on: a numeric default, and no environment anywhere sets it.",
|
|
684
702
|
_warning: "A candidate list, not a licence. Each still needs its read site read and its guard checked before deletion.",
|
|
@@ -47,7 +47,8 @@ import {
|
|
|
47
47
|
} from "node:fs";
|
|
48
48
|
import { join, dirname, relative, basename } from "node:path";
|
|
49
49
|
import { createHash } from "node:crypto";
|
|
50
|
-
import { driverDir } from "../shared/driver-dir.mjs";
|
|
50
|
+
import { driverDir } from "../shared/driver-dir.mjs";
|
|
51
|
+
import { PUBLISH_INPUTS } from "../driver/publish/publish-inputs.mjs"; //
|
|
51
52
|
import { tmpdir } from "node:os";
|
|
52
53
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
53
54
|
|
|
@@ -61,24 +62,33 @@ const FROZEN_FILES = [
|
|
|
61
62
|
{ path: "audit.md", why: "publish/index.mjs:1022 auditMd, the audit workbook source" },
|
|
62
63
|
{ path: "findings.json", why: "publish/index.mjs:715 readStore, the per-finding machine contract" },
|
|
63
64
|
{ path: "status.json", why: "publish/index.mjs:913 machineLedgerNote + markName" },
|
|
64
|
-
{ path: "case-law-findings.md", why: "
|
|
65
|
-
{ path: "common-law-grid.json", why: "
|
|
65
|
+
{ path: "case-law-findings.md", why: "`clPath` declared in index.mjs, the case-law section" },
|
|
66
|
+
{ path: "common-law-grid.json", why: "`commonLawJoinedTerms` declared in index.mjs, common-law coverage" },
|
|
66
67
|
// publish/index.mjs — the _driver sidecars it reads by name
|
|
67
68
|
{ path: "_driver/receipts.json", why: "publish/index.mjs:761 fetchReceipts" },
|
|
68
69
|
{ path: "_driver/senior-rights.json", why: "publish/index.mjs:787 seniorRights" },
|
|
69
70
|
{ path: "_driver/verdict.json", why: "publish/index.mjs:792 verdictInfo" },
|
|
70
71
|
{ path: "_driver/framework.json", why: "publish/index.mjs, the frozen band vocabulary the run was rated under" },
|
|
71
|
-
{ path: "_driver/register-plan.json", why: "publish/index.mjs:
|
|
72
|
-
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:
|
|
73
|
-
{ path: "_driver/enforcer-signals.json", why: "
|
|
72
|
+
{ path: "_driver/register-plan.json", why: "publish/index.mjs:901 scopeBasis" },
|
|
73
|
+
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:902 searchedJurisdictions, the fallback for register-plan" },
|
|
74
|
+
{ path: "_driver/enforcer-signals.json", why: "`esPath` declared in index.mjs" },
|
|
74
75
|
{ path: "_driver/predelivery-lint.json", why: "publish/index.mjs:172 lintSink" },
|
|
75
76
|
{ path: "_driver/escalation-state.json", why: "publish/index.mjs:173 escSink" },
|
|
76
|
-
{ path: "_driver/reasoning-integrity.json", why: "
|
|
77
|
+
{ path: "_driver/reasoning-integrity.json", why: "`integritySink` declared in index.mjs" },
|
|
77
78
|
{ path: "_driver/corrections-state.json", why: "publish/index.mjs:174 correctionsSink" },
|
|
78
|
-
{ path: "_driver/search-policy.json", why: "
|
|
79
|
+
{ path: "_driver/search-policy.json", why: "`searchPolicy` declared in index.mjs, level + stage label" },
|
|
79
80
|
{ path: "_driver/profile.json", why: "publish/index.mjs reads the frozen profile; report-registry.mjs:42 republishRun, customer key" },
|
|
80
81
|
];
|
|
81
82
|
|
|
83
|
+
// EVERY INPUT THE PUBLISHER DECLARES TRAVELS, taken from its own closed table rather than restated. The
|
|
84
|
+
// hand-kept list above drifted from that table: register-named-band.json, the recall receipt and the
|
|
85
|
+
// local-language lane's two files were declared publish inputs and were left behind, so a frozen republish
|
|
86
|
+
// drew no register section and no local-language row where the source run drew both. A declared input
|
|
87
|
+
// added later now travels without an edit here; one the publisher does not declare still needs a line above.
|
|
88
|
+
for (const path of Object.keys(PUBLISH_INPUTS)) {
|
|
89
|
+
if (!FROZEN_FILES.some((f) => f.path === path)) FROZEN_FILES.push({ path, why: "declared in publish/publish-inputs.mjs" });
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
// ── THE KNOCKOUT LANE IS A DIFFERENT WORKSPACE, AND report.md IS NOT IN IT ─
|
|
83
93
|
//
|
|
84
94
|
// A knockout run never writes report.md: for that lane the markdown is an OUTPUT of publish, not an
|
|
@@ -108,7 +118,7 @@ const KNOCKOUT_FILES = [
|
|
|
108
118
|
// knockout report render empty (publish/knockout.mjs:140-155). Named by stages-knockout.mjs:32,41.
|
|
109
119
|
{ path: "_driver/register-counts.json", why: "publish/knockout.mjs:140-155 counted figures + the Register column" },
|
|
110
120
|
{ path: "_driver/register-records.json", why: "stages-knockout.mjs:41 the terms behind the close-variation axis" },
|
|
111
|
-
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:
|
|
121
|
+
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:902 searchedJurisdictions, the fallback for register-plan" },
|
|
112
122
|
];
|
|
113
123
|
|
|
114
124
|
/** The allowlist for a template. One place, so a new template cannot half-exist. */
|
|
@@ -176,7 +186,7 @@ const VOLATILE = [
|
|
|
176
186
|
//
|
|
177
187
|
// 1. THE BIRTH CODENAME, IN CONTENT. `--codename` rebuilds the run's IDENTITY — the leaf, the runId, the
|
|
178
188
|
// directory — and the content keeps the old pair: `_receipt.context` in every record artifact reads
|
|
179
|
-
// "
|
|
189
|
+
// "clearance-<matter>-<codename>-register-unit-primary-sweep", and audit.md and status.json carry it in
|
|
180
190
|
// prose. driver/test/no-client-identifiers.test.mjs check 2 sweeps CONTENT as well as paths, so a
|
|
181
191
|
// tree renamed but not rewritten can never be committed — which is exactly what the old echo check
|
|
182
192
|
// reported, correctly, and could do nothing about.
|