clearotron 0.2.4 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -2
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +62 -38
- package/bin/brandowner.mjs +18 -169
- package/bin/clearotron.mjs +3 -1
- package/bin/connect.mjs +28 -19
- package/bin/disconnect.mjs +3 -3
- package/bin/example.mjs +7 -7
- package/bin/framework-preflight.mjs +49 -0
- package/bin/grant.mjs +151 -93
- package/bin/onboard.mjs +220 -138
- package/bin/start.mjs +146 -137
- package/bin/stop.mjs +2 -2
- package/bin/update.mjs +1 -1
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +2 -2
- package/docs/E2E.md +12 -2
- package/docs/ONBOARDING.md +1 -1
- package/docs/PORTAL.md +14 -13
- package/docs/SECURITY.md +23 -24
- package/docs/architecture/04-configuration-reference.md +12 -5
- package/docs/architecture/05-config-governance.md +7 -7
- package/docs/architecture/07-quality-and-audit.md +1 -1
- package/docs/architecture/08-development-guide.md +5 -0
- package/docs/configuration.md +118 -0
- package/docs/decisions/0004-documentation-structure.md +2 -2
- package/docs/decisions/0006-what-the-public-repository-carries.md +2 -2
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/cancel.mjs +27 -0
- package/driver/case-law-sources.mjs +3 -3
- package/driver/company-bundle.mjs +261 -0
- package/driver/compare.mjs +1 -1
- package/driver/compose-read.mjs +2 -2
- package/driver/config-inventory.mjs +2 -2
- package/driver/contract-audit.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/contract-vocabulary.mjs +1 -1
- package/driver/declination-call.mjs +1 -1
- package/driver/deliver-trigger.sh +3 -3
- package/driver/dev-portal.mjs +3 -1
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-tool.mjs +1 -1
- package/driver/doc-constants.mjs +1 -1
- package/driver/drain-posture.mjs +2 -2
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +44 -9
- package/driver/effective-scope.mjs +30 -1
- package/driver/effort-model.mjs +6 -6
- package/driver/engine/CONTRACT.md +2 -2
- package/driver/engine/anthropic-agent.mjs +11 -11
- package/driver/engine/jx-turn.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +29 -4
- package/driver/engine/mcp/recording-server.mjs +73 -1
- package/driver/engine/openai-agent.mjs +1 -1
- package/driver/engine/probe.mjs +28 -4
- package/driver/enqueue-schema.mjs +23 -3
- package/driver/findings-model.mjs +2 -2
- package/driver/flag-snapshot.mjs +2 -2
- package/driver/floor-duty.mjs +2 -2
- package/driver/frame-diff-model.mjs +1 -1
- package/driver/framework-preflight.mjs +143 -0
- package/driver/gateway.mjs +9 -1
- package/driver/hit-list.mjs +1 -1
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +1 -1
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/knockout-review-record.mjs +435 -0
- package/driver/order-probe.mjs +1 -1
- package/driver/outbox-backoff.mjs +2 -2
- package/driver/owner-use-check.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +105 -8
- package/driver/pipeline.mjs +81 -30
- package/driver/plain-register.mjs +77 -3
- package/driver/portal-access.mjs +141 -74
- package/driver/portal-config-view.mjs +59 -70
- package/driver/portal-report.mjs +4 -4
- package/driver/portal-service.mjs +348 -141
- package/driver/portal-upstream.mjs +105 -17
- package/driver/predelivery-lint.mjs +43 -18
- package/driver/product-rows.mjs +1 -1
- package/driver/products.mjs +1 -1
- package/driver/profile-page.html +30 -5
- package/driver/profile-service.mjs +197 -26
- package/driver/profiles.mjs +48 -1
- package/driver/publish/index.mjs +31 -13
- package/driver/publish/knockout.mjs +9 -5
- package/driver/publish/office-record-links.mjs +189 -0
- package/driver/publish/parse.mjs +3 -3
- package/driver/publish/publish-inputs.mjs +26 -0
- package/driver/publish/render-knockout.mjs +42 -42
- package/driver/publish/render.mjs +29 -4
- package/driver/publish/report-data.mjs +2 -2
- package/driver/publish/seed-pool.mjs +1 -1
- package/driver/publish/templates/report.css +8 -8
- package/driver/publish/xlsx.mjs +49 -7
- package/driver/queue-watch-verdict.mjs +2 -2
- package/driver/recipe-service.mjs +1 -1
- package/driver/record-carry.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/reference-strip-signatures.mjs +1 -1
- package/driver/register-availability.mjs +4 -3
- package/driver/register-count.mjs +3 -3
- package/driver/register-records.mjs +1 -1
- package/driver/repair-composers.mjs +1 -1
- package/driver/repairs.mjs +3 -3
- package/driver/replay-archive.mjs +1 -1
- package/driver/report-card-record.mjs +1 -1
- package/driver/result-noun-fields.mjs +5 -0
- package/driver/roster-verdict.mjs +48 -5
- package/driver/run-activity.mjs +1 -1
- package/driver/run-requirements.mjs +18 -5
- package/driver/runner.mjs +24 -15
- package/driver/search-policy.mjs +8 -8
- package/driver/senior-rights.mjs +1 -1
- package/driver/skills/prelim-search/delivery-contract.md +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.md +10 -7
- package/driver/stages-knockout.mjs +72 -6
- package/driver/stages.mjs +10 -10
- package/driver/suite-census.json +238 -70
- package/driver/synthesis-record.mjs +2 -2
- package/driver/systemd/clearotron-client-mcp.service +3 -3
- package/driver/systemd/clearotron-deploy.service +2 -2
- package/driver/systemd/clearotron-mcp-face.service +1 -1
- package/driver/systemd/clearotron-portal.service +3 -3
- package/driver/systemd/clearotron-worker.service +5 -5
- package/driver/systemd/install-census.mjs +1 -1
- package/driver/systemd/render-units.mjs +9 -9
- package/driver/terminal-clamp.mjs +1 -1
- package/driver/trigger-cap.mjs +18 -2
- package/driver/unit-inventory.mjs +8 -8
- package/driver/usage-ledger.mjs +5 -3
- package/driver/verify-knockout.mjs +7 -7
- package/driver/verify.mjs +5 -5
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/whatif-queue.mjs +3 -3
- package/driver/whatif-worker.mjs +2 -2
- package/examples/README.md +1 -1
- package/examples/grants.example.json +25 -24
- package/mcp-server/CHANGELOG.md +10 -0
- package/mcp-server/http-server.mjs +3 -3
- package/mcp-server/key-socket.mjs +1 -1
- package/mcp-server/lib/audit-view.mjs +3 -3
- package/mcp-server/lib/brief.mjs +3 -3
- package/mcp-server/lib/driver.mjs +1 -1
- package/mcp-server/lib/events.mjs +1 -1
- package/mcp-server/lib/http-handler.mjs +2 -2
- package/mcp-server/lib/instructions.mjs +2 -2
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +6 -3
- package/mcp-server/lib/options.mjs +15 -4
- package/mcp-server/lib/plan.mjs +7 -6
- package/mcp-server/lib/runs.mjs +10 -0
- package/mcp-server/lib/whatif.mjs +5 -5
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/remote/client-mcp-apikey.service +1 -1
- package/mcp-server/remote/client-mcp.service +2 -2
- package/mcp-server/remote/trademark-artifacts-http.service +1 -1
- package/mcp-server/server.mjs +38 -24
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-KFAHMgdT.js → index-CWTHP0sH.js} +3471 -1901
- package/portal-ui/dist/assets/{index-1ziUJX1E.css → index-KpytsmNH.css} +79 -26
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/lane-probe.mjs +9 -3
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/scripts/citation-drift-report.mjs +1 -1
- package/scripts/citation-line-check.mjs +2 -2
- package/scripts/drive-env-check.mjs +1 -1
- package/scripts/e2e.mjs +5 -5
- package/scripts/env-audit.mjs +13 -1
- package/scripts/headless-page.mjs +5 -5
- package/scripts/live-surface-check.mjs +26 -2
- package/scripts/mint-names-in-force.mjs +19 -5
- package/scripts/mint-reference-strip-backlog.mjs +1 -1
- package/scripts/mint-suite-census.mjs +37 -10
- package/scripts/pack-publishable.mjs +1 -1
- package/scripts/preinstall-node-check.mjs +1 -1
- package/scripts/release-await-cut.mjs +3 -3
- package/scripts/release-cut-decision.mjs +1 -1
- package/scripts/release-dist-tag.mjs +1 -1
- package/scripts/release-install-check.mjs +1 -1
- package/scripts/release-notes-lint.mjs +1 -1
- package/scripts/release-publish-guard.mjs +1 -1
- package/scripts/release-version-pr-checks.mjs +2 -2
- package/scripts/release-version.mjs +61 -5
- package/scripts/render-brand-banner.mjs +1 -1
- package/scripts/render-check.mjs +2 -2
- package/scripts/repo-writes.mjs +1 -1
- package/scripts/report-frame-check.mjs +1 -1
- package/scripts/report-screenshot.mjs +2 -2
- package/scripts/retire-bare-refs.mjs +1 -1
- package/scripts/revisit-render-check.mjs +1 -1
- package/scripts/score.mjs +1 -1
- package/scripts/strip-titles-and-attributions.mjs +389 -0
- package/scripts/strip-tracker-citations.mjs +122 -5
- package/scripts/test-run.mjs +4 -4
- package/scripts/third-party-notices.mjs +1 -1
- package/scripts/verify-publishable.mjs +1 -1
- package/shared/access-audience.mjs +2 -2
- package/shared/anon-overlay.mjs +1 -1
- package/shared/brand.mjs +15 -1
- package/shared/bundle-freshness.mjs +1 -1
- package/shared/bundle-rebuild.mjs +1 -1
- package/shared/checkout-move.mjs +2 -2
- package/shared/client-door.mjs +8 -8
- package/shared/connect-clients.mjs +7 -7
- package/shared/connector-signin-probe.mjs +1 -1
- package/shared/env-aliases.mjs +1 -1
- package/shared/env-local.mjs +5 -5
- package/shared/grants-edit.mjs +76 -0
- package/shared/install-auth.mjs +1 -1
- package/shared/listen.mjs +3 -3
- package/shared/mcp-challenge.mjs +1 -1
- package/shared/names-in-force.mjs +2 -1
- package/shared/onboarding-store.mjs +19 -2
- package/shared/reference-guard-classes.mjs +44 -2
- package/shared/register-selection.mjs +1 -1
- package/shared/scope.mjs +223 -56
- package/shared/secret-file.mjs +1 -1
- package/shared/server-units.mjs +1 -1
- package/shared/staff-domain.mjs +45 -78
- package/shared/summary-blocks.mjs +2 -2
- package/shared/systemd-failure.mjs +3 -3
- package/shared/tracked-files.mjs +1 -1
- package/shared/trigger-lane.mjs +1 -1
- package/shared/tty-style.mjs +1 -1
- package/shared/usage-block.mjs +1 -1
- package/shared/vacuous-pass.mjs +1 -1
- package/shared/verb-shim.mjs +1 -1
|
@@ -66,7 +66,7 @@ let ENFORCER_SIGNALS = new Map(); // E6: registration uri (lowercase) → {aggr
|
|
|
66
66
|
// inference and no receipt line renders ⇒ archived output stays byte-identical.
|
|
67
67
|
let RECORD_ORIGIN = null;
|
|
68
68
|
let RECORD_ORIGINS = null; // — the run's ALLOW-LIST, not one origin
|
|
69
|
-
let RECORD_CITATION = null; // — what a card shows where a link cannot go (provider table)
|
|
69
|
+
let RECORD_CITATION = null, RECORD_LINKS = null; // — what a card shows where a link cannot go (provider table); the office's own page per fetched record (office-record-links.mjs)
|
|
70
70
|
let PROVIDER_LABEL = null;
|
|
71
71
|
// WP-receipts W3/W4 — senior-right rows by finding ordinal (from _driver/senior-rights.json via
|
|
72
72
|
// publish). The open item is CODE-OWNED: a verdict-driving card whose senior right went unverified
|
|
@@ -872,7 +872,7 @@ function scopeSection(ranBucket, coverage, coverageJudgment, methodologyText, co
|
|
|
872
872
|
// page had a register-index entry on it — a definition of a label the reader could not see. It renders
|
|
873
873
|
// now only where such an entry does, and the remaining two say what "inferred" means in the words a
|
|
874
874
|
// reader would use for it rather than in the renderer's.
|
|
875
|
-
if (hasRecordSet || hasCards) parts.push(`<p class="scoperead" style="margin:0 0 4px;font-weight:600">Record provenance</p><p class="provnote" style="margin:0 0 6px;font-size:13px">${hasRecordSet ? 'Registration numbers on the cards were read from the register records. ' : ''}${hasIndexEntry ? 'A registration shown as a register-index entry was seen in the register index; its full record was not pulled. ' : ''}“Inferred” beside an owner’s likelihood to object means we judged it from what the owner sells and holds; we had no enforcement history to read.</p>`);
|
|
875
|
+
if (hasRecordSet || hasCards) parts.push(`<p class="scoperead" style="margin:0 0 4px;font-weight:600">Record provenance</p><p class="provnote" style="margin:0 0 6px;font-size:13px">${hasRecordSet ? 'Registration numbers on the cards were read from the register records. ' : ''}${officeLinkNote()}${hasIndexEntry ? 'A registration shown as a register-index entry was seen in the register index; its full record was not pulled. ' : ''}“Inferred” beside an owner’s likelihood to object means we judged it from what the owner sells and holds; we had no enforcement history to read.</p>`);
|
|
876
876
|
// — this is the one part of §4 that does NOT fold. Same markup, same heading, same marker; it is
|
|
877
877
|
// emitted beside the <details> instead of inside it, wrapped in the panel the only-you section already
|
|
878
878
|
// uses so it reads as a region of the page rather than a stray heading.
|
|
@@ -1298,7 +1298,7 @@ function fullDetail(f, card, recordsByUri = new Map()) {
|
|
|
1298
1298
|
// "file exists on disk" inference. Absent receipt (archived runs) renders nothing.
|
|
1299
1299
|
const receiptTail = rec._receipt?.fetched_at
|
|
1300
1300
|
? ` · <i class="receipt">verified — ${esc(PROVIDER_LABEL || 'register')} record fetched ${esc(String(rec._receipt.fetched_at).slice(0, 10))}</i>` : '';
|
|
1301
|
-
return `<li><b>${
|
|
1301
|
+
return `<li><b>${officeRecordCell(r.uri, regUri, RECORD_CITATION ? NO_LINK_NOTE[RECORD_CITATION] : null)}</b>${cls ? ` · Cl.${esc(cls)}` : ''}${status ? ` · ${esc(status)}` : ''}${tail ? ` (${esc(tail)})` : ''}${jur ? ` · ${esc(jur)}${sys ? ` <i class="jsys">(${sys})</i>` : ''}` : ''}${desigHtml}${prio}${receiptTail}</li>`;
|
|
1302
1302
|
}
|
|
1303
1303
|
// (2)+(3): a cited record with NO fetched body. NEVER-INVENT when the run has a record set (this URI was
|
|
1304
1304
|
// not fetched ⇒ the model's fields are unconfirmed); also when there are no findings.json facts to show.
|
|
@@ -2072,7 +2072,7 @@ export function renderHtml(parsed, findings = [], coverage = [], opts = {}) {
|
|
|
2072
2072
|
// The provider table's own answer, not a branch on a vendor name here: `workbook` (no register UI
|
|
2073
2073
|
// exists — point at the artifact that carries the record) or `placeholder` (a UI exists, its
|
|
2074
2074
|
// per-record URL is unknown — say so, and say it is unfinished). Unset ⇒ plain text, as before.
|
|
2075
|
-
RECORD_CITATION = typeof opts.recordCitation === "string" ? opts.recordCitation : null;
|
|
2075
|
+
RECORD_CITATION = typeof opts.recordCitation === "string" ? opts.recordCitation : null; RECORD_LINKS = opts.recordLinks instanceof Map ? opts.recordLinks : null;
|
|
2076
2076
|
PROVIDER_LABEL = opts.providerLabel ?? null; // WP-receipts W2
|
|
2077
2077
|
SENIOR_RIGHTS = new Map((Array.isArray(opts.seniorRights) ? opts.seniorRights : []).map((r) => [r.ordinal, r])); // WP-receipts W3/W4
|
|
2078
2078
|
// A1 — a review-KILLED finding (disposition "withdrawn") renders NOWHERE: not in the
|
|
@@ -2411,3 +2411,28 @@ if (isEntrypoint(import.meta.url)) {
|
|
|
2411
2411
|
writeFileSync(out, renderHtml(parsed, findings, coverage, { runId: basename(reportPath), contextNotes, markAssessment, fourAnswers }));
|
|
2412
2412
|
console.log(`rendered ${findings.length} findings + ${coverage.length} coverage areas + ${contextNotes.length} context notes -> ${out}`);
|
|
2413
2413
|
}
|
|
2414
|
+
|
|
2415
|
+
// ── The office's own page for a fetched record, where the run's register publishes none of its own ─────
|
|
2416
|
+
// publish addresses each fetched record from its own numbers (office-record-links.mjs) and hands the map
|
|
2417
|
+
// in as `recordLinks`. Kept down here, below every line the rest of the tree cites by number.
|
|
2418
|
+
import { officeReasonSentences } from './office-record-links.mjs';
|
|
2419
|
+
|
|
2420
|
+
// The label is the office and the number, linked where the office has a page. The handle names the
|
|
2421
|
+
// vendor's record, not the office's, and a reader can look it up nowhere. With no link the number stands
|
|
2422
|
+
// on its own beside the workbook note, and Scope says once, per office, why it is not a link.
|
|
2423
|
+
function officeRecordCell(uri, regUri, note) {
|
|
2424
|
+
const l = RECORD_LINKS?.get(String(uri || '').toLowerCase());
|
|
2425
|
+
if (!l?.label) return regUri(uri);
|
|
2426
|
+
if (l.href) return `<a href="${esc(l.href)}" target="_blank" rel="noopener noreferrer">${esc(l.label)}</a>`;
|
|
2427
|
+
return note ? `${esc(l.label)}<span class="reg-nolink">${note}</span>` : esc(l.label);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// What a linked registration number opens and, once per office, why the rest are cited by number. Said
|
|
2431
|
+
// in Scope rather than beside every registration, for the reason the placeholder note was removed: a
|
|
2432
|
+
// note repeated on every line reads as a broken report. Empty on every other run.
|
|
2433
|
+
function officeLinkNote() {
|
|
2434
|
+
if (!RECORD_LINKS) return '';
|
|
2435
|
+
const linked = [...RECORD_LINKS.values()].some((l) => l?.href);
|
|
2436
|
+
return (linked ? 'A registration number shown as a link opens the office’s own page for that record. ' : '')
|
|
2437
|
+
+ officeReasonSentences(RECORD_LINKS).map((s) => `${esc(s)} `).join('');
|
|
2438
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Two readers, one file (the knockout doctrine, as it stood): the assistant drafts client-facing mail
|
|
8
8
|
// from it, and the portal's native-render path reads it instead of iframing baked HTML.
|
|
9
9
|
//
|
|
10
|
-
// THE TWO LANES DIVERGED ON 2026-09-07 AND THIS FILE DID NOT MOVE. The owner ruled
|
|
10
|
+
// THE TWO LANES DIVERGED ON 2026-09-07 AND THIS FILE DID NOT MOVE. The owner ruled
|
|
11
11
|
// that the KNOCKOUT report carries the reviewer's notes, on the ground that one report is read by the
|
|
12
12
|
// person who ran Clearotron and holding material back confuses where data went; render-knockout.mjs
|
|
13
13
|
// projects them under `reviewerNotes`. That ruling was about those notes on that lane. It did not touch
|
|
@@ -187,7 +187,7 @@ export function clearanceReportData({
|
|
|
187
187
|
// filing-routine "(ordinary mechanics are not an ask) — same rule as the code-built only-you
|
|
188
188
|
// section", and that cross-reference was accurate: the only-you section dropped it too, through
|
|
189
189
|
// ADVISORY_TAG rather than a named filter. then wrote a watch group for "monitoring and
|
|
190
|
-
// filing-routine" and neither copy was told.
|
|
190
|
+
// filing-routine" and neither copy was told. Ruling 2026-08-19: the kind renders.
|
|
191
191
|
//
|
|
192
192
|
// This surface is the one that matters most for it. Legacy runs are served as baked bytes, but NEW
|
|
193
193
|
// runs render component-native from report-data.json — so a fix that reached only the markdown
|
|
@@ -105,7 +105,7 @@ export function frozenSamples(examplesDir) {
|
|
|
105
105
|
*/
|
|
106
106
|
export async function seedPool({ pool, examplesDir, republish }) {
|
|
107
107
|
const existing = poolRunIds(pool);
|
|
108
|
-
// ── A POOL THAT ALREADY HOLDS SOMETHING IS TOPPED UP, NOT SKIPPED
|
|
108
|
+
// ── A POOL THAT ALREADY HOLDS SOMETHING IS TOPPED UP, NOT SKIPPED ──────────────────────────────────
|
|
109
109
|
//
|
|
110
110
|
// This used to return early on any non-empty pool. That was invisible while `demo/` shipped one child:
|
|
111
111
|
// seeding one and seeding all were the same act. When the other three landed, every box seeded before
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
h1.mark{font-weight:900;font-size:58px;line-height:1.0;letter-spacing:-.022em;margin:0 0 14px}
|
|
38
38
|
.sub{font-size:17.5px;color:var(--slate);max-width:700px;line-height:1.5;margin:0 0 30px}
|
|
39
39
|
.sub b{color:var(--ink);font-weight:700}
|
|
40
|
-
/*
|
|
40
|
+
/* the hero caption folds to its first sentence; the remainder sits behind this disclosure.
|
|
41
41
|
The visible sentence loses its bottom margin (the fold row carries the spacing instead) so a folded
|
|
42
42
|
caption occupies the same vertical run as an unfolded one. `details.sub-more` is styled here and NOT
|
|
43
43
|
in the frozen renderer, and the existing @media print rule that forces every <details> open already
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
.panel{background:var(--card);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow)}
|
|
57
57
|
.gauge{padding:22px 24px}
|
|
58
58
|
.gauge .label{margin-bottom:18px}
|
|
59
|
-
/*
|
|
59
|
+
/* the framework that owns the ticks, named where they are read. Normal case and un-tracked on
|
|
60
60
|
purpose: `.label` is an uppercase eyebrow, and a proper name in the eyebrow's own letterforms reads
|
|
61
61
|
as a second heading rather than as the thing being cited. */
|
|
62
62
|
.gauge .label .gauge-fw{display:block;margin-top:5px;font-size:11px;font-weight:600;letter-spacing:0;
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
.pips.l3 i:nth-child(-n+3){background:var(--high)} .pips.l2 i:nth-child(-n+2){background:var(--med)} .pips.l1 i:nth-child(-n+1){background:var(--low)}
|
|
168
168
|
.pips.g3 i{background:var(--clear)} .pips.g2 i:nth-child(-n+2){background:var(--clear)}
|
|
169
169
|
.mv{font-size:11px;color:var(--slate);margin-top:6px;font-weight:500;line-height:1.3}
|
|
170
|
-
/*
|
|
170
|
+
/* D4 — the meter's SECOND position: how well the fact above it is evidenced. It is subordinate
|
|
171
171
|
to .mv on purpose (lighter, smaller): the finding is the fact, the receipt is the footnote. It
|
|
172
172
|
wraps inside its own 1fr grid track, so a long evidence clause costs height, never overflow. */
|
|
173
173
|
.mev{font-size:10px;color:var(--faint);margin-top:3px;font-weight:500;line-height:1.3}
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
details.method[open]>summary::after{content:"▴"}
|
|
215
215
|
.method .mbody{padding:0 22px 20px;font-size:13px;color:var(--slate);line-height:1.62}
|
|
216
216
|
|
|
217
|
-
/* Ask-your-AI launcher
|
|
217
|
+
/* Ask-your-AI launcher */
|
|
218
218
|
.askai{position:relative;display:inline-block}
|
|
219
219
|
.askai-pop{position:absolute;z-index:30;top:calc(100% + 8px);left:0;width:340px;max-width:88vw;background:var(--card);
|
|
220
220
|
border:1px solid var(--line2);border-radius:14px;box-shadow:var(--shadow);padding:14px}
|
|
@@ -272,7 +272,7 @@
|
|
|
272
272
|
.jchip.ww{font-family:inherit;letter-spacing:.03em;color:var(--clear-tx,#2e6b32);background:var(--clear-soft,#EAF3E6);border-color:transparent}
|
|
273
273
|
.jnote{font-size:10.5px;color:var(--faint);line-height:1.4}
|
|
274
274
|
|
|
275
|
-
/* spec-49 T4 retired the review-notes caveat body;
|
|
275
|
+
/* spec-49 T4 retired the review-notes caveat body; a later change retired the slim internal toolbar that
|
|
276
276
|
survived it, because the Flag/Etch capture controls it hosted are gone. The .review rules stay:
|
|
277
277
|
reports ALREADY DELIVERED are frozen bytes carrying that markup, and portal-report.mjs strips
|
|
278
278
|
it at serve time against these selectors. */
|
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
.gbody{padding:0 12px 8px}
|
|
321
321
|
.rgroup .card{margin:0 0 10px} .rgroup .card:last-child{margin-bottom:4px}
|
|
322
322
|
|
|
323
|
-
/*
|
|
323
|
+
/* a grouped reasoned negative. The shared ground is in the .fold-lead above the run of rows;
|
|
324
324
|
each row carries only what is its own, so the line has to stay ONE line's worth of weight: the mark
|
|
325
325
|
and owner anchor it, the jurisdictions/classes sit quiet, and the finding's own sentence takes the
|
|
326
326
|
remaining width and wraps rather than truncating. Denser than .rgroup because these are cleared
|
|
@@ -349,7 +349,7 @@
|
|
|
349
349
|
own content was carried correctly and only the control leaked, which is why the commit that added
|
|
350
350
|
the fold could check the half it thought about and ship the half it did not. */
|
|
351
351
|
details.drill>summary,details.method>summary,details.sub-more>summary{display:none!important}
|
|
352
|
-
/*
|
|
352
|
+
/* The caption fold made this line load-bearing for the document's CONTENT, not just its completeness. The
|
|
353
353
|
legal/practical reads now live inside .drillbody, so without this rule the exported PDF would
|
|
354
354
|
carry the one-line verdicts and none of the reasoning behind them. The export prints everything a
|
|
355
355
|
reader could open — ruled on #470, deliberately unchanged. Ticked-card selection (.print-hidden)
|
|
@@ -412,7 +412,7 @@
|
|
|
412
412
|
.fourans ul.fa-obstacles li{margin:0 0 4px;font-size:13px}
|
|
413
413
|
@media (max-width:640px){.fourans .fa-row{flex-direction:column;gap:2px}.fourans .fa-k{flex:auto}}
|
|
414
414
|
|
|
415
|
-
/* ──
|
|
415
|
+
/* ── the coverage ledger stops being the page's smallest print ─────────────────────────────
|
|
416
416
|
`details.scope` had NO screen rule at all: its summary rendered at the browser default, in the
|
|
417
417
|
browser's default weight, under the browser's default triangle, at the foot of a document whose
|
|
418
418
|
every other section carries a numbered spine header. The section that states what the search did
|
package/driver/publish/xlsx.mjs
CHANGED
|
@@ -179,7 +179,7 @@ const FINDING_COLS = ['#', 'Conflicting mark', 'Owner', 'Country', 'Source', 'Cl
|
|
|
179
179
|
'Status & key dates', 'Record retrieved?', 'Risk band', 'How we treated it',
|
|
180
180
|
'Mark similarity', 'Goods proximity', 'Use', 'Enforcer', 'Link'];
|
|
181
181
|
|
|
182
|
-
function findingRows(findings, fetchState) {
|
|
182
|
+
function findingRows(findings, fetchState, recordLinks = null) {
|
|
183
183
|
return (findings || []).map(f => {
|
|
184
184
|
const regs = f?.owner?.registrations || [];
|
|
185
185
|
const ctx = !!f.isContextNote;
|
|
@@ -190,7 +190,7 @@ function findingRows(findings, fetchState) {
|
|
|
190
190
|
'Country': f.owner?.country || '',
|
|
191
191
|
'Source': sourceWord(f.source?.source_type || ''),
|
|
192
192
|
'Class': classCell(f),
|
|
193
|
-
'Registration(s)': regs.length ? regs.map(r => regLabel(r.uri)).join('; ') : (ctx ? '—' : '— (common-law)'),
|
|
193
|
+
'Registration(s)': regs.length ? regs.map(r => recordLinks?.get(String(r.uri || '').toLowerCase())?.label || regLabel(r.uri)).join('; ') : (ctx ? '—' : '— (common-law)'),
|
|
194
194
|
'Status & key dates': statusDatesCell(f),
|
|
195
195
|
'Record retrieved?': ctx ? 'n/a — noted only' : retrievedCell(f, fetchState),
|
|
196
196
|
'Risk band': f.band || '—',
|
|
@@ -200,7 +200,7 @@ function findingRows(findings, fetchState) {
|
|
|
200
200
|
'Goods proximity': ctx ? 'n/a' : meterCell(f.meters?.goods_proximity),
|
|
201
201
|
'Use': ctx ? 'n/a' : meterCell(f.meters?.use),
|
|
202
202
|
'Enforcer': ctx ? 'n/a' : meterCell(f.meters?.enforcer),
|
|
203
|
-
'Link': f.source?.resolved_link || '
|
|
203
|
+
'Link': f.source?.resolved_link || linkCellFor(regs, recordLinks), // the office's own page, or why there is none
|
|
204
204
|
};
|
|
205
205
|
});
|
|
206
206
|
}
|
|
@@ -598,7 +598,7 @@ export async function buildAudit(contract, auditParsed, outPath, mark = '', fm =
|
|
|
598
598
|
(row, _d, kept) => { if (kept.has('Field')) row.getCell('Field').font = { bold: true }; });
|
|
599
599
|
|
|
600
600
|
// 2 · Findings — one row per conflict; registrations joined; provenance folded into the driver tags.
|
|
601
|
-
addSheet(wb, 'Findings', FINDING_COLS, findingRows(findings, fetchState), (row, _d, kept) => {
|
|
601
|
+
addSheet(wb, 'Findings', FINDING_COLS, findingRows(findings, fetchState, contract?.recordLinks instanceof Map ? contract.recordLinks : null), (row, _d, kept) => {
|
|
602
602
|
if (kept.has('Link')) {
|
|
603
603
|
const link = row.getCell('Link');
|
|
604
604
|
const url = String(link.value || '').trim();
|
|
@@ -629,7 +629,8 @@ export async function buildAudit(contract, auditParsed, outPath, mark = '', fm =
|
|
|
629
629
|
// never a reason to drop the Excel. (A gate that deletes the artifact it was meant to protect is the bug
|
|
630
630
|
// this replaced: a legitimately-sparse run — e.g. no bound Class — must still ship its workbook.)
|
|
631
631
|
await wb.xlsx.writeFile(outPath);
|
|
632
|
-
const gate = validateAudit(wb, { findings, coverage, coverageJudgment, verdict, registerOnly
|
|
632
|
+
const gate = validateAudit(wb, { findings, coverage, coverageJudgment, verdict, registerOnly,
|
|
633
|
+
registerPublishesRecordPages: contract?.registerPublishesRecordPages ?? null });
|
|
633
634
|
return {
|
|
634
635
|
sheets: wb.worksheets.map(w => w.name),
|
|
635
636
|
findings: findings.length,
|
|
@@ -643,7 +644,8 @@ export async function buildAudit(contract, auditParsed, outPath, mark = '', fm =
|
|
|
643
644
|
* validateAudit — the build gate. Reads the assembled workbook back and asserts the spec's MUST-BE-TRUE list.
|
|
644
645
|
* Returns { ok, violations[] }. Exported so tests assert the same contract the build enforces.
|
|
645
646
|
*/
|
|
646
|
-
export function validateAudit(wb, { findings = [], coverage = [], coverageJudgment = null, verdict = null, registerOnly = false
|
|
647
|
+
export function validateAudit(wb, { findings = [], coverage = [], coverageJudgment = null, verdict = null, registerOnly = false,
|
|
648
|
+
registerPublishesRecordPages = null } = {}) {
|
|
647
649
|
const v = [];
|
|
648
650
|
const sheets = wb.worksheets.map(w => w.name);
|
|
649
651
|
|
|
@@ -722,6 +724,13 @@ export function validateAudit(wb, { findings = [], coverage = [], coverageJudgme
|
|
|
722
724
|
if (!textAt(findWs.getRow(r), cMark0).trim()) ghost++;
|
|
723
725
|
}
|
|
724
726
|
if (ghost) v.push(`Findings has ${ghost} ghost/continuation row(s) with no mark (multi-registration owners must stay on one row)`);
|
|
727
|
+
// the ordinal joins a row back to its finding's typed source; the record identity is office + number
|
|
728
|
+
const cOrd = col(findWs, '#'), cReg = col(findWs, 'Registration(s)'), cCountry = col(findWs, 'Country');
|
|
729
|
+
// ordinal → the finding's OWN typed source_type. Never pattern-matched off the Source cell, which is
|
|
730
|
+
// display text: `sourceCell` composes it and a register row can read "EUIPO" or a vendor label.
|
|
731
|
+
const srcTypeByOrdinal = new Map((Array.isArray(findings) ? findings : [])
|
|
732
|
+
.filter((f) => f?.ordinal != null)
|
|
733
|
+
.map((f) => [String(f.ordinal), String(f?.source?.source_type ?? '')]));
|
|
725
734
|
const cMark = col(findWs, 'Conflicting mark'), cSrc = col(findWs, 'Source'), cTreat = col(findWs, 'How we treated it'),
|
|
726
735
|
cLink = col(findWs, 'Link'), cRet = col(findWs, 'Record retrieved?'),
|
|
727
736
|
cMs = col(findWs, 'Mark similarity'), cGp = col(findWs, 'Goods proximity'), cUse = col(findWs, 'Use'), cEnf = col(findWs, 'Enforcer'),
|
|
@@ -737,9 +746,37 @@ export function validateAudit(wb, { findings = [], coverage = [], coverageJudgme
|
|
|
737
746
|
if (!cellText(cBand).trim()) v.push(`Findings "${mark}": no band/treatment marker`);
|
|
738
747
|
// link resolves — but a famous-neighbour context note is knowledge-cited with no fetched record, so it
|
|
739
748
|
// legitimately carries no link (its row is exempt, exactly as it is from the four-driver check below).
|
|
749
|
+
//
|
|
750
|
+
// WHAT A READER CAN DO WITH THE CITATION, not whether a URL is present. Some registers publish no
|
|
751
|
+
// per-record page at all (`hasPublicRecordUrl: false`), and for those the engine's own publish step
|
|
752
|
+
// EMPTIES any absolute record link a model wrote, because constructing a per-record URL for a
|
|
753
|
+
// register that publishes none would be a fabricated citation on a legal deliverable. Demanding an
|
|
754
|
+
// http(s) link of such a row asked for the one thing the engine is forbidden to supply, and reported
|
|
755
|
+
// the whole register layer of a delivered workbook as defective: every register finding, every run,
|
|
756
|
+
// scaling with how many register hits the matter had. So on such a run the record's IDENTITY is the
|
|
757
|
+
// citation — the registration number and the office — and a row carrying it is followable.
|
|
758
|
+
//
|
|
759
|
+
// FAIL-CLOSED IN BOTH DIRECTIONS OF DOUBT. The exemption needs a register that positively declares
|
|
760
|
+
// it has no record pages (`false`, never `null` — a legacy run with no fetch receipts cannot say
|
|
761
|
+
// what its register publishes), the row's source type to come back `register*` from the findings
|
|
762
|
+
// themselves, AND the row to actually carry the record identity. If the ordinal join misses, or the
|
|
763
|
+
// identity columns are absent from this workbook, nothing is exempt and the row is reported exactly
|
|
764
|
+
// as before.
|
|
740
765
|
const linkVal = cLink ? row.getCell(cLink).value : null;
|
|
741
766
|
const href = linkVal && typeof linkVal === 'object' ? linkVal.hyperlink : linkVal;
|
|
742
|
-
if (!isCtx && !/^https?:\/\//i.test(String(href ?? '')))
|
|
767
|
+
if (!isCtx && !/^https?:\/\//i.test(String(href ?? ''))) {
|
|
768
|
+
const isRegisterRow = String(srcTypeByOrdinal.get(cellText(cOrd).trim()) ?? '').startsWith('register');
|
|
769
|
+
// The identity has to be a real one. `Registration(s)` is never blank — findingRows writes the
|
|
770
|
+
// placeholder `— (common-law)` for a finding with no registrations at all — so a bare non-empty
|
|
771
|
+
// test would have exempted exactly the register row that cites nothing.
|
|
772
|
+
const regCell = cellText(cReg).trim();
|
|
773
|
+
const citesRecord = Boolean(regCell && !regCell.startsWith('—') && cellText(cCountry).trim());
|
|
774
|
+
if (registerPublishesRecordPages === false && isRegisterRow) {
|
|
775
|
+
// The register has no record page. The number and the office ARE the citation; without them the
|
|
776
|
+
// row cites nothing at all, which is a worse defect than a missing link and is named as itself.
|
|
777
|
+
if (!citesRecord) v.push(`Findings "${mark}": no record link, and no registration number + office either — nothing identifies the record`);
|
|
778
|
+
} else v.push(`Findings "${mark}": link does not resolve to http(s)`);
|
|
779
|
+
}
|
|
743
780
|
// fetch-failed visibly unverified
|
|
744
781
|
const ret = cellText(cRet);
|
|
745
782
|
if (/not retrieved/i.test(ret) && !/unverified/i.test(ret) && !/n\/a/i.test(ret) && !/read/i.test(ret)) {
|
|
@@ -812,3 +849,8 @@ export function validateAudit(wb, { findings = [], coverage = [], coverageJudgme
|
|
|
812
849
|
|
|
813
850
|
return { ok: v.length === 0, violations: v };
|
|
814
851
|
}
|
|
852
|
+
|
|
853
|
+
// Where the run's register publishes no record pages of its own, the Findings sheet shows each
|
|
854
|
+
// registration's office and number and links the office's own page (office-record-links.mjs). Kept down
|
|
855
|
+
// here, below every line the rest of the tree cites by number.
|
|
856
|
+
import { linkCellFor } from './office-record-links.mjs';
|
|
@@ -66,7 +66,7 @@ import { drainPosture, CONTINUOUS } from "./drain-posture.mjs"; // one rule ab
|
|
|
66
66
|
*/
|
|
67
67
|
export function queueWatchVerdict({ queueDirs, watched, unitPath, unitError = null, resolveError = null, timer = null, worker = null } = {}) {
|
|
68
68
|
// ── THE POSTURE QUESTION COMES FIRST, because after the retirement it decides whether the rest of
|
|
69
|
-
// this function is even asking about anything (;
|
|
69
|
+
// this function is even asking about anything (; ruling 2026-08-26, restated
|
|
70
70
|
// 2026-08-31). The arrival trigger and the timer were two halves of ONE posture. The worker drains
|
|
71
71
|
// continuously under --watch, so on a box in that posture there is no arrival to trigger and no
|
|
72
72
|
// schedule to wait for, and every finding below would be describing the absence of machinery this
|
|
@@ -77,7 +77,7 @@ export function queueWatchVerdict({ queueDirs, watched, unitPath, unitError = nu
|
|
|
77
77
|
// permission error still skips — a privilege-limited read that answers "fine" is the exact failure
|
|
78
78
|
// this family of checks exists to refuse, and letting a worker unit talk it into a pass would put
|
|
79
79
|
// that failure back one door along.
|
|
80
|
-
// ONE RULE, NOT A SECOND COPY OF IT
|
|
80
|
+
// ONE RULE, NOT A SECOND COPY OF IT. This predicate used to be spelled out here
|
|
81
81
|
// and nowhere else, and the drainer arm — deciding the same question about the same box — did not
|
|
82
82
|
// read the units at all and applied the timer-era rule instead. Both arms now consult
|
|
83
83
|
// drain-posture.mjs, so a box cannot be continuous for one of them and timer-shaped for the other.
|
|
@@ -250,7 +250,7 @@ const PORT = PORT_CHOICE.port;
|
|
|
250
250
|
//
|
|
251
251
|
// `makeAccessVerifier` has ALWAYS accepted issuer/jwksUrl/emailClaim — this service simply never
|
|
252
252
|
// passed them, which is how one product shipped a provider-agnostic API face and three single-vendor
|
|
253
|
-
// services beside it.
|
|
253
|
+
// services beside it. Ruling 2026-08-23: "we can't launch with an identity vendor, they bring
|
|
254
254
|
// their own… they pick their own."
|
|
255
255
|
const OIDC_ISSUER = process.env.RECIPE_OIDC_ISSUER || "";
|
|
256
256
|
const JWKS_URL = process.env.RECIPE_JWKS_URL || "";
|
package/driver/record-carry.mjs
CHANGED
|
@@ -829,7 +829,7 @@ export function silentlyLostFindings({ reconciliation = null, carryRows = null,
|
|
|
829
829
|
/**
|
|
830
830
|
* Positions the DIGEST ended as findings that did not reach the findings, dropped WITH a stated reason.
|
|
831
831
|
*
|
|
832
|
-
* ── THE SIBLING'S BLIND SPOT, AND IT IS THE ONE THAT REACHED A CLIENT
|
|
832
|
+
* ── THE SIBLING'S BLIND SPOT, AND IT IS THE ONE THAT REACHED A CLIENT ──────────────────────────────
|
|
833
833
|
*
|
|
834
834
|
* `silentlyLostFindings` above is correct and must not be widened to cover this. Its population is
|
|
835
835
|
* `step-silent` — a finding-ending followed by silence — and its own header records why that boundary
|
|
@@ -667,7 +667,7 @@ export function scoreRecall({ reference, findings = [], retrieved = [], scopeCla
|
|
|
667
667
|
// what it saw. Auto-promoting a collision to `found` would be the scorer manufacturing recall from
|
|
668
668
|
// its own confusion, which is the defect one layer up from the one being fixed. score.mjs prints
|
|
669
669
|
// these; a reader adjudicates.
|
|
670
|
-
// ── THE PREDICATE WAS THE OWNER, AND THE HEADING ABOVE SAYS RECORD
|
|
670
|
+
// ── THE PREDICATE WAS THE OWNER, AND THE HEADING ABOVE SAYS RECORD ──────────────────────────────
|
|
671
671
|
//
|
|
672
672
|
// The only condition used to be `ownersMatch`. That implements a different class from the one the
|
|
673
673
|
// paragraph above states, and the justification — "they cannot both be true" — does not hold for any
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
3
|
//
|
|
4
|
-
// THE TWO SIGNATURES THE REFERENCE STRIP LEFT BEHIND
|
|
4
|
+
// THE TWO SIGNATURES THE REFERENCE STRIP LEFT BEHIND.
|
|
5
5
|
//
|
|
6
6
|
// The strip's job was to remove internal references from this repository before it went public, and it
|
|
7
7
|
// did that. Where the reference was the SUBJECT of the sentence, it took the subject with it:
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// store-wide veto's GRANULARITY (refuse the scenario that does not fit, not every scenario beside
|
|
29
29
|
// it) and the door not narrowing declared coverage. Whether a matter whose ENTIRE ordered scope is
|
|
30
30
|
// unreachable on this box should start is a separate question and did not answer it. The
|
|
31
|
-
//
|
|
31
|
+
// ruling of 2026-08-12 does: partial coverage discloses, empty coverage refuses by name
|
|
32
32
|
// before spend (reachableRegions below, and the two Depth 2 preflights that read it).
|
|
33
33
|
// · resolveRegions would then classify the US as "outside the provider's coverage", which is a false
|
|
34
34
|
// sentence about the product. The free tier covers the US. This install is not configured for it.
|
|
@@ -49,8 +49,9 @@
|
|
|
49
49
|
// read it: coverage-form.mjs seeded its deferred rows from skeleton qids alone, so an EU+US matter on a
|
|
50
50
|
// box with no index shipped an EU-only clean with no row saying the US was never searched. A false
|
|
51
51
|
// clean, reached through the disclosure that was supposed to prevent one, and this sentence is what
|
|
52
|
-
// made it look already-handled. driver.config.mjs
|
|
53
|
-
// requiring USPTO_LOCAL_DB.
|
|
52
|
+
// made it look already-handled. driver.config.mjs freeTierMissing cited it as the reason preflight
|
|
53
|
+
// could stop requiring USPTO_LOCAL_DB. Cited by SYMBOL and no line: the number was 84 lines above that
|
|
54
|
+
// comment before anything moved, and a line citation into a file this size stales on the next edit.
|
|
54
55
|
//
|
|
55
56
|
// The split is still right and is unchanged. What had to be built is the consumer: coverage-form.mjs
|
|
56
57
|
// now reads `plan.deferred_coverage` directly and emits an `open` row per active axis. The disclosure
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// 2. A COUNT WE COULD NOT TAKE IS NEVER ZERO. Every failure path carries `total: null` plus a reason,
|
|
20
20
|
// and the surfaces render "not available". A provider that cannot count at all
|
|
21
21
|
// (capabilities.countProbe "none") does not degrade to zeroes: the lane REFUSES before spending
|
|
22
|
-
// anything (see countPreflight) — that is the
|
|
22
|
+
// anything (see countPreflight) — that is the ruling, and it is the difference between a
|
|
23
23
|
// degraded answer and a wrong one.
|
|
24
24
|
//
|
|
25
25
|
// 3. THE NUMBER IS NEVER BANDED. No colour, no threshold, no "high/low". The moment a count carries a
|
|
@@ -187,7 +187,7 @@ export function countPreflight({ capabilities, jurisdictions, credentialPresent
|
|
|
187
187
|
+ `read as though it covered the ones ordered. Name a territory ${id} covers, or switch the register provider.`;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
// — EMPTY COVERAGE REFUSES EARLY; PARTIAL COVERAGE DISCLOSES (
|
|
190
|
+
// — EMPTY COVERAGE REFUSES EARLY; PARTIAL COVERAGE DISCLOSES (ruling, 2026-08-12).
|
|
191
191
|
//
|
|
192
192
|
// The check above is about the CONTRACT: territories the provider does not cover. This one is about
|
|
193
193
|
// the BOX: territories it covers and this deployment cannot reach, because a member is unconfigured.
|
|
@@ -442,7 +442,7 @@ export async function countRegisterHits({
|
|
|
442
442
|
// report of exactly that.
|
|
443
443
|
...(deferred.length ? { deferredScope: deferred.map((d) => d.jurisdiction) } : {}),
|
|
444
444
|
// The generated set, recorded whether or not the aggregate landed: the report prints WHICH forms
|
|
445
|
-
// were checked, and a number whose forms are not on the page is unauditable (
|
|
445
|
+
// were checked, and a number whose forms are not on the page is unauditable (ruling).
|
|
446
446
|
variants: { cap: variantCap, generated: variants.generated, truncated: variants.truncated, forms: variants.forms.map((f) => f.form) },
|
|
447
447
|
...(reusedCells === COUNT_PREDICATES.length ? { reused: true } : {}),
|
|
448
448
|
};
|
|
@@ -72,7 +72,7 @@ import { variantForms, VARIANT_CAP } from "./register-variants.mjs";
|
|
|
72
72
|
import { recordOriginsFor } from "./record-origins.mjs"; // — provider-derived, never CLEAROTRON_DATABASE
|
|
73
73
|
|
|
74
74
|
/** Records per MARK, across every listed term. A bound on cost, on artifact size and on what a reader
|
|
75
|
-
* can usefully hold — and it is stated wherever the list renders, per the
|
|
75
|
+
* can usefully hold — and it is stated wherever the list renders, per the ruling. */
|
|
76
76
|
export const RECORD_CAP = 100;
|
|
77
77
|
|
|
78
78
|
/** The floor on a single term's share of the cap. Without it a name generating twelve forms would give
|
|
@@ -165,7 +165,7 @@ export const REPAIR_COMPOSERS = [
|
|
|
165
165
|
`If applying a flag genuinely REQUIRES changing a finding outside that list — a knock-on the reviewer did not see — make the change and say which one and why in the "corrections" note. A silent edit outside the scope is the one thing this instruction forbids; a reasoned one is not.`,
|
|
166
166
|
) : "";
|
|
167
167
|
return lines(
|
|
168
|
-
// —
|
|
168
|
+
// — RULING 2026-08-09: "add caveat" is DELETED, not softened. A repair has exactly
|
|
169
169
|
// four moves and there is no fifth. The standing outward-language rule governs the REPAIR path
|
|
170
170
|
// exactly as it governs a first draft, and this instruction was the one place the engine invited
|
|
171
171
|
// a hedge into a deliverable. It fired on 4 of 4 delivered clearances in the 2026-08-09 round.
|
package/driver/repairs.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
|
26
26
|
// ── the terminal kind that is not a failure ───────────────────────────────────────────────
|
|
27
27
|
//
|
|
28
28
|
// A REFUSAL THE PRODUCT IS DESIGNED TO MAKE. The engine did not break: an order arrived that this
|
|
29
|
-
// deployment does not serve, a preflight said so before any spend, and the run stopped.
|
|
29
|
+
// deployment does not serve, a preflight said so before any spend, and the run stopped. Ruling
|
|
30
30
|
// 2026-08-13 — such a refusal must be distinguishable from a failure IN EVERY SINK IT REACHES, must
|
|
31
31
|
// never feed failure statistics, and must never trigger recovery machinery.
|
|
32
32
|
//
|
|
@@ -216,7 +216,7 @@ export function classifyFailureReason(reason) {
|
|
|
216
216
|
// and a second derivation is a second answer waiting to disagree with the first.
|
|
217
217
|
// ── BUILD A — WHERE THE CLASSIFICATION CAME FROM ────────────────────────────────────────────
|
|
218
218
|
//
|
|
219
|
-
//
|
|
219
|
+
// Ruling 2026-08-19: `classSource` becomes `validator-token` for a token the validator already
|
|
220
220
|
// named. Budgets identical, no new class value, no lane change — `decideRecovery` takes no
|
|
221
221
|
// `classSource` argument at all, so this cannot reach a budget branch even by mistake.
|
|
222
222
|
//
|
|
@@ -553,7 +553,7 @@ export function countRecoveryLanes(history, { total = 0 } = {}) {
|
|
|
553
553
|
return { weather, defect: defect + shortfall };
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
// ── CAP PARKS: A PROVIDER SAYING "NOT YET" IS NOT A STAGE FAILING
|
|
556
|
+
// ── CAP PARKS: A PROVIDER SAYING "NOT YET" IS NOT A STAGE FAILING ──────────────────────────────────
|
|
557
557
|
//
|
|
558
558
|
// Owner, watching a run spend 4 of its 6 recovery parks against one subscription cap:
|
|
559
559
|
//
|
|
@@ -218,7 +218,7 @@ export function diffSnapshots(prev, curr) {
|
|
|
218
218
|
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
|
-
// renaming the default points the reader at one that does not exist.
|
|
221
|
+
// renaming the default points the reader at one that does not exist. Ruling.
|
|
222
222
|
const snapshotPath = process.env.CLEAROTRON_REPLAY_SNAPSHOT || join(homedir(), ".prelim-replay-snapshot.json");
|
|
223
223
|
const roots = process.env.CLEAROTRON_REPLAY_ROOTS
|
|
224
224
|
? process.env.CLEAROTRON_REPLAY_ROOTS.split(":").filter(Boolean)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// ── WHAT MOVES, AND THE LINE THIS CONVERSION MUST NOT CROSS ────────────────────────────────────────
|
|
17
17
|
//
|
|
18
|
-
//
|
|
18
|
+
// RULING S2 (2026-08-13) re-scoped rather than adopting it: "the mechanical card fields
|
|
19
19
|
// (headings, ids, links, driver-stamped values) move to code NOW — uncontested. The prose half is
|
|
20
20
|
// decided by EVIDENCE: after config #40 lands, build one matter both ways and the owner reads the two
|
|
21
21
|
// cards side by side. If the rendered card reads worse, the model pass stays, on typed inputs."
|
|
@@ -61,6 +61,11 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
|
|
|
61
61
|
why: "counts read off the coverage/placement unions" },
|
|
62
62
|
{ file: "driver/pipeline-knockout.mjs", noun: "recovered", sites: 1, atWriteSite: 1, verdict: "result",
|
|
63
63
|
why: "written after the chunk files were actually removed" },
|
|
64
|
+
{ file: "driver/pipeline-knockout.mjs", noun: "outcome", sites: 3, atWriteSite: 3, verdict: "result",
|
|
65
|
+
why: "the reviewing pass's own verdict on itself, read at each writing site: `nothing-flagged` after "
|
|
66
|
+
+ "the measurement came back empty, `applied` after the rewrite passed the merged gate and was "
|
|
67
|
+
+ "written, and the `keep()` forms after the artifact or the gate refused it. Every one is a fact "
|
|
68
|
+
+ "about what already happened to the record, never the return of the call being logged" },
|
|
64
69
|
{ file: "driver/pipeline.mjs", noun: "closed", sites: 6, atWriteSite: 4, verdict: "result",
|
|
65
70
|
why: "closure counts derived from the landed set (`qids.filter(q => landed.has(q)).length`) and from the receipt" },
|
|
66
71
|
{ file: "driver/pipeline.mjs", noun: "executed", sites: 4, atWriteSite: 3, verdict: "result",
|
|
@@ -29,9 +29,13 @@
|
|
|
29
29
|
* @param {string[]|null} o.onDisk keys in the configured store, sorted — null when none is configured
|
|
30
30
|
* @param {string[]} o.bundledDemos the roster shipped in the product repo, sorted
|
|
31
31
|
* @param {boolean} o.expectDemos CLEAROTRON_E2E_EXPECT_DEMO_ROSTER=1 — "this instance is allowed to break"
|
|
32
|
+
* @param {{readable: boolean, accounts: string[]|null}} [o.caller]
|
|
33
|
+
* the claims of the key the door was asked with, in opsTokenPosture's
|
|
34
|
+
* shape. ABSENT, or `accounts: null`, means uncapped — every account — the
|
|
35
|
+
* way triggerCapGap reads a null cap. Never an empty one.
|
|
32
36
|
* @returns {{state: "pass"|"fail"|"skip", message: string}}
|
|
33
37
|
*/
|
|
34
|
-
export function rosterVerdict({ keys, onDisk, bundledDemos, expectDemos }) {
|
|
38
|
+
export function rosterVerdict({ keys, onDisk, bundledDemos, expectDemos, caller }) {
|
|
35
39
|
const sameSet = (a, b) => a.length === b.length && a.every((k, i) => k === b[i]);
|
|
36
40
|
const isBundled = sameSet(keys, bundledDemos);
|
|
37
41
|
|
|
@@ -40,17 +44,56 @@ export function rosterVerdict({ keys, onDisk, bundledDemos, expectDemos }) {
|
|
|
40
44
|
return { state: "skip", message: "the session resolved zero accounts — this probe is unscoped for this door, so the roster is NOT probed rather than failed" };
|
|
41
45
|
|
|
42
46
|
if (onDisk) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
// THE DOOR ANSWERS FOR THE KEY THAT ASKED. `list_profiles` is narrowed to the caller's account cap,
|
|
48
|
+
// so a key minted before a company existed does not list it, and correctly. Compared with the whole
|
|
49
|
+
// store, that read as the door and the store disagreeing, and the message below then pointed at a
|
|
50
|
+
// stale roster (measured in testing, 2026-09-10). So the store is narrowed by the same cap before the
|
|
51
|
+
// two are compared; a company outside the cap is the key's gap, which the caller reports on its own line.
|
|
52
|
+
const unreadable = caller?.readable === false;
|
|
53
|
+
const cap = !unreadable && Array.isArray(caller?.accounts) ? caller.accounts : null;
|
|
54
|
+
const expected = cap ? onDisk.filter((k) => cap.includes(k)) : onDisk;
|
|
55
|
+
if (!sameSet(keys, expected) && unreadable)
|
|
56
|
+
// A KEY WHOSE CLAIMS CANNOT BE READ may be narrowing the answer, and a narrowing looks exactly like a
|
|
57
|
+
// door that disagrees with its store. Neither passed nor failed: not compared, and said so.
|
|
58
|
+
return { state: "skip", message: `the door sees ${keys.length} customer(s) (${keys.join(", ")}), `
|
|
59
|
+
+ `the configured store holds ${onDisk.length} (${onDisk.join(", ")}), and the claims of the key this `
|
|
60
|
+
+ "check asked with could not be read — so a narrowing by that key cannot be told from a door that "
|
|
61
|
+
+ "disagrees with its store. NOT compared" };
|
|
62
|
+
if (!sameSet(keys, expected))
|
|
63
|
+
return { state: "fail", message: `the door sees ${keys.length} customer(s) (${keys.join(", ")}), `
|
|
64
|
+
+ `the configured store holds ${onDisk.length} (${onDisk.join(", ")})`
|
|
65
|
+
+ (cap ? `, ${expected.length} of them within the cap of the key this check asked with` : "")
|
|
66
|
+
+ " — they disagree"
|
|
67
|
+
// THE CAUSE IS NOT DERIVABLE FROM THIS COMPARISON, and it used to be asserted anyway. `isBundled`
|
|
68
|
+
// is a set equality over NAMES, and a configured store ordinarily CONTAINS the bundled demo
|
|
69
|
+
// names — so "the door resolved exactly the bundled roster" is equally true of a door with no
|
|
70
|
+
// store and of a door whose store simply has not gained the newest company yet. Measured
|
|
71
|
+
// 2026-09-09: a company added to the configured store minutes earlier, a door still holding the
|
|
72
|
+
// roster it read at boot, and this line reporting a variable that was reaching the service
|
|
73
|
+
// perfectly well. What the check can see is the two lists; which of the two causes produced them
|
|
74
|
+
// is a question for whoever reads it.
|
|
75
|
+
+ (isBundled
|
|
76
|
+
? ". The door's set is name-identical to the bundled demo roster, which this comparison cannot "
|
|
77
|
+
+ "tell apart from a configured store carrying those same names: it is consistent BOTH with "
|
|
78
|
+
+ "CLEAROTRON_CUSTOMERS_DIR not reaching the service AND with the door still holding a roster "
|
|
79
|
+
+ "it read before the store changed. Check the door's environment and when it last read the store."
|
|
80
|
+
: "") };
|
|
46
81
|
return { state: "pass", message: `${keys.length} customer(s), matching the configured store`
|
|
82
|
+
+ (cap && expected.length < onDisk.length
|
|
83
|
+
? ` within the cap of the key this check asked with (${expected.length} of ${onDisk.length})` : "")
|
|
47
84
|
+ (expectDemos ? " — and this instance declares itself a test box, which the store's own CI keeps free of real client bundles" : "") };
|
|
48
85
|
}
|
|
49
86
|
|
|
50
87
|
if (isBundled && expectDemos)
|
|
51
88
|
return { state: "pass", message: `the bundled demo roster (${keys.join(", ")}) — correct for a test instance with no configured store, which must never see real client bundles` };
|
|
52
89
|
if (isBundled)
|
|
53
|
-
|
|
90
|
+
// NO STORE IS VISIBLE TO THIS PROCESS, so unlike the branch above there is no second list to print
|
|
91
|
+
// and the fallback reading is the likeliest one. It is still a reading: this process not holding
|
|
92
|
+
// CLEAROTRON_CUSTOMERS_DIR is not proof the service does not, and the comparison is over names a
|
|
93
|
+
// configured store would also carry. Named as the probable cause rather than the established one.
|
|
94
|
+
return { state: "fail", message: `the door resolved ${keys.length} account(s) name-identical to the bundled demo roster (${keys.join(", ")}), `
|
|
95
|
+
+ `and no configured store is visible from here to compare against — most likely CLEAROTRON_CUSTOMERS_DIR is not reaching the service, `
|
|
96
|
+
+ `in which case every real customer will be refused. Confirm at the door's own environment before acting: a store holding these same names would look identical here` };
|
|
54
97
|
if (expectDemos)
|
|
55
98
|
return { state: "fail", message: `CLEAROTRON_E2E_EXPECT_DEMO_ROSTER=1 says this is a test instance with no configured store, but the door resolved ${keys.length} NON-demo customers — real client config has reached an instance that must not have it` };
|
|
56
99
|
return { state: "pass", message: `${keys.length} customers (no CLEAROTRON_CUSTOMERS_DIR in THIS process to compare against)` };
|
package/driver/run-activity.mjs
CHANGED
|
@@ -52,7 +52,7 @@ export function readActivity(ledgerPath, { limit = 50, now = Date.now(), staleAf
|
|
|
52
52
|
if (!Number.isFinite(tsMs) || nowMs - tsMs <= staleAfterMs) return r; // fresh (or undatable) → leave as-is
|
|
53
53
|
return { ...r, state: "failed", stale: true, note: r.note || "no terminal record — presumed interrupted (stale)" };
|
|
54
54
|
};
|
|
55
|
-
// Presentation retire (2026-07-06
|
|
55
|
+
// Presentation retire (2026-07-06 steer — same reversible pattern as run rows /): the
|
|
56
56
|
// ledger stays append-only — APPEND a record with the same id carrying `retired: true` to hide the
|
|
57
57
|
// row from every activity surface; append another without it to resurface. Forensics untouched.
|
|
58
58
|
return [...byId.values()].filter((r) => r.retired !== true).map(reap).sort((a, b) => String(b.ts || "").localeCompare(String(a.ts || ""))).slice(0, limit);
|