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.
Files changed (132) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/bin/README.md +1 -1
  3. package/bin/clearotron.mjs +2 -0
  4. package/bin/mcp.mjs +80 -0
  5. package/bin/start.mjs +14 -1
  6. package/build-info.json +2 -2
  7. package/demo/README.md +1 -1
  8. package/docs/SECURITY-OWASP.md +21 -13
  9. package/docs/SECURITY.md +20 -14
  10. package/docs/architecture/04-configuration-reference.md +3 -3
  11. package/docs/architecture/05-config-governance.md +1 -1
  12. package/docs/architecture/06-operations-runbook.md +5 -4
  13. package/docs/architecture/08-development-guide.md +3 -1
  14. package/docs/architecture/09-security-and-data.md +16 -11
  15. package/driver/CHANGELOG.md +29 -0
  16. package/driver/binding-layers.mjs +28 -6
  17. package/driver/citation-census.json +3 -3
  18. package/driver/common-law-coverage-status.mjs +52 -0
  19. package/driver/common-law-receipts.mjs +1 -1
  20. package/driver/compare.mjs +1 -1
  21. package/driver/connotation-search.mjs +5 -5
  22. package/driver/contract-audit.mjs +6 -6
  23. package/driver/contract-dictation-registry.mjs +1 -1
  24. package/driver/contract-e3-backlog.mjs +18 -17
  25. package/driver/contract-vocabulary.mjs +145 -132
  26. package/driver/declination-call.mjs +5 -1
  27. package/driver/degraded-parts.mjs +10 -3
  28. package/driver/dev-portal.mjs +2 -1
  29. package/driver/dispatch-record.mjs +19 -0
  30. package/driver/doubt-closure-tool.mjs +2 -2
  31. package/driver/driver.config.mjs +39 -6
  32. package/driver/engagement-receipt.mjs +1 -1
  33. package/driver/engine/CONTRACT.md +41 -1
  34. package/driver/engine/anthropic-agent.mjs +56 -4
  35. package/driver/engine/mcp/codex-config.mjs +8 -3
  36. package/driver/engine/mcp/coverage-server.mjs +1 -1
  37. package/driver/engine/mcp/gather-config.mjs +1 -1
  38. package/driver/engine/mcp/recording-server.mjs +32 -5
  39. package/driver/engine/mcp/stdio-server.mjs +45 -7
  40. package/driver/engine/mcp/supplemental.mjs +26 -1
  41. package/driver/engine/openai-agent.mjs +113 -20
  42. package/driver/engine/session-record.mjs +223 -0
  43. package/driver/enqueue-schema.mjs +2 -2
  44. package/driver/findings-model.mjs +4 -4
  45. package/driver/flag-snapshot.mjs +2 -1
  46. package/driver/form-neighbourhood.mjs +7 -7
  47. package/driver/frame-diff-model.mjs +3 -3
  48. package/driver/framework.mjs +22 -0
  49. package/driver/gateway.mjs +96 -14
  50. package/driver/jx.mjs +1 -1
  51. package/driver/knockout-review-record.mjs +55 -1
  52. package/driver/named-band.mjs +4 -0
  53. package/driver/owner-screen.mjs +7 -4
  54. package/driver/package.json +1 -1
  55. package/driver/phase0.mjs +13 -2
  56. package/driver/pipeline-knockout.mjs +15 -13
  57. package/driver/pipeline.mjs +132 -48
  58. package/driver/portal-families.mjs +3 -3
  59. package/driver/portal-service.mjs +6 -5
  60. package/driver/predelivery-lint.mjs +19 -19
  61. package/driver/products.mjs +1 -1
  62. package/driver/profiles.mjs +4 -3
  63. package/driver/publish/index.mjs +1 -1
  64. package/driver/publish/knockout.mjs +5 -4
  65. package/driver/publish/office-record-links.mjs +17 -2
  66. package/driver/publish/publish-inputs.mjs +3 -3
  67. package/driver/publish/render-knockout.mjs +17 -5
  68. package/driver/publish/render.mjs +4 -2
  69. package/driver/publish/xlsx.mjs +1 -1
  70. package/driver/record-fold.mjs +177 -0
  71. package/driver/register-count.mjs +20 -3
  72. package/driver/register-coverage.mjs +1 -1
  73. package/driver/register-plan.mjs +83 -18
  74. package/driver/register-records.mjs +7 -0
  75. package/driver/registry-fidelity.mjs +2 -2
  76. package/driver/repairs.mjs +1 -1
  77. package/driver/report-card-record.mjs +4 -4
  78. package/driver/result-noun-fields.mjs +3 -3
  79. package/driver/scope-facts.mjs +11 -3
  80. package/driver/scope-rules.mjs +2 -2
  81. package/driver/search-policy.mjs +20 -1
  82. package/driver/skills/clearance-common-law/SKILL.md +22 -22
  83. package/driver/skills/clearance-register/digest.md +3 -3
  84. package/driver/skills/clearance-register/register-recipes.md +11 -11
  85. package/driver/skills/clearance-register/status-rules.md +1 -1
  86. package/driver/skills/clearance-register/unit.md +1 -1
  87. package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -2
  88. package/driver/skills/clearance-search/phase2-execution.md +6 -5
  89. package/driver/skills/clearance-search/synthesis-rules.md +4 -4
  90. package/driver/skills/clearance-search/worked-examples.md +39 -39
  91. package/driver/skills/clearance-variants/SKILL.md +16 -16
  92. package/driver/skills/frame-diff/SKILL.md +2 -2
  93. package/driver/stage-context.mjs +36 -1
  94. package/driver/stages-knockout.mjs +6 -5
  95. package/driver/stages.mjs +30 -30
  96. package/driver/suite-census.json +259 -55
  97. package/driver/synthesis-record.mjs +5 -0
  98. package/driver/unit-inventory.mjs +17 -28
  99. package/driver/verify.mjs +7 -7
  100. package/mcp-server/CHANGELOG.md +8 -0
  101. package/mcp-server/CONNECT.md +9 -3
  102. package/mcp-server/demo-mode.mjs +10 -0
  103. package/mcp-server/lib/evidence.mjs +8 -8
  104. package/mcp-server/lib/knockout.mjs +1 -1
  105. package/mcp-server/lib/runs.mjs +7 -1
  106. package/mcp-server/lib/scrub.mjs +1 -1
  107. package/mcp-server/package.json +2 -2
  108. package/mcp-server/packs/README.md +1 -1
  109. package/mcp-server/server.mjs +16 -2
  110. package/package.json +4 -3
  111. package/portal-ui/package.json +3 -3
  112. package/providers/_shared/count.mjs +9 -2
  113. package/providers/_shared/enumerate.mjs +125 -41
  114. package/providers/_shared/execute-plan.mjs +15 -4
  115. package/providers/corsearch/src/capabilities.js +1 -1
  116. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  117. package/providers/oauth-mcp-bridge/package.json +2 -2
  118. package/providers/signa/src/capabilities.js +32 -2
  119. package/providers/signa/src/core.js +262 -36
  120. package/scripts/band-shape-probe.mjs +2 -2
  121. package/scripts/deprecate-below.mjs +28 -4
  122. package/scripts/e2e-first-time.mjs +17 -2
  123. package/scripts/env-audit.mjs +3 -2
  124. package/scripts/freeze-example-run.mjs +10 -10
  125. package/scripts/mcp-registry-listing.mjs +199 -0
  126. package/scripts/mint-plugin-folder.mjs +65 -0
  127. package/scripts/release-version.mjs +44 -0
  128. package/scripts/score.mjs +1 -1
  129. package/shared/register-floor.mjs +19 -0
  130. package/shared/scope.mjs +18 -2
  131. package/shared/trigger-lane.mjs +1 -1
  132. package/skills/clearotron-ops/SKILL.md +81 -24
