clearotron 0.4.0 → 0.4.1-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/THIRD-PARTY-NOTICES.md +2 -2
- package/bin/README.md +1 -1
- package/bin/clearotron.mjs +2 -0
- package/bin/mcp.mjs +80 -0
- package/bin/start.mjs +14 -1
- package/build-info.json +2 -2
- package/demo/README.md +1 -1
- package/docs/SECURITY-OWASP.md +21 -13
- package/docs/SECURITY.md +20 -14
- package/docs/architecture/04-configuration-reference.md +3 -3
- package/docs/architecture/05-config-governance.md +1 -1
- package/docs/architecture/06-operations-runbook.md +5 -4
- package/docs/architecture/08-development-guide.md +3 -1
- package/docs/architecture/09-security-and-data.md +16 -11
- package/driver/CHANGELOG.md +29 -0
- package/driver/binding-layers.mjs +28 -6
- package/driver/citation-census.json +3 -3
- package/driver/common-law-coverage-status.mjs +52 -0
- package/driver/common-law-receipts.mjs +1 -1
- package/driver/compare.mjs +1 -1
- package/driver/connotation-search.mjs +5 -5
- package/driver/contract-audit.mjs +6 -6
- package/driver/contract-dictation-registry.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +18 -17
- package/driver/contract-vocabulary.mjs +145 -132
- package/driver/declination-call.mjs +5 -1
- package/driver/degraded-parts.mjs +10 -3
- package/driver/dev-portal.mjs +2 -1
- package/driver/dispatch-record.mjs +19 -0
- package/driver/doubt-closure-tool.mjs +2 -2
- package/driver/driver.config.mjs +39 -6
- package/driver/engagement-receipt.mjs +1 -1
- package/driver/engine/CONTRACT.md +41 -1
- package/driver/engine/anthropic-agent.mjs +56 -4
- package/driver/engine/mcp/codex-config.mjs +8 -3
- package/driver/engine/mcp/coverage-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/mcp/recording-server.mjs +32 -5
- package/driver/engine/mcp/stdio-server.mjs +45 -7
- package/driver/engine/mcp/supplemental.mjs +26 -1
- package/driver/engine/openai-agent.mjs +113 -20
- package/driver/engine/session-record.mjs +223 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/findings-model.mjs +4 -4
- package/driver/flag-snapshot.mjs +2 -1
- package/driver/form-neighbourhood.mjs +7 -7
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/framework.mjs +22 -0
- package/driver/gateway.mjs +96 -14
- package/driver/jx.mjs +1 -1
- package/driver/knockout-review-record.mjs +55 -1
- package/driver/named-band.mjs +4 -0
- package/driver/owner-screen.mjs +7 -4
- package/driver/package.json +1 -1
- package/driver/phase0.mjs +13 -2
- package/driver/pipeline-knockout.mjs +15 -13
- package/driver/pipeline.mjs +132 -48
- package/driver/portal-families.mjs +3 -3
- package/driver/portal-service.mjs +6 -5
- package/driver/predelivery-lint.mjs +19 -19
- package/driver/products.mjs +1 -1
- package/driver/profiles.mjs +4 -3
- package/driver/publish/index.mjs +1 -1
- package/driver/publish/knockout.mjs +5 -4
- package/driver/publish/office-record-links.mjs +17 -2
- package/driver/publish/publish-inputs.mjs +3 -3
- package/driver/publish/render-knockout.mjs +17 -5
- package/driver/publish/render.mjs +4 -2
- package/driver/publish/xlsx.mjs +1 -1
- package/driver/record-fold.mjs +177 -0
- package/driver/register-count.mjs +20 -3
- package/driver/register-coverage.mjs +1 -1
- package/driver/register-plan.mjs +83 -18
- package/driver/register-records.mjs +7 -0
- package/driver/registry-fidelity.mjs +2 -2
- package/driver/repairs.mjs +1 -1
- package/driver/report-card-record.mjs +4 -4
- package/driver/result-noun-fields.mjs +3 -3
- package/driver/scope-facts.mjs +11 -3
- package/driver/scope-rules.mjs +2 -2
- package/driver/search-policy.mjs +20 -1
- package/driver/skills/clearance-common-law/SKILL.md +22 -22
- package/driver/skills/clearance-register/digest.md +3 -3
- package/driver/skills/clearance-register/register-recipes.md +11 -11
- package/driver/skills/clearance-register/status-rules.md +1 -1
- package/driver/skills/clearance-register/unit.md +1 -1
- package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -2
- package/driver/skills/clearance-search/phase2-execution.md +6 -5
- package/driver/skills/clearance-search/synthesis-rules.md +4 -4
- package/driver/skills/clearance-search/worked-examples.md +39 -39
- package/driver/skills/clearance-variants/SKILL.md +16 -16
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/stage-context.mjs +36 -1
- package/driver/stages-knockout.mjs +6 -5
- package/driver/stages.mjs +30 -30
- package/driver/suite-census.json +259 -55
- package/driver/synthesis-record.mjs +5 -0
- package/driver/unit-inventory.mjs +17 -28
- package/driver/verify.mjs +7 -7
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/CONNECT.md +9 -3
- package/mcp-server/demo-mode.mjs +10 -0
- package/mcp-server/lib/evidence.mjs +8 -8
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/runs.mjs +7 -1
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/package.json +2 -2
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/server.mjs +16 -2
- package/package.json +4 -3
- package/portal-ui/package.json +3 -3
- package/providers/_shared/count.mjs +9 -2
- package/providers/_shared/enumerate.mjs +125 -41
- package/providers/_shared/execute-plan.mjs +15 -4
- package/providers/corsearch/src/capabilities.js +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +2 -2
- package/providers/signa/src/capabilities.js +32 -2
- package/providers/signa/src/core.js +262 -36
- package/scripts/band-shape-probe.mjs +2 -2
- package/scripts/deprecate-below.mjs +28 -4
- package/scripts/e2e-first-time.mjs +17 -2
- package/scripts/env-audit.mjs +3 -2
- package/scripts/freeze-example-run.mjs +10 -10
- package/scripts/mcp-registry-listing.mjs +199 -0
- package/scripts/mint-plugin-folder.mjs +65 -0
- package/scripts/release-version.mjs +44 -0
- package/scripts/score.mjs +1 -1
- package/shared/register-floor.mjs +19 -0
- package/shared/scope.mjs +18 -2
- package/shared/trigger-lane.mjs +1 -1
- package/skills/clearotron-ops/SKILL.md +81 -24
package/driver/pipeline.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// verdict, and never parks (there is no LLM continuation decision anywhere). CLI: `node pipeline.mjs --job <file.json>`.
|
|
6
6
|
|
|
7
7
|
import "../shared/env-local.mjs"; // side effect: apply <repo>/.env when THIS file is the CLI entry (never on library import)
|
|
8
|
-
import "./engine/mcp/http-dispatcher.mjs"; // side effect: raise undici headersTimeout (code-side fetches)
|
|
8
|
+
import "./engine/mcp/http-dispatcher.mjs"; import { recordStatusesFromFindings } from "./common-law-coverage-status.mjs"; // side effect: raise undici headersTimeout (code-side fetches); the second records a common-law ledger's statuses in code
|
|
9
9
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, copyFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { join, dirname, basename, resolve } from "node:path"; // resolve: the resume line must work from any cwd
|
|
@@ -72,7 +72,7 @@ import { classifyGroundsNote } from "./grounds-grammar.mjs"; // — a charged
|
|
|
72
72
|
// — the register coverage form: the driver writes it before the digest dispatches, renders the
|
|
73
73
|
// `## Coverage ledger` table and the machine ledger FROM it after the pass, and unions it across passes.
|
|
74
74
|
import { documentCoverage, renderDocumentCoverageSection, spliceDocumentCoverage } from "./document-coverage.mjs"; //
|
|
75
|
-
import { buildCoverageAbsenceForm, coverageAbsenceGaps, coverageFormAbsence, coverageFormBrief, renderCoverageAbsenceSection, renderCoverageLedgerSection, spliceCoverageLedger, renderCoverageLedgerJsonFromForm } from "./coverage-form.mjs";
|
|
75
|
+
import { buildCoverageAbsenceForm, coverageAbsenceGaps, coverageFormAbsence, coverageFormBrief, renderCoverageAbsenceSection, renderCoverageLedgerSection, spliceCoverageLedger, renderCoverageLedgerJsonFromForm, unitLabel } from "./coverage-form.mjs";
|
|
76
76
|
import { unionCoverageForm } from "./coverage-union.mjs";
|
|
77
77
|
import { armCoverageForm, coverageFormInput, coverageFormPaths, coverageFormStamp, readCoverageForm, readCoverageFormInput, waitingFamilyStates, writeCoverageForm } from "./coverage-form-io.mjs";
|
|
78
78
|
import { releasedFamilyQids, readWithheldFamilies } from "./withheld-families.mjs"; // the waiting families the reading turn released join as dictated entries
|
|
@@ -148,6 +148,7 @@ import { beginAnswerMemory, endAnswerMemory } from "../providers/_shared/answer-
|
|
|
148
148
|
import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause } from "./screen-gate.mjs";
|
|
149
149
|
import { emptyQueue, coerceQueue, mintItem, pendingItems, markFlushed, receiptKeyFor,
|
|
150
150
|
buildFlushFollowup, runPostFlushGateRepair } from "./digest-queue.mjs"; // (t1cd) — the digest-trigger funnel
|
|
151
|
+
import { foldFindingsFile, foldAfterSave, openPass, modelRecordPath, snapshotFold, restoreFold } from "./record-fold.mjs"; // the folded record from the delivery seam on
|
|
151
152
|
import { writeStamp, stageStaleness, restamp, restampStage, staleOnPath, reconcileStamps, shaOf } from "./stage-freshness.mjs";
|
|
152
153
|
import { parseManifestVariants, variantsParseFailure, findCoverageLimitedCells, partitionClosableCells, findSimilarListingSignals,
|
|
153
154
|
GRID_HALVES, GRID_SEATS, MEANING_SEAT, splitGridSpec, halfOfTerm, balanceClosureCells, mergeGrids, mergeCommonLawFindings, openChannelRows,
|
|
@@ -2280,7 +2281,7 @@ export function deriveFormNeighbourhood(ctx) { // @internal — exported for i
|
|
|
2280
2281
|
} catch { /* scope ledger optional — markets are informational; no ledger ⇒ nothing dropped (fail-open) */ }
|
|
2281
2282
|
// NOT scoped here: the transliteration SCRIPT set. `markets` is passed through but never narrows
|
|
2282
2283
|
// SUPPORTED_SCRIPTS, so latin-diacritic/german/nordic/cyrillic/greek run on every matter regardless of the
|
|
2283
|
-
// jurisdictions judgment applied. That is what generated the five
|
|
2284
|
+
// jurisdictions judgment applied. That is what generated the five Drivers Haven diacritics — but the fold-dedupe
|
|
2284
2285
|
// in formNeighbourhood removes them at the source, and the surviving cross-script forms (ß→ss, Cyrillic and
|
|
2285
2286
|
// Greek homoglyphs) are genuinely distinct strings that cost one term each. Deferred deliberately: narrowing
|
|
2286
2287
|
// scripts by market is a coverage decision, and the harm it was meant to fix is already gone.
|
|
@@ -3933,7 +3934,7 @@ export function findFloorBreaches(ledger, floorAxes) { // @internal
|
|
|
3933
3934
|
* `[]`: no manifest, unreadable, unparseable. That is the honest default here and not an absence read as
|
|
3934
3935
|
* a pass, because the whole mechanism is opt-in — a run with no designation owes no floor, and a run whose
|
|
3935
3936
|
* manifest cannot be read has no designation to honour. The refusal for an absent or unparseable manifest
|
|
3936
|
-
* belongs to the stage that writes it and already exists there — verify.mjs
|
|
3937
|
+
* belongs to the stage that writes it and already exists there — validators.variantManifest in verify.mjs runs the same parser
|
|
3937
3938
|
* through `checkSiblingJson` and fails clearance-variants with `variantmodel_missing`. Checked, because
|
|
3938
3939
|
* "something else refuses it" is exactly the assumption that turns a swallowed error into a silent pass.
|
|
3939
3940
|
*/
|
|
@@ -4802,7 +4803,7 @@ async function stage(name, ctx, opts = {}) {
|
|
|
4802
4803
|
let wedgeCycles = 0;
|
|
4803
4804
|
for (;;) {
|
|
4804
4805
|
const r = await stageWithChain(name, ctx, opts);
|
|
4805
|
-
if (r.ok || r.fail !== "lane_wedge" || wedgeCycles >= LANE_WEDGE_CHAIN_RETRIES) { sweepStrayArtifacts(ctx, name); return r; }
|
|
4806
|
+
if (r.ok || r.fail !== "lane_wedge" || wedgeCycles >= LANE_WEDGE_CHAIN_RETRIES) { sweepStrayArtifacts(ctx, name); await afterLateSynthesisSave(name, ctx, r); return r; }
|
|
4806
4807
|
wedgeCycles++;
|
|
4807
4808
|
note(`[${name}] command-lane wedge — the stage timed out with zero progress (saturated lane). Waiting ${Math.round(LANE_WEDGE_BACKOFF_MS / 1000)}s for it to clear, then re-dispatching (${wedgeCycles}/${LANE_WEDGE_CHAIN_RETRIES}); full per-attempt timeouts preserved.`);
|
|
4808
4809
|
try { runLog(ctx.paths.runDir, { event: "lane-wedge-retry", stage: name + (ctx.axis ? `:${ctx.axis}` : ""), cycle: wedgeCycles, max: LANE_WEDGE_CHAIN_RETRIES }); } catch { /* telemetry best-effort */ }
|
|
@@ -5922,6 +5923,14 @@ function snapshotFindingsForCorrections(P, runDir) {
|
|
|
5922
5923
|
try { raw = readFileSync(P.findings, "utf8"); } catch { /* legacy / not-yet-populated */ }
|
|
5923
5924
|
if (raw == null) return null;
|
|
5924
5925
|
const sha = createHash("sha256").update(raw).digest("hex");
|
|
5926
|
+
// THE RECORD IN THE MODEL'S NUMBERING, beside the one on disk. The review's flags name findings in the
|
|
5927
|
+
// model's numbering, so the corrective body compares the record the model wrote; once the delivered
|
|
5928
|
+
// record is folded the two differ (record-fold.mjs). The fold is held too, for a rollback to put back.
|
|
5929
|
+
const modelAt = modelRecordPath(runDir, P.findings);
|
|
5930
|
+
let modelRaw = raw;
|
|
5931
|
+
if (modelAt !== P.findings) { try { modelRaw = readFileSync(modelAt, "utf8"); } catch { modelRaw = raw; } }
|
|
5932
|
+
const model = { raw: modelRaw, sha: modelRaw === raw ? sha : createHash("sha256").update(modelRaw).digest("hex") };
|
|
5933
|
+
const fold = snapshotFold(runDir);
|
|
5925
5934
|
try {
|
|
5926
5935
|
const tmp = driverDir(runDir, "findings-pre-corrective.json.tmp");
|
|
5927
5936
|
// PR-4 (snapshot-before-overwrite): store the FULL pre-corrective text, not just its sha — a
|
|
@@ -5931,7 +5940,7 @@ function snapshotFindingsForCorrections(P, runDir) {
|
|
|
5931
5940
|
writeFileSync(tmp, JSON.stringify({ ts: new Date().toISOString(), sha, text: raw }, null, 2));
|
|
5932
5941
|
renameSync(tmp, driverDir(runDir, "findings-pre-corrective.json"));
|
|
5933
5942
|
} catch { /* advisory sidecar */ }
|
|
5934
|
-
return { raw, sha };
|
|
5943
|
+
return { raw, sha, model, fold };
|
|
5935
5944
|
}
|
|
5936
5945
|
/**
|
|
5937
5946
|
*, T3b — THE CORRECTIVE PASS FAILED VALIDATION. RESTORE THE LAST GOOD STATE.
|
|
@@ -5979,6 +5988,7 @@ export function rollbackCorrectivePass(P, runDir, pre, fail) { // @internal
|
|
|
5979
5988
|
// Atomic, like every other write to this file: a half-restored findings.json is a worse state than
|
|
5980
5989
|
// either of the two this is choosing between.
|
|
5981
5990
|
atomicWrite(P.findings, pre.raw);
|
|
5991
|
+
if (pre.fold) restoreFold(runDir, pre.fold); // the record the model wrote and the map, as they stood
|
|
5982
5992
|
}
|
|
5983
5993
|
const record = { ts: new Date().toISOString(), reason: String(fail?.fail ?? "failed").slice(0, 200),
|
|
5984
5994
|
restored: changed, preSha: pre.sha, failedSha: nowSha };
|
|
@@ -6023,9 +6033,11 @@ export function rollbackCorrectivePass(P, runDir, pre, fail) { // @internal
|
|
|
6023
6033
|
*/
|
|
6024
6034
|
export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks, namedLines = []) { // @internal
|
|
6025
6035
|
if (!pre?.raw) return null; // nothing held — nothing to compare against
|
|
6036
|
+
// In the model's numbering: the flags name its ordinals, and a folded record renumbers, so a finding
|
|
6037
|
+
// removed from the folded record would shift every ordinal after it (record-fold.mjs).
|
|
6026
6038
|
let preDoc = null, postDoc = null, postRaw = null;
|
|
6027
|
-
try { preDoc = JSON.parse(pre.raw); } catch { return null; }
|
|
6028
|
-
try { postRaw = readFileSync(P.findings, "utf8"); postDoc = JSON.parse(postRaw); } catch { return null; }
|
|
6039
|
+
try { preDoc = JSON.parse(pre.model?.raw ?? pre.raw); } catch { return null; }
|
|
6040
|
+
try { postRaw = readFileSync(modelRecordPath(runDir, P.findings), "utf8"); postDoc = JSON.parse(postRaw); } catch { return null; }
|
|
6029
6041
|
if (!preDoc || !postDoc || typeof postDoc !== "object") return null;
|
|
6030
6042
|
|
|
6031
6043
|
const norm = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
@@ -6095,6 +6107,7 @@ export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks,
|
|
|
6095
6107
|
leftRemoved: leftRemoved.map(brief),
|
|
6096
6108
|
};
|
|
6097
6109
|
atomicWrite(P.findings, JSON.stringify(merged, null, 2) + "\n");
|
|
6110
|
+
foldAfterSave(runDir, P.findings); // after the seam the repaired record is folded again, as a save's is
|
|
6098
6111
|
try { atomicWrite(driverDir(runDir, "corrective-repair.json"), JSON.stringify(record, null, 2) + "\n"); }
|
|
6099
6112
|
catch { /* sidecar is best-effort; the runLog event is the record that decides */ }
|
|
6100
6113
|
return record;
|
|
@@ -6218,10 +6231,11 @@ async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
|
|
|
6218
6231
|
const named = correctionNamedSet(P);
|
|
6219
6232
|
const readState = () => {
|
|
6220
6233
|
let raw = null, corrections = null;
|
|
6221
|
-
|
|
6234
|
+
// the record the model wrote: a change the fold hides is still a change it made (record-fold.mjs)
|
|
6235
|
+
try { raw = readFileSync(modelRecordPath(run.runDir, P.findings), "utf8"); } catch { return { stale: false }; }
|
|
6222
6236
|
try { corrections = parseFindingsJsonLenient(raw).corrections; } catch { /* shape defects ride the normal ladder */ }
|
|
6223
6237
|
const sha = createHash("sha256").update(raw).digest("hex");
|
|
6224
|
-
return { stale: named.length > 0 && sha === pre.sha && !(corrections && corrections.applied), sha };
|
|
6238
|
+
return { stale: named.length > 0 && sha === (pre.model?.sha ?? pre.sha) && !(corrections && corrections.applied), sha };
|
|
6225
6239
|
};
|
|
6226
6240
|
if (!readState().stale) return;
|
|
6227
6241
|
note(`[corrections] findings.json unchanged after the corrective pass (review names: ${named.join(", ")}) — demanding the re-emit`);
|
|
@@ -7014,7 +7028,7 @@ export function coverageRowAreaLabel(axis, unit) { // @internal
|
|
|
7014
7028
|
return coverageRowAreaFrom(axis, unit, labelled, "(all of it)");
|
|
7015
7029
|
}
|
|
7016
7030
|
|
|
7017
|
-
export function coverageJudgmentRows(ledgerRows, planExecution) { // @internal
|
|
7031
|
+
export function coverageJudgmentRows(ledgerRows, planExecution, plan = null) { // @internal
|
|
7018
7032
|
const open = [];
|
|
7019
7033
|
for (const r of ledgerRows ?? []) {
|
|
7020
7034
|
// `withheld-by-judgment` joins `confirmed-clean` in NOT reaching the reader, and for the opposite
|
|
@@ -7040,13 +7054,13 @@ export function coverageJudgmentRows(ledgerRows, planExecution) { // @internal
|
|
|
7040
7054
|
const host = axisOf.has(q) ? open.find((o) => o.axis === axisOf.get(q)) : null;
|
|
7041
7055
|
if (host) { host.qids.push(q); claimed.add(q); }
|
|
7042
7056
|
}
|
|
7043
|
-
//
|
|
7044
|
-
//
|
|
7057
|
+
// A claimed qid is printed nowhere: the plan-execution receipt keeps it for every gate to join on, and a
|
|
7058
|
+
// row a lawyer reads names its slice, never an engine identifier (ruled 2026-10-01). One row per slice.
|
|
7045
7059
|
const rows = open.map((o) => ({ area: o.area, ...(o.areaLabel ? { areaLabel: o.areaLabel } : {}),
|
|
7046
|
-
note: o.
|
|
7060
|
+
note: o.note }));
|
|
7047
7061
|
for (const qid of Array.isArray(planExecution?.missing) ? planExecution.missing : []) {
|
|
7048
7062
|
const q = String(qid ?? "").trim();
|
|
7049
|
-
if (q && !claimed.has(q)) rows.push({ area: q, note: "planned and not executed this run
|
|
7063
|
+
if (q && !claimed.has(q)) rows.push({ area: q, ...orphanAreaLabel(q, axisOf.get(q), plan), note: "planned and not executed this run" });
|
|
7050
7064
|
}
|
|
7051
7065
|
return rows.filter((r) => r.area && r.note);
|
|
7052
7066
|
}
|
|
@@ -7060,7 +7074,7 @@ export function stampCoverageJudgmentRows(P, runDir, note, ctx) { // @internal
|
|
|
7060
7074
|
runLog(runDir, { event: "coverage-judgment-rows", judgment: false, reason: "synthesis emitted no coverage_judgment — none is invented here" });
|
|
7061
7075
|
return;
|
|
7062
7076
|
}
|
|
7063
|
-
const rows = coverageJudgmentRows(loadCoverageLedger(runDir).rows, readPlanExecution(ctx));
|
|
7077
|
+
const rows = coverageJudgmentRows(loadCoverageLedger(runDir).rows, readPlanExecution(ctx), ctx?.registerPlan ?? null);
|
|
7064
7078
|
const discarded = Array.isArray(cj.rows) ? cj.rows.length : 0;
|
|
7065
7079
|
const next = { ...doc, coverage_judgment: { ...cj } };
|
|
7066
7080
|
if (rows.length) next.coverage_judgment.rows = rows;
|
|
@@ -7136,7 +7150,7 @@ function injectDeferralCoverage(P, runDir, note) {
|
|
|
7136
7150
|
// the two constants below BYTE-FOR-BYTE, which is why they stay exported and are asserted against the
|
|
7137
7151
|
// derivation in the tests.
|
|
7138
7152
|
//
|
|
7139
|
-
// SERP_LANES is deliberately untouched: zh-only there is the shipped design (jx-lanes.mjs
|
|
7153
|
+
// SERP_LANES is deliberately untouched: zh-only there is the shipped design (SERP_LANES declared in jx-lanes.mjs), not
|
|
7140
7154
|
// drift, and generalising the platform grid is a different piece of work.
|
|
7141
7155
|
//
|
|
7142
7156
|
// ── THE REMEDY CLAUSE NAMES A PRODUCT, AND FOR A WHILE IT NAMED A DELETED ONE ───────────────────────
|
|
@@ -7231,7 +7245,7 @@ export function scriptLaneRanOnRun(runDir, lane, { searchPolicy = null, env = pr
|
|
|
7231
7245
|
// zh — shipped the full Phase-4 deepening: the SERP platform grid and the native read. Either
|
|
7232
7246
|
// unit running IS the lane running (they gate independently; one on and one off still
|
|
7233
7247
|
// searched in Chinese), so the legs stay exactly as they were.
|
|
7234
|
-
// ja/ko — shipped as slice-1 CANDIDATE lanes only (jx-lanes.mjs
|
|
7248
|
+
// ja/ko — shipped as slice-1 CANDIDATE lanes only (LANGUAGE_LANES declared in jx-lanes.mjs): no SERP_LANES entry, so
|
|
7235
7249
|
// runJxSerpGrid hard-refuses them and neither unit flag can ever describe them. The only
|
|
7236
7250
|
// work these lanes do is the candidate fold, so "ran" is the fold having ACCEPTED
|
|
7237
7251
|
// candidates — a frozen lane decision with an empty fold searched nothing and owes the row.
|
|
@@ -7477,26 +7491,15 @@ function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDay
|
|
|
7477
7491
|
}
|
|
7478
7492
|
}
|
|
7479
7493
|
|
|
7494
|
+
// THE SEAM'S FOLD. From here on every accepted save folds the record again on acceptance, so the record
|
|
7495
|
+
// the reviewer, the cards and the delivery read stays folded (record-fold.mjs, design 2026-10-03). doc 50 —
|
|
7496
|
+
// on a v4 record the merge base is the WORST BAND by the frozen manifest's order. No duplicates leave the
|
|
7497
|
+
// file byte-identical; a folded shape that does not validate keeps the record as written.
|
|
7480
7498
|
function consolidateFindingsFile(P, note, manifest = null) {
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
// doc 50 — on a v4 record the merge base is the WORST BAND by the frozen manifest's order
|
|
7486
|
-
const { findings, merges, ordinalMap } = consolidateFindings(doc.findings, (doc.schema_version ?? 1) >= 4 ? manifest : null);
|
|
7487
|
-
if (!merges.length) return; // no duplicates → leave the file byte-identical
|
|
7488
|
-
const next = { ...doc, findings };
|
|
7489
|
-
// spec 64 — actions follow the renumber: a merged-away ordinal remaps to its kept finding's new
|
|
7490
|
-
// ordinal (the action still applies to the consolidated conflict — dropping the reference would
|
|
7491
|
-
// silently un-condition a live demand).
|
|
7492
|
-
if (Array.isArray(doc.actions)) next.actions = remapActionOrdinals(doc.actions, ordinalMap);
|
|
7493
|
-
parseFindingsJson(JSON.stringify(next)); // re-validate the consolidated shape (throws → catch keeps original)
|
|
7494
|
-
atomicWrite(P.findings, `${JSON.stringify(next, null, 2)}\n`); // B5 — a crash mid-write must never leave a truncated findings.json
|
|
7495
|
-
for (const m of merges)
|
|
7496
|
-
note(`[consolidate] ${m.owner} — ${m.mark}: folded ${m.dropped.length} duplicate filing(s) (ord ${m.dropped.join(", ")}) into one card`);
|
|
7497
|
-
} catch (e) {
|
|
7498
|
-
note(`[consolidate] skipped: ${String(e?.message || e).replace(/\s+/g, " ").slice(0, 100)}`);
|
|
7499
|
-
}
|
|
7499
|
+
const { merges, error } = foldFindingsFile(P.runDir, P.findings, manifest, { seam: true });
|
|
7500
|
+
for (const m of merges)
|
|
7501
|
+
note(`[consolidate] ${m.owner} — ${m.mark}: folded ${m.dropped.length} duplicate filing(s) (ord ${m.dropped.join(", ")}) into one card`);
|
|
7502
|
+
if (error) note(`[consolidate] skipped: ${error.slice(0, 100)}`);
|
|
7500
7503
|
}
|
|
7501
7504
|
// minor (a): a finding earns a FULL prose card when composite ≥ 3 (on-field) OR level ∈ {A,B} (high legal
|
|
7502
7505
|
// exposure even at a low practical composite — a mis-rated-low mark must not be reduced to a structured-only row).
|
|
@@ -8495,9 +8498,15 @@ function postponeRun(e, run, meta = {}) {
|
|
|
8495
8498
|
* PURE: a job in, a plain object out, no IO.
|
|
8496
8499
|
*/
|
|
8497
8500
|
export function instructedScopeOf(job) {
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
+
// THE MARKS ARE ALWAYS A LIST. A job that names its mark only as `markName` — a queue file written by
|
|
8502
|
+
// hand or by a forwarding agent, a resume rebuilt from status.json — recorded the bare string, and the
|
|
8503
|
+
// declination tool's identical-mark refusal reads a list, so on those runs it saw no mark and never
|
|
8504
|
+
// fired. An array that names no mark falls back the same way the intake gate does; null when nothing
|
|
8505
|
+
// names one.
|
|
8506
|
+
const named = Array.isArray(job?.marks)
|
|
8507
|
+
? job.marks.map((m) => (typeof m === "string" ? m : m?.name)).filter(Boolean) : [];
|
|
8508
|
+
const single = job?.markName ?? job?.name ?? null;
|
|
8509
|
+
const markNames = named.length ? named : (single ? [single] : null);
|
|
8501
8510
|
return {
|
|
8502
8511
|
marks: markNames,
|
|
8503
8512
|
classes: job?.classes ?? null,
|
|
@@ -8793,6 +8802,9 @@ async function pipelineInner(job, opts = {}) {
|
|
|
8793
8802
|
// BEFORE doc 50 backfills its framework sidecar from the frozen frameworkPath (deploy-boundary case) so
|
|
8794
8803
|
// the v4 gates always have vocabulary; a run that already has one reads it verbatim (never re-derived).
|
|
8795
8804
|
attachFramework(ctx);
|
|
8805
|
+
// A pass opens before the delivery seam: until this pass's seam folds the record, a save writes the
|
|
8806
|
+
// model's record as it is (record-fold.mjs).
|
|
8807
|
+
openPass(ctx.paths.runDir);
|
|
8796
8808
|
|
|
8797
8809
|
// ── (t1cd): the digest-trigger FUNNEL ──────────────────────────────────────────────────────
|
|
8798
8810
|
// The digest-trigger funnel (; flag deleted post-E2E 2026-07-22): the queue
|
|
@@ -8935,7 +8947,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
8935
8947
|
// frame validator compares against THIS file, never against the frame's own paraphrase) —
|
|
8936
8948
|
// paraphrase drift between the request and the frame is a defect, not a style choice.
|
|
8937
8949
|
try {
|
|
8938
|
-
writeFileSync(P.instructedScope, JSON.stringify(
|
|
8950
|
+
const scope = instructedScopeToWrite(job, { jobRebuiltFromStatus: opts.jobRebuiltFromStatus, scopeOnDisk: existsSync(P.instructedScope) }); if (scope) writeFileSync(P.instructedScope, JSON.stringify(scope, null, 2) + "\n");
|
|
8939
8951
|
} catch (e) { note(`instructed-scope write failed (non-fatal): ${e.message}`); }
|
|
8940
8952
|
// THE STORED DEFAULTS THE ENGINE CANNOT SEARCH — recorded by the run, not only by the plan preview.
|
|
8941
8953
|
//
|
|
@@ -9217,7 +9229,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9217
9229
|
: stage(g.name, { ...ctx, axis: g.axis })),
|
|
9218
9230
|
stage("blind-frame", ctx).catch((e) => ({ ok: false, fail: String(e?.message ?? e) })),
|
|
9219
9231
|
]);
|
|
9220
|
-
if (!blindRes.ok) {
|
|
9232
|
+
if (!clSplit && clMember()?.ok) recordStatusesFromFindings(P.commonLaw); if (!blindRes.ok) {
|
|
9221
9233
|
note(`blind-frame failed (non-fatal — no frame-diff/reopen this run): ${blindRes.fail}`);
|
|
9222
9234
|
runLog(run.runDir, { event: "blind-frame-skipped", reason: blindRes.fail });
|
|
9223
9235
|
}
|
|
@@ -9345,7 +9357,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9345
9357
|
// synthesis, replay) reads exactly the artifacts a single-member run would have written. Re-run after
|
|
9346
9358
|
// every routed half followup (closure / frame-reopen) — the half files stay the source of truth and
|
|
9347
9359
|
// the canonical pair is always derived, which also makes the merge resume-idempotent.
|
|
9348
|
-
const mergeCommonLawArtifacts = !clSplit ? null : ({ discloseUnfinished = false } = {}) => {
|
|
9360
|
+
const mergeCommonLawArtifacts = !clSplit ? null : ({ discloseUnfinished = false } = {}) => { for (const h of GRID_HALVES) recordStatusesFromFindings(P.commonLawHalf(h), { half: h }); // each half's ledger statuses, recorded by code where the seat made no call (ruling 2026-10-01)
|
|
9349
9361
|
// Each half's coverage truth = its plugin-written main ledger FOLDED with any plugin-written
|
|
9350
9362
|
// supplementary sibling(s) the closure lane produced. Returns the batches as an ARRAY (mergeGrids'
|
|
9351
9363
|
// batchesOf flattens it, cells union across all of them); a missing/corrupt main leaves its cells to
|
|
@@ -9424,7 +9436,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9424
9436
|
//
|
|
9425
9437
|
// The prior channel is the TRUSTED one and already carries everything needed. Half forms are
|
|
9426
9438
|
// driver-written, `seatFields(p, row)` carries their rulings by the same `pOk` path, and
|
|
9427
|
-
// gateway.mjs
|
|
9439
|
+
// syncDispositionForm() in gateway.mjs already unions this exact shape — prior-only, `{ rows: null }` submitted — on
|
|
9428
9440
|
// every attempt. So this is that mode, not a new one.
|
|
9429
9441
|
//
|
|
9430
9442
|
// ONE METRIC MOVES, deliberately: with no submission, `carried` counts every ruled row, because
|
|
@@ -12644,9 +12656,9 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12644
12656
|
// always did, which is today's behaviour exactly.
|
|
12645
12657
|
try {
|
|
12646
12658
|
const rows = parseCorrections(readFileSync(P.seniorEyeReview, "utf8"));
|
|
12647
|
-
const preDoc = preCorrective ? parseFindingsJsonLenient(preCorrective.raw) : null;
|
|
12659
|
+
const preDoc = preCorrective ? parseFindingsJsonLenient(preCorrective.model?.raw ?? preCorrective.raw) : null; // the flags name the model's ordinals, so both sides are the record the model wrote (record-fold.mjs)
|
|
12648
12660
|
let postDoc = null;
|
|
12649
|
-
try { postDoc = parseFindingsJsonLenient(readFileSync(P.findings, "utf8")); } catch { /* shape defects ride the normal ladder */ }
|
|
12661
|
+
try { postDoc = parseFindingsJsonLenient(readFileSync(modelRecordPath(run.runDir, P.findings), "utf8")); } catch { /* shape defects ride the normal ladder */ }
|
|
12650
12662
|
correctionsApplied = buildCorrectionsApplied(rows, preDoc, postDoc);
|
|
12651
12663
|
// — A CLAIM MAY NOT OUTLIVE ITS EVIDENCE, AND A DEMOTED STAMP MAY NOT COME BACK.
|
|
12652
12664
|
//
|
|
@@ -13473,6 +13485,9 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13473
13485
|
// structured-only). Each stage is file-gated/resumable; per-card sessions feed the lint repair below.
|
|
13474
13486
|
// C2 — fold same-owner+same-mark duplicate filings into one finding BEFORE the overview + cards read
|
|
13475
13487
|
// findings.json, so the whole delivery phase (and the published copy) sees the single consolidated set.
|
|
13488
|
+
// THE SEAM'S WRITES ARE ONE CLOSURE, so they can run again: a synthesis save after this point rewrites
|
|
13489
|
+
// findings.json from the seat's own call, and every write below went with it (`stage()` re-runs them).
|
|
13490
|
+
const seamWrites = async () => {
|
|
13476
13491
|
injectDeferralCoverage(P, run.runDir, note); injectMeaningGapCoverage(P, run.runDir, note); // A3: unclosed reopen directives, and meaning searches that did not complete, become reader-visible coverage rows first
|
|
13477
13492
|
// qw/cn-scope-honesty — the sibling injection: a CN-family-scope run whose zh lane did not run
|
|
13478
13493
|
// discloses what the native-language investigation would have searched, and where it is offered
|
|
@@ -13528,6 +13543,15 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13528
13543
|
try { writeVerdictSidecar(); }
|
|
13529
13544
|
catch (e2) { throw new StageFailure("verdict", `verdict sidecar refresh failed after consolidation: ${String(e2.message).slice(0, 120)}`); }
|
|
13530
13545
|
}
|
|
13546
|
+
};
|
|
13547
|
+
await seamWrites();
|
|
13548
|
+
// Every write above is idempotent: each injection skips a row the record already carries, the stamp
|
|
13549
|
+
// replaces its rows, and the fold leaves a folded record as it is.
|
|
13550
|
+
ctx.afterSynthesisSave = async () => {
|
|
13551
|
+
note(`[seam] a synthesis save after the seam rewrote findings.json — re-applying the seam's writes`);
|
|
13552
|
+
runLog(run.runDir, { event: "seam-reapplied", after: "synthesis-save" });
|
|
13553
|
+
await seamWrites();
|
|
13554
|
+
};
|
|
13531
13555
|
// wp50/wi2 — the overview PROSE must speak the derived tier (VENZY: caption said "High risk" while
|
|
13532
13556
|
// every code surface said VERY HIGH). Thread the just-refreshed sidecar into the stage prompt; the
|
|
13533
13557
|
// deterministic backstop is predelivery-lint's overallTierChecks.
|
|
@@ -14390,6 +14414,13 @@ async function pipelineInner(job, opts = {}) {
|
|
|
14390
14414
|
lintActions = soft.actions ?? null; lintAskAnswers = soft.askAnswers ?? null; lintFourAnswers = soft.fourAnswers ?? null;
|
|
14391
14415
|
} } catch { /* validators.findings owns the unparseable case */ }
|
|
14392
14416
|
}
|
|
14417
|
+
// The narrative's finding headings are in the model's numbering: once the record is folded, its
|
|
14418
|
+
// write-ups join the record the model wrote (record-fold.mjs).
|
|
14419
|
+
let lintNarrativeFindings = null;
|
|
14420
|
+
const lintModelAt = modelRecordPath(run.runDir, P.findings);
|
|
14421
|
+
if (lintModelAt !== P.findings) {
|
|
14422
|
+
try { lintNarrativeFindings = parseFindingsJsonLenient(readFileSync(lintModelAt, "utf8")).findings; } catch { lintNarrativeFindings = null; }
|
|
14423
|
+
}
|
|
14393
14424
|
// wp50/wi2 — the sidecar tier joins the lint so overall-level prose can't contradict it.
|
|
14394
14425
|
let lintVerdict = null;
|
|
14395
14426
|
try { lintVerdict = JSON.parse(readFileSync(driverDir(run.runDir, "verdict.json"), "utf8")); } catch { /* legacy */ }
|
|
@@ -14413,7 +14444,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
14413
14444
|
// is presence-gated and simply emits nothing here; they stay alive for replay-archive.mjs,
|
|
14414
14445
|
// which passes the archived summary.
|
|
14415
14446
|
reportMd, narrativeMd, auditMd, recordsByUri: assembled.records, searchedNames,
|
|
14416
|
-
fetchFailures: recordFetchFailures, findings: lintFindings, findingsRaw: lintFindingsRaw, actionsRegister: lintActions, verdictDoc: lintVerdict,
|
|
14447
|
+
fetchFailures: recordFetchFailures, findings: lintFindings, narrativeFindings: lintNarrativeFindings, findingsRaw: lintFindingsRaw, actionsRegister: lintActions, verdictDoc: lintVerdict,
|
|
14417
14448
|
// WP-56 B2 — fresh v4 runs expect the standing mark-assessment (structural banner flag when
|
|
14418
14449
|
// absent). The replay harness's runLint call never passes this, so archived runs never flip.
|
|
14419
14450
|
markAssessment: lintMarkAssessment, markAssessmentExpected: lintSv >= 4,
|
|
@@ -16002,6 +16033,20 @@ export function reconstructCtx(job, opts) { // @internal
|
|
|
16002
16033
|
return ctx;
|
|
16003
16034
|
}
|
|
16004
16035
|
|
|
16036
|
+
/**
|
|
16037
|
+
* The aim count the cold pass sets before synthesis (`ctx.jxAim`), set on a reconstructed context from the
|
|
16038
|
+
* same function and behind the same gate. It lives here rather than in reconstructCtx because the lane
|
|
16039
|
+
* module is only ever imported lazily and reconstructCtx is synchronous. Without it a synthesis arm on a run
|
|
16040
|
+
* whose pass carried the Chinese-evidence block dispatched without it. Returns the count it set, or null.
|
|
16041
|
+
*/
|
|
16042
|
+
export async function attachJxAim(ctx) { // @internal
|
|
16043
|
+
if (!ctx?.searchPolicy?.components?.jxLanes) return null;
|
|
16044
|
+
const { jxAimForSynthesis } = await import("./jx-units.mjs");
|
|
16045
|
+
const aim = jxAimForSynthesis(ctx.paths.runDir);
|
|
16046
|
+
if (aim) ctx.jxAim = aim.count;
|
|
16047
|
+
return aim ? aim.count : null;
|
|
16048
|
+
}
|
|
16049
|
+
|
|
16005
16050
|
// Copy a stage's about-to-be-overwritten output (+ its per-stage telemetry) into _history/<ts>-<reason>/ so a
|
|
16006
16051
|
// prior result is NEVER lost and stays comparable. Returns the snapshot dir (or null if there was no output).
|
|
16007
16052
|
//
|
|
@@ -16055,6 +16100,7 @@ export async function repairStale(job, opts = {}) { // @internal
|
|
|
16055
16100
|
const labels = Array.isArray(rec?.labels) ? rec.labels : [];
|
|
16056
16101
|
if (!labels.length) return null;
|
|
16057
16102
|
attachFramework(ctx, { write: true });
|
|
16103
|
+
await attachJxAim(ctx); // the aim count the cold pass sets, as --experiment sets it on its rebuilt context
|
|
16058
16104
|
// RE-DERIVE THE ORDER rather than trusting the file's. The recorded order was right when it was
|
|
16059
16105
|
// written, and a code change since could have moved an edge — the file names WHAT, the live graph says
|
|
16060
16106
|
// WHEN. (the file names WHAT, the live graph says WHEN)
|
|
@@ -16198,6 +16244,7 @@ async function runExperimentInner(job, opts) {
|
|
|
16198
16244
|
if (opts.dispatchTrigger != null && !DISPATCH_TRIGGERS.includes(opts.dispatchTrigger))
|
|
16199
16245
|
throw new Error(`--dispatch-trigger: unknown value "${opts.dispatchTrigger}" — it decides which production pass the arm reproduces, so a typo would compose a quietly different prompt. One of: ${DISPATCH_TRIGGERS.join(", ")}`);
|
|
16200
16246
|
const ctx = reconstructCtx(job, opts);
|
|
16247
|
+
await attachJxAim(ctx); // read off the canonical run, as the cold pass reads it; the file itself is a sandbox edge
|
|
16201
16248
|
const model = opts.model ?? (axis ? axisTier(axis).model : STAGES[name].model);
|
|
16202
16249
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
16203
16250
|
const tag = [name, axis, opts.label ? kebab(opts.label) : null].filter(Boolean).join("-");
|
|
@@ -16729,7 +16776,7 @@ if (isEntrypoint(import.meta.url)) void (async () => {
|
|
|
16729
16776
|
// rate-limit catch already finds the run to postpone; the same hook lets a THROWN exit name the run.
|
|
16730
16777
|
const opts = a.experiment
|
|
16731
16778
|
? { ...base, codename: a.codename, experiment: a.experiment, model: a.model, instructions: a.instructions, axis: a.axis, label: a.label, dispatchTrigger: a.dispatchTrigger }
|
|
16732
|
-
: { ...base, codename: a.codename, fromStage: a.fromStage };
|
|
16779
|
+
: { ...base, codename: a.codename, fromStage: a.fromStage, ...(a.job ? {} : { jobRebuiltFromStatus: true }) };
|
|
16733
16780
|
const script = resolve(fileURLToPath(import.meta.url));
|
|
16734
16781
|
// `jobPath` is null on a rebuilt resume, so the advice composes a command that does not name a file
|
|
16735
16782
|
// the box does not have — which is the whole defect this path exists to close, and printing it again
|
|
@@ -16909,3 +16956,40 @@ export function injectMeaningGapCoverage(P, runDir, note) { // @internal — e
|
|
|
16909
16956
|
note(`[common-law] meaning-gap coverage skipped: ${String(e?.message || e).replace(/\s+/g, " ").slice(0, 100)}`);
|
|
16910
16957
|
}
|
|
16911
16958
|
}
|
|
16959
|
+
|
|
16960
|
+
/**
|
|
16961
|
+
* A SYNTHESIS SAVE AFTER THE SEAM (the pre-delivery narrative redo, the in-pass stale repair) rewrites
|
|
16962
|
+
* findings.json from the seat's own call, and the seam's writes go with it: the coverage-judgment rows, the
|
|
16963
|
+
* injected coverage rows, the fold. The seam leaves them on the run's context, and they run again here,
|
|
16964
|
+
* before anything downstream reads the record. Before the seam there is nothing on the context, so nothing
|
|
16965
|
+
* runs. It sits at the end of this file so that adding it moved no line another file cites.
|
|
16966
|
+
*/
|
|
16967
|
+
async function afterLateSynthesisSave(name, ctx, r) {
|
|
16968
|
+
if (r?.ok && !r.skipped && name === "synthesis" && typeof ctx?.afterSynthesisSave === "function") await ctx.afterSynthesisSave();
|
|
16969
|
+
}
|
|
16970
|
+
|
|
16971
|
+
/**
|
|
16972
|
+
* The reader's name for a planned search that never ran and that no open ledger row on its axis claims —
|
|
16973
|
+
* the orphan row of `coverageJudgmentRows`. Its area is the plan's qid, which a lawyer cannot read; the
|
|
16974
|
+
* label is composed the way the workbook's released-family rows compose theirs (publish/index.mjs
|
|
16975
|
+
* releasedFamilyRows): the plan entry's unit in the coverage form's grammar, its axis head in the reader's
|
|
16976
|
+
* words. `{}` when the frozen plan does not carry the entry, so the row keeps the identifier and nothing is
|
|
16977
|
+
* invented in its place. PURE.
|
|
16978
|
+
*/
|
|
16979
|
+
function orphanAreaLabel(qid, axis, plan) {
|
|
16980
|
+
const entry = (Array.isArray(plan?.entries) ? plan.entries : []).find((e) => e?.qid === qid);
|
|
16981
|
+
if (!entry) return {};
|
|
16982
|
+
const label = coverageUnitLabel(unitLabel(axis || entry.axis, entry));
|
|
16983
|
+
return label && label !== qid ? { areaLabel: label } : {};
|
|
16984
|
+
}
|
|
16985
|
+
|
|
16986
|
+
/**
|
|
16987
|
+
* The instructed scope this run writes, or null to keep the one already on disk. A resume that rebuilt its
|
|
16988
|
+
* job from status.json holds only the job's identity (id, reference, mark, classes, forwarder), so writing
|
|
16989
|
+
* the scope from it would replace the order's goods, customer and territories with nothing, and every
|
|
16990
|
+
* later reader of the scope would see a narrower matter than was ordered. Such a resume keeps the scope the
|
|
16991
|
+
* run started with. A job read from a file, or a run with no scope yet, writes as before. PURE.
|
|
16992
|
+
*/
|
|
16993
|
+
export function instructedScopeToWrite(job, { jobRebuiltFromStatus = false, scopeOnDisk = false } = {}) { // @internal
|
|
16994
|
+
return jobRebuiltFromStatus && scopeOnDisk ? null : instructedScopeOf(job);
|
|
16995
|
+
}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// ── the shape ────────────────────────────────────────────────────────────────────────────────────────
|
|
19
19
|
//
|
|
20
20
|
// { "schema": 1,
|
|
21
|
-
// "families": { "
|
|
22
|
-
// "of": { "<runId>": "
|
|
21
|
+
// "families": { "aqua-range": { "name": "Aqua range", "account": "foxglade" } },
|
|
22
|
+
// "of": { "<runId>": "aqua-range" } }
|
|
23
23
|
//
|
|
24
24
|
// Keyed by RUN rather than by mark. A mark is not a thing the pool stores — it is a grouping the browser
|
|
25
25
|
// derives from run metadata — so a run id is the only stable handle the two sides already agree on. The
|
|
@@ -39,7 +39,7 @@ const FILE = "family-tags.json";
|
|
|
39
39
|
// Caught by a test asserting a malformed file degrades to no families; it degraded to someone else's.
|
|
40
40
|
const empty = () => ({ schema: 1, families: {}, of: {} });
|
|
41
41
|
|
|
42
|
-
/** Ids are derived from the name so that grouping into "
|
|
42
|
+
/** Ids are derived from the name so that grouping into "Aqua range" twice MERGES rather than duplicating. */
|
|
43
43
|
export function familyId(name) {
|
|
44
44
|
return String(name ?? "")
|
|
45
45
|
.normalize("NFKD")
|
|
@@ -131,7 +131,7 @@ const INSTALL_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
131
131
|
const READ_OFF_NOTE = "Reading a brief is not available on this instance — set the search up below.";
|
|
132
132
|
import { basename, dirname, join, resolve as pathResolve } from "node:path"; import { studioDirFor } from "../shared/pre-rename-spellings.mjs";
|
|
133
133
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
134
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
134
|
+
import { createHmac, timingSafeEqual, randomBytes } from "node:crypto";
|
|
135
135
|
import { makePrincipal, assertPrincipal, genericOrgOf, mayReadRun, reachCovers, principalView, seesEverything, mayRun,
|
|
136
136
|
PortalDeny } from "./portal-access.mjs";
|
|
137
137
|
// — the THIRD identity source (after the CF Access edge and, until, the deleted bypass). It
|
|
@@ -220,7 +220,7 @@ export function jobHashOf(job) {
|
|
|
220
220
|
})).digest("base64url");
|
|
221
221
|
}
|
|
222
222
|
export function mintConfirmation({ secret, account, email, jobHash, now = Date.now(), ttlMs = 10 * 60 * 1000 }) {
|
|
223
|
-
const jti = createHmac("sha256", secret).update(`${account}|${jobHash}|${now}|${
|
|
223
|
+
const jti = createHmac("sha256", secret).update(`${account}|${jobHash}|${now}|${randomBytes(16).toString("hex")}`).digest("base64url").slice(0, 24);
|
|
224
224
|
const payload = { account, email, jobHash, jti, exp: now + ttlMs };
|
|
225
225
|
const body = Buffer.from(canonical(payload)).toString("base64url");
|
|
226
226
|
const sig = createHmac("sha256", secret).update(body).digest("base64url");
|
|
@@ -1395,7 +1395,7 @@ export function makePortalService({
|
|
|
1395
1395
|
//
|
|
1396
1396
|
// DISPLAY NAMES, computed server-side, because five vocabularies sit between a name on the form
|
|
1397
1397
|
// and an office code (display → normalizeTerritory → canonicalJurisdictionCode → offices.translate
|
|
1398
|
-
// → covered.has) and register-plan.mjs
|
|
1398
|
+
// → covered.has) and resolveRegions() in register-plan.mjs records what re-implementing that chain cost.
|
|
1399
1399
|
//
|
|
1400
1400
|
// THE FIELD IS OMITTED, not nulled, when the snapshot does not say. `null` is a real answer here
|
|
1401
1401
|
// ("no declared restriction") and the browser must be able to tell it from "this deployment has
|
|
@@ -3147,7 +3147,7 @@ async function connectorDoorKind(url) {
|
|
|
3147
3147
|
//
|
|
3148
3148
|
// STAFF ONLY, and by construction rather than by filtering: it hangs off /portal/admin, which
|
|
3149
3149
|
// asserted staffOnly above and 404s for everyone else. That is a decision, not an oversight. A
|
|
3150
|
-
// family name is staff shorthand for a brand line — "
|
|
3150
|
+
// family name is staff shorthand for a brand line — "Aqua range" can name a launch a client has
|
|
3151
3151
|
// not announced — and a grouping a client can see but not edit or explain raises more questions
|
|
3152
3152
|
// than it answers. Revisit when clients have a reason to care; until then the client's list is
|
|
3153
3153
|
// exactly the mark list, which is what the browser falls back to when this 404s.
|
|
@@ -4620,7 +4620,8 @@ const PORT = PORT_CHOICE.port;
|
|
|
4620
4620
|
// stays fail-closed — an empty array would be truthy and open it.
|
|
4621
4621
|
const AUD = accessAudience(envFrom(process.env, "CLEAROTRON_OIDC_AUDIENCE"));
|
|
4622
4622
|
// item 1 — THE SAME FOUR VALUES THE STAFF MCP FACE ALREADY READS, under portal-side names
|
|
4623
|
-
// (
|
|
4623
|
+
// (OIDC_ISSUER declared in http-server.mjs, and the three read beside it). `makeAccessVerifier` has
|
|
4624
|
+
// always accepted them; the portal simply
|
|
4624
4625
|
// never passed them, which is how one product shipped a provider-agnostic API face and a
|
|
4625
4626
|
// Cloudflare-only web portal. Unset ⇒ the CF Access shapes derived from CF_ACCESS_TEAM, exactly as
|
|
4626
4627
|
// before.
|
|
@@ -1205,29 +1205,25 @@ export function narrativeWriteUpChecks({ narrativeMd, findings, depth, manifest,
|
|
|
1205
1205
|
maxWords: depth?.narrativeWriteUpWords ?? null });
|
|
1206
1206
|
// AN UNGRADED PRODUCT EXAMINES NOTHING, AND THAT IS THE ONLY SILENCE THIS CHECK MAY KEEP.
|
|
1207
1207
|
if (!r.graded) return [];
|
|
1208
|
-
// — A GRADED NARRATIVE
|
|
1208
|
+
// — A GRADED NARRATIVE WITH NO WRITE-UP BLOCK GETS A ROW, NEVER SILENCE. The old line here was
|
|
1209
1209
|
// `if (!r.total && !r.violations.length) return []`, which returned the SAME empty array for product
|
|
1210
|
-
// 4's correct inertness and for a graded run whose narrative carries no
|
|
1211
|
-
// 6 of 22 graded runs on the box are the second kind, and every one of them read as compliant.
|
|
1210
|
+
// 4's correct inertness and for a graded run whose narrative carries no write-up block at all.
|
|
1212
1211
|
//
|
|
1213
|
-
//
|
|
1214
|
-
//
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1212
|
+
// AND THE ROW SAYS THE RULE DOES NOT APPLY, because it does not (the owner's ruling of 2026-10-01). The
|
|
1213
|
+
// depth rule governs per-finding write-ups: which findings get one, and how long each may be. A
|
|
1214
|
+
// sectional narrative — the spine, the verdict, coverage, calibration — carries none, measured on 36
|
|
1215
|
+
// of 43 saved clearance narratives, so the rule has nothing to govern there. This row used to fail
|
|
1216
|
+
// every such clearance for a heading the directive never asks for, which put an INVESTIGATE line on
|
|
1217
|
+
// nearly every delivery that nobody could act on. Whether the directive should ask for per-finding
|
|
1218
|
+
// write-ups at all is a separate question, held for the synthesis redesign.
|
|
1218
1219
|
if (!r.total && !r.violations.length) {
|
|
1219
1220
|
if (!r.findingsTotal)
|
|
1220
1221
|
return [check("narrative-write-ups", "narrative-depth", surface, true,
|
|
1221
1222
|
"no findings on this run, so there is nothing to write up")];
|
|
1222
|
-
return [
|
|
1223
|
-
`this run has ${r.findingsTotal} finding(s)
|
|
1224
|
-
+ "
|
|
1225
|
-
+ "
|
|
1226
|
-
+ "states which findings get a prose write-up and how long it may be, and never asks for the "
|
|
1227
|
-
+ "heading this check keys on. It means the rule is unenforced on this run."),
|
|
1228
|
-
// The flag rides the FAILURE, as everywhere else here, and it is what stops a warm redo handing
|
|
1229
|
-
// the seat a correction no directive lets it satisfy.
|
|
1230
|
-
structural: true }];
|
|
1223
|
+
return [check("narrative-write-ups", "narrative-depth", surface, true,
|
|
1224
|
+
`the depth rule does not apply to a sectional narrative: this run has ${r.findingsTotal} finding(s) `
|
|
1225
|
+
+ "and the narrative carries no per-finding write-up block, so the band-rank cut and the word cap "
|
|
1226
|
+
+ "have nothing to govern")];
|
|
1231
1227
|
}
|
|
1232
1228
|
const coverage = r.unjoined
|
|
1233
1229
|
? ` (${r.examined} of ${r.total} write-ups joined to a finding; ${r.unjoined} could not be, and were not judged for membership)`
|
|
@@ -2200,7 +2196,7 @@ export function cardBudgetChecks({ cardFolds }) {
|
|
|
2200
2196
|
`assembly folded ${folds.length} surface(s) to the level budgets (moved, never deleted): ${folds.map((f) => `${f.surface} → +${f.movedSentences} sentence(s)/${f.movedWords} word(s) into depth`).join("; ")}`)];
|
|
2201
2197
|
}
|
|
2202
2198
|
|
|
2203
|
-
export function runLint({ depth, commonLawGrid, matterContext, clientPartyName, reportMd, clientSummaryMd, narrativeMd, auditMd, recordsByUri, searchedNames, headerName, ratedNames, actionsText, fetchFailures, extraPlatformNames, findings, findingsRaw, actionsRegister, actionsExpected, intakeAsks, askAnswers, cardFolds, verdictDoc, manifest, seniorRights, seniorRightsExpected, markAssessment, markAssessmentExpected, fourAnswers, contentModelExpected, findingsSchemaVersion, placements, ownerScreen, recordCarry, commonLawCarries, searchedJurisdictions = null }) {
|
|
2199
|
+
export function runLint({ depth, commonLawGrid, matterContext, clientPartyName, reportMd, clientSummaryMd, narrativeMd, auditMd, recordsByUri, searchedNames, headerName, ratedNames, actionsText, fetchFailures, extraPlatformNames, findings, narrativeFindings = null, findingsRaw, actionsRegister, actionsExpected, intakeAsks, askAnswers, cardFolds, verdictDoc, manifest, seniorRights, seniorRightsExpected, markAssessment, markAssessmentExpected, fourAnswers, contentModelExpected, findingsSchemaVersion, placements, ownerScreen, recordCarry, commonLawCarries, searchedJurisdictions = null }) {
|
|
2204
2200
|
// WS-B: the run's profile platforms join the vocabulary for this run. Profiles carry store
|
|
2205
2201
|
// DOMAINS by contract, so derive the name tokens a report would actually print: the raw norm
|
|
2206
2202
|
// ("thomasnetcom"), the separator-spaced phrase ("thomasnet com" / "made in china com"), and the
|
|
@@ -2290,7 +2286,9 @@ export function runLint({ depth, commonLawGrid, matterContext, clientPartyName,
|
|
|
2290
2286
|
if (clientSummaryMd) checks.push(...permissionProseChecks({ text: clientSummaryMd, surface: "client-summary", idSuffix: ":client" }));
|
|
2291
2287
|
if (narrativeMd) checks.push(...permissionProseChecks({ text: narrativeMd, surface: "narrative", idSuffix: ":narrative", structural: true }));
|
|
2292
2288
|
// — the narrative's own depth rules, on the narrative surface so they route to the synthesis redo.
|
|
2293
|
-
|
|
2289
|
+
// Its finding headings are in the model's numbering, so they join the record the model wrote when the
|
|
2290
|
+
// delivered record is folded (`narrativeFindings`), and the delivered record otherwise.
|
|
2291
|
+
if (narrativeMd) checks.push(...narrativeWriteUpChecks({ narrativeMd, findings: narrativeFindings ?? findings, depth, manifest }));
|
|
2294
2292
|
// — the same rule on both surfaces the client's words come from. The narrative is where the
|
|
2295
2293
|
// shipped defect was authored; the report is what a client reads, and a repair to one that left the
|
|
2296
2294
|
// other standing is how this family's previous cures kept being re-applied by hand.
|
|
@@ -2669,6 +2667,8 @@ const DELIVERY_LINES = {
|
|
|
2669
2667
|
// The sentence states the CONSEQUENCE, in the receipt's own terms: not "a check failed" but "the rule
|
|
2670
2668
|
// was applied to nothing". Eleven graded runs delivered carrying this, two of them orderable product
|
|
2671
2669
|
// demos, and the only place it was written was one row of a fifty-odd-row receipt nobody routes.
|
|
2670
|
+
// No new run emits it now (a sectional narrative is a passing row that says the rule does not apply);
|
|
2671
|
+
// the line stays so a republished receipt from before reads as it did.
|
|
2672
2672
|
"narrative-write-ups:could-not-read":
|
|
2673
2673
|
"The depth rules for the written-up findings were applied to nothing on this run — the narrative "
|
|
2674
2674
|
+ "carries no block this check can read, so neither the ranking cut nor the length cap was verified.",
|
package/driver/products.mjs
CHANGED
|
@@ -344,7 +344,7 @@ export const CASE_LAW_NOT_A_REQUEST = Object.freeze({
|
|
|
344
344
|
/**
|
|
345
345
|
* `searchLevel` — THE SELECTOR ITSELF, AND THE ONE FIELD DELETED WITHOUT REFUSING.
|
|
346
346
|
*
|
|
347
|
-
* `search-policy.mjs
|
|
347
|
+
* `search-policy.mjs` says the wire field is "DELETED — not deprecated, not hidden behind the product
|
|
348
348
|
* name." The code made it deprecated: nothing on any door read it, nothing refused it, and there is no
|
|
349
349
|
* generic unknown-field rejection — so a request naming a depth was accepted, the field dropped, and the
|
|
350
350
|
* run went out at whatever product the SCOPE happened to imply.
|
package/driver/profiles.mjs
CHANGED
|
@@ -1107,9 +1107,10 @@ export function resolveEffectiveProfile(job, { profiles = loadProfiles(), projec
|
|
|
1107
1107
|
//
|
|
1108
1108
|
// `resolveEffectiveScope` decides whether to tell an approver "this project" or "the account's default
|
|
1109
1109
|
// classes" by reading `profile.origins`. It was returned HERE as a sibling and no caller bridged the
|
|
1110
|
-
// two — checked on origin/main: run-quote.mjs
|
|
1111
|
-
// all pass the profile alone. So both
|
|
1112
|
-
// FROM.project branch was dead code: a
|
|
1110
|
+
// two — checked on origin/main: quoteForJob() in run-quote.mjs, resolveRequest() in resolve-request.mjs
|
|
1111
|
+
// (twice) and checkClearanceScopeRules() in scope-rules.mjs all pass the profile alone. So both
|
|
1112
|
+
// `=== "project"` tests were permanently false and the whole FROM.project branch was dead code: a
|
|
1113
|
+
// project that REPLACES its customer's classes ran on the
|
|
1113
1114
|
// project's list and told the approver those were the account's defaults. The number was right; the
|
|
1114
1115
|
// reason given for it was wrong, and it pointed at the wrong place to go and change it.
|
|
1115
1116
|
//
|
package/driver/publish/index.mjs
CHANGED
|
@@ -598,7 +598,7 @@ export async function regenSurfaces(poolRoot) {}
|
|
|
598
598
|
* rather than discovered later.
|
|
599
599
|
*
|
|
600
600
|
* WHAT THIS DOES NOT REACH, so the next reader does not assume it does:
|
|
601
|
-
* · the run's PROSE. `- Source: [EUIPO ·
|
|
601
|
+
* · the run's PROSE. `- Source: [EUIPO · 099999992](https://euipo.europa.eu/…)` is markdown the synthesis
|
|
602
602
|
* wrote and renderProse links it like any other markdown. No field distinguishes a prose register
|
|
603
603
|
* citation from a prose evidence citation, so the only way to catch it is to pattern-match the URL —
|
|
604
604
|
* which is the move that produced this defect class in the first place.
|