@@ -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 PARADISE diacritics — but the fold-dedupe
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:1335 runs the same parser
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
- try { raw = readFileSync(P.findings, "utf8"); } catch { return { stale: false }; }
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
- // The qid rides VERBATIM wherever it lands. It is the identifier the plan, the receipt and every gate
7044
- // join on, and a shortened one is a row nobody can match back to the query that did not run.
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.qids.length ? `${o.note} (no band block for: ${o.qids.join(", ")})` : o.note }));
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 — the funnel produced no band block for this query" });
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:72-74), not
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:32-35): no SERP_LANES entry, so
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
- try {
7482
- if (!existsSync(P.findings)) return;
7483
- const doc = JSON.parse(readFileSync(P.findings, "utf8"));
7484
- if (!doc || !Array.isArray(doc.findings) || doc.findings.length < 2) return;
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
- const markNames = Array.isArray(job?.marks)
8499
- ? job.marks.map((m) => (typeof m === "string" ? m : m?.name)).filter(Boolean)
8500
- : (job?.markName ?? job?.name ?? null);
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(instructedScopeOf(job), null, 2) + "\n");
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:603 already unions this exact shape — prior-only, `{ rows: null }` submitted — on
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": { "hydra-range": { "name": "Hydra range", "account": "foxglade" } },
22
- // "of": { "<runId>": "hydra-range" } }
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 "Hydra range" twice MERGES rather than duplicating. */
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}|${Math.random()}`).digest("base64url").slice(0, 24);
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:206-216 records what re-implementing that chain cost.
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 — "Hydra range" can name a launch a client has
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
- // (mcp-server/http-server.mjs:92-95). `makeAccessVerifier` has always accepted them; the portal simply
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 THE PARSER CANNOT KEY IS NOT A COMPLIANT ONE. The old line here was
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 recognisable write-up block.
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
- // STRUCTURAL, so it reports without sending a seat to fix it. The depth directive tells the seat
1214
- // WHICH findings get a prose write-up and HOW LONG it may be. It does not ask for the `Finding N —
1215
- // <mark>` heading this parser keys on, so a narrative without one breaks no rule the seat was given,
1216
- // and a warm redo would hand it a correction it has no instruction to satisfy. What is wrong here is
1217
- // that the DEPTH RULES WENT UNVERIFIED — a coverage fact, stated as one.
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 [{ ...check("narrative-write-ups:could-not-read", "narrative-depth", surface, false,
1223
- `this run has ${r.findingsTotal} finding(s) and the narrative carries NO recognisable prose `
1224
- + "write-up block, so the depth rules were not verified on it — neither the band-rank cut nor the "
1225
- + "word cap was applied to anything. This is NOT a fault in the narrative: the depth directive "
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
- if (narrativeMd) checks.push(...narrativeWriteUpChecks({ narrativeMd, findings, depth, manifest }));
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.",
@@ -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:22` says the wire field is "DELETED — not deprecated, not hidden behind the product
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.
@@ -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:59, resolve-request.mjs:44,47 and scope-rules.mjs:107
1111
- // all pass the profile alone. So both `=== "project"` tests were permanently false and the whole
1112
- // FROM.project branch was dead code: a project that REPLACES its customer's classes ran on the
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
  //
@@ -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 · 018575624](https://euipo.europa.eu/…)` is markdown the synthesis
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.