clearotron 0.4.1-beta.0 → 0.4.1-beta.2

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 (79) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  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/architecture/04-configuration-reference.md +0 -1
  9. package/docs/architecture/06-operations-runbook.md +4 -3
  10. package/docs/architecture/08-development-guide.md +3 -1
  11. package/driver/CHANGELOG.md +17 -0
  12. package/driver/citation-census.json +3 -3
  13. package/driver/compare.mjs +1 -1
  14. package/driver/contract-audit.mjs +6 -6
  15. package/driver/contract-dictation-registry.mjs +1 -1
  16. package/driver/contract-e3-backlog.mjs +17 -16
  17. package/driver/contract-vocabulary.mjs +138 -125
  18. package/driver/declination-call.mjs +5 -1
  19. package/driver/dev-portal.mjs +2 -1
  20. package/driver/engine/mcp/codex-config.mjs +7 -2
  21. package/driver/engine/mcp/gather-config.mjs +4 -0
  22. package/driver/engine/mcp/recording-server.mjs +1 -1
  23. package/driver/engine/mcp/supplemental.mjs +26 -1
  24. package/driver/engine/openai-agent.mjs +13 -3
  25. package/driver/findings-model.mjs +80 -13
  26. package/driver/flag-snapshot.mjs +2 -1
  27. package/driver/foreign-record-links.mjs +50 -0
  28. package/driver/frame-diff-model.mjs +1 -1
  29. package/driver/gateway.mjs +4 -4
  30. package/driver/jx.mjs +1 -1
  31. package/driver/knockout-review-record.mjs +55 -1
  32. package/driver/named-band.mjs +2 -0
  33. package/driver/owner-screen.mjs +7 -4
  34. package/driver/package.json +1 -1
  35. package/driver/pipeline-knockout.mjs +14 -12
  36. package/driver/pipeline.mjs +118 -20
  37. package/driver/portal-service.mjs +5 -4
  38. package/driver/products.mjs +1 -1
  39. package/driver/profiles.mjs +4 -3
  40. package/driver/publish/publish-inputs.mjs +2 -2
  41. package/driver/publish/xlsx.mjs +1 -1
  42. package/driver/register-count.mjs +2 -2
  43. package/driver/register-coverage.mjs +1 -1
  44. package/driver/register-plan.mjs +13 -2
  45. package/driver/result-noun-fields.mjs +3 -3
  46. package/driver/scope-rules.mjs +2 -2
  47. package/driver/stage-context.mjs +9 -0
  48. package/driver/stages-knockout.mjs +6 -5
  49. package/driver/stages.mjs +23 -23
  50. package/driver/suite-census.json +97 -31
  51. package/driver/synthesis-record.mjs +14 -5
  52. package/driver/verify.mjs +1 -1
  53. package/mcp-server/CHANGELOG.md +8 -0
  54. package/mcp-server/CONNECT.md +9 -3
  55. package/mcp-server/demo-mode.mjs +10 -0
  56. package/mcp-server/lib/runs.mjs +7 -1
  57. package/mcp-server/lib/scrub.mjs +1 -1
  58. package/mcp-server/package.json +1 -1
  59. package/mcp-server/packs/README.md +1 -1
  60. package/mcp-server/server.mjs +16 -2
  61. package/package.json +3 -2
  62. package/portal-ui/package.json +3 -3
  63. package/providers/_shared/count.mjs +5 -1
  64. package/providers/_shared/enumerate.mjs +66 -21
  65. package/providers/_shared/execute-plan.mjs +13 -4
  66. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  67. package/providers/oauth-mcp-bridge/package.json +1 -1
  68. package/providers/signa/src/capabilities.js +15 -0
  69. package/providers/signa/src/core.js +55 -26
  70. package/scripts/e2e-first-time.mjs +3 -3
  71. package/scripts/env-audit.mjs +3 -2
  72. package/scripts/freeze-example-run.mjs +8 -8
  73. package/scripts/mcp-registry-listing.mjs +199 -0
  74. package/scripts/mint-plugin-folder.mjs +65 -0
  75. package/scripts/release-version.mjs +44 -0
  76. package/scripts/score.mjs +1 -1
  77. package/shared/register-floor.mjs +19 -0
  78. package/shared/trigger-lane.mjs +1 -1
  79. package/skills/clearotron-ops/SKILL.md +81 -24
@@ -45,7 +45,7 @@ import { parseCoverageLedgerJson, parseCoverageLedgerFull, deriveCoverageStatus,
45
45
  import { receiptSettled, readEnvelopeDecision, settleReceipt, settledDeferralsSection, readStickyGaps } from "./envelope-settle.mjs";
46
46
  import { readRegisterTaint, readActiveTaintAxes } from "./register-taint.mjs";
47
47
  import { parseNamedBand, mergeNamedBands, findCollapsedBands, quarantineUnknownStates, taintQuarantineCleanBlocks, bandRecords } from "./named-band.mjs";
48
- import { recordOriginsFor } from "./record-origins.mjs";
48
+ import { recordOriginsFor, activeRecordOrigins } from "./record-origins.mjs"; import { recordBlanking, FOREIGN_HOST_TOKEN } from "./foreign-record-links.mjs";
49
49
  import { REGISTER_PROVIDER } from "./driver.config.mjs";
50
50
  import { noteRegisterServed, registersServedFrom, providerUsageCaveat } from "./register-served.mjs"; // which registers actually served this run — noted eagerly from the dispatch's own resolver, and reconciled at publish against the ledger rows, which carry the vendor per call
51
51
  import { FACTS_FILE as DIGEST_FACTS_FILE, ACCOUNTING_STAMP as DIGEST_ACCOUNTING_STAMP, recordedFindingUris,
@@ -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
@@ -163,7 +163,7 @@ import { parsePlacementsJson } from "./placement-model.mjs"; // B2 — the str
163
163
  import { readAnchors } from "./anchor-reader.mjs";
164
164
  import { findEngagementReceipts } from "./engagement-receipt.mjs";
165
165
  import { plainRegisterFlags } from "./plain-register.mjs";
166
- import { parseFindingsJson, parseFindingsJsonLenient, consolidateFindings, deriveDisplayVerdict, bindRecommendation, compareBlockingPower, inDispositionMode, DISPOSITION_GROUP, deriveActionConditions, cardedParties, actionPartyReferences, quarantinedConditionRows, salvageRepairTargets, riskStatement, verdictStance, remapActionOrdinals, joinAskToAnswer, stripAskLabel, bandBorderlineDeclarations, reasonedNegativeGroups } from "./findings-model.mjs";
166
+ import { parseFindingsJson, parseFindingsJsonLenient, consolidateFindings, deriveDisplayVerdict, bindRecommendation, compareBlockingPower, inDispositionMode, DISPOSITION_GROUP, deriveActionConditions, cardedParties, actionPartyReferences, quarantinedConditionRows, salvageRepairTargets, riskStatement, verdictStance, remapActionOrdinals, joinAskToAnswer, stripAskLabel, bandBorderlineDeclarations, reasonedNegativeGroups, blankForeignRecordLinks } from "./findings-model.mjs";
167
167
  import { foldCaption, foldCardRead } from "./card-budget.mjs";
168
168
  // S2 — the report card's mechanical frame, composed from the record instead of dictated (see below).
169
169
  import { carriesOwnFrame, composeCard } from "./card-frame.mjs";
@@ -3934,7 +3934,7 @@ export function findFloorBreaches(ledger, floorAxes) { // @internal
3934
3934
  * `[]`: no manifest, unreadable, unparseable. That is the honest default here and not an absence read as
3935
3935
  * a pass, because the whole mechanism is opt-in — a run with no designation owes no floor, and a run whose
3936
3936
  * manifest cannot be read has no designation to honour. The refusal for an absent or unparseable manifest
3937
- * belongs to the stage that writes it and already exists there — verify.mjs:1335 CALL_REASON_SET 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
3938
3938
  * through `checkSiblingJson` and fails clearance-variants with `variantmodel_missing`. Checked, because
3939
3939
  * "something else refuses it" is exactly the assumption that turns a swallowed error into a silent pass.
3940
3940
  */
@@ -4803,7 +4803,7 @@ async function stage(name, ctx, opts = {}) {
4803
4803
  let wedgeCycles = 0;
4804
4804
  for (;;) {
4805
4805
  const r = await stageWithChain(name, ctx, opts);
4806
- 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; }
4807
4807
  wedgeCycles++;
4808
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.`);
4809
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 */ }
@@ -5035,7 +5035,7 @@ export function buildFailurePacket({ runId, agent, job = {}, failedStage, shortR
5035
5035
  // a top-level defect, or nothing salvageable — fails the run exactly as before. One bad object never silently
5036
5036
  // suppresses a real conflict, and never silently kills a ~$40 run.
5037
5037
  async function quarantineSynth(r, ctx, name) {
5038
- if (r.ok) return r;
5038
+ if (r.ok) return r; if (foreignRecordLinksBlanked(r, ctx, name)) return { ...r, ok: true, fail: undefined, repaired: "foreign-record-links-blanked" };
5039
5039
  // A6: a max_tokens-named content fault wraps the underlying validator token — strip the wrapper so a
5040
5040
  // per-finding shape defect on a ceiling-cut turn still reaches the lenient-salvage lane.
5041
5041
  const fail = String(r.fail || "").replace(/^max_tokens_no_output:/, "");
@@ -7028,7 +7028,7 @@ export function coverageRowAreaLabel(axis, unit) { // @internal
7028
7028
  return coverageRowAreaFrom(axis, unit, labelled, "(all of it)");
7029
7029
  }
7030
7030
 
7031
- export function coverageJudgmentRows(ledgerRows, planExecution) { // @internal
7031
+ export function coverageJudgmentRows(ledgerRows, planExecution, plan = null) { // @internal
7032
7032
  const open = [];
7033
7033
  for (const r of ledgerRows ?? []) {
7034
7034
  // `withheld-by-judgment` joins `confirmed-clean` in NOT reaching the reader, and for the opposite
@@ -7054,13 +7054,13 @@ export function coverageJudgmentRows(ledgerRows, planExecution) { // @internal
7054
7054
  const host = axisOf.has(q) ? open.find((o) => o.axis === axisOf.get(q)) : null;
7055
7055
  if (host) { host.qids.push(q); claimed.add(q); }
7056
7056
  }
7057
- // The qid rides VERBATIM wherever it lands. It is the identifier the plan, the receipt and every gate
7058
- // 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.
7059
7059
  const rows = open.map((o) => ({ area: o.area, ...(o.areaLabel ? { areaLabel: o.areaLabel } : {}),
7060
- note: o.qids.length ? `${o.note} (no band block for: ${o.qids.join(", ")})` : o.note }));
7060
+ note: o.note }));
7061
7061
  for (const qid of Array.isArray(planExecution?.missing) ? planExecution.missing : []) {
7062
7062
  const q = String(qid ?? "").trim();
7063
- 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" });
7064
7064
  }
7065
7065
  return rows.filter((r) => r.area && r.note);
7066
7066
  }
@@ -7074,7 +7074,7 @@ export function stampCoverageJudgmentRows(P, runDir, note, ctx) { // @internal
7074
7074
  runLog(runDir, { event: "coverage-judgment-rows", judgment: false, reason: "synthesis emitted no coverage_judgment — none is invented here" });
7075
7075
  return;
7076
7076
  }
7077
- const rows = coverageJudgmentRows(loadCoverageLedger(runDir).rows, readPlanExecution(ctx));
7077
+ const rows = coverageJudgmentRows(loadCoverageLedger(runDir).rows, readPlanExecution(ctx), ctx?.registerPlan ?? null);
7078
7078
  const discarded = Array.isArray(cj.rows) ? cj.rows.length : 0;
7079
7079
  const next = { ...doc, coverage_judgment: { ...cj } };
7080
7080
  if (rows.length) next.coverage_judgment.rows = rows;
@@ -7150,7 +7150,7 @@ function injectDeferralCoverage(P, runDir, note) {
7150
7150
  // the two constants below BYTE-FOR-BYTE, which is why they stay exported and are asserted against the
7151
7151
  // derivation in the tests.
7152
7152
  //
7153
- // 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
7154
7154
  // drift, and generalising the platform grid is a different piece of work.
7155
7155
  //
7156
7156
  // ── THE REMEDY CLAUSE NAMES A PRODUCT, AND FOR A WHILE IT NAMED A DELETED ONE ───────────────────────
@@ -7245,7 +7245,7 @@ export function scriptLaneRanOnRun(runDir, lane, { searchPolicy = null, env = pr
7245
7245
  // zh — shipped the full Phase-4 deepening: the SERP platform grid and the native read. Either
7246
7246
  // unit running IS the lane running (they gate independently; one on and one off still
7247
7247
  // searched in Chinese), so the legs stay exactly as they were.
7248
- // 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
7249
7249
  // runJxSerpGrid hard-refuses them and neither unit flag can ever describe them. The only
7250
7250
  // work these lanes do is the candidate fold, so "ran" is the fold having ACCEPTED
7251
7251
  // candidates — a frozen lane decision with an empty fold searched nothing and owes the row.
@@ -8498,9 +8498,15 @@ function postponeRun(e, run, meta = {}) {
8498
8498
  * PURE: a job in, a plain object out, no IO.
8499
8499
  */
8500
8500
  export function instructedScopeOf(job) {
8501
- const markNames = Array.isArray(job?.marks)
8502
- ? job.marks.map((m) => (typeof m === "string" ? m : m?.name)).filter(Boolean)
8503
- : (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);
8504
8510
  return {
8505
8511
  marks: markNames,
8506
8512
  classes: job?.classes ?? null,
@@ -8941,7 +8947,7 @@ async function pipelineInner(job, opts = {}) {
8941
8947
  // frame validator compares against THIS file, never against the frame's own paraphrase) —
8942
8948
  // paraphrase drift between the request and the frame is a defect, not a style choice.
8943
8949
  try {
8944
- 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");
8945
8951
  } catch (e) { note(`instructed-scope write failed (non-fatal): ${e.message}`); }
8946
8952
  // THE STORED DEFAULTS THE ENGINE CANNOT SEARCH — recorded by the run, not only by the plan preview.
8947
8953
  //
@@ -9430,7 +9436,7 @@ async function pipelineInner(job, opts = {}) {
9430
9436
  //
9431
9437
  // The prior channel is the TRUSTED one and already carries everything needed. Half forms are
9432
9438
  // driver-written, `seatFields(p, row)` carries their rulings by the same `pOk` path, and
9433
- // gateway.mjs:630 selectEngine 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
9434
9440
  // every attempt. So this is that mode, not a new one.
9435
9441
  //
9436
9442
  // ONE METRIC MOVES, deliberately: with no submission, `carried` counts every ruled row, because
@@ -13479,6 +13485,9 @@ async function pipelineInner(job, opts = {}) {
13479
13485
  // structured-only). Each stage is file-gated/resumable; per-card sessions feed the lint repair below.
13480
13486
  // C2 — fold same-owner+same-mark duplicate filings into one finding BEFORE the overview + cards read
13481
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 () => {
13482
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
13483
13492
  // qw/cn-scope-honesty — the sibling injection: a CN-family-scope run whose zh lane did not run
13484
13493
  // discloses what the native-language investigation would have searched, and where it is offered
@@ -13534,6 +13543,15 @@ async function pipelineInner(job, opts = {}) {
13534
13543
  try { writeVerdictSidecar(); }
13535
13544
  catch (e2) { throw new StageFailure("verdict", `verdict sidecar refresh failed after consolidation: ${String(e2.message).slice(0, 120)}`); }
13536
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
+ };
13537
13555
  // wp50/wi2 — the overview PROSE must speak the derived tier (VENZY: caption said "High risk" while
13538
13556
  // every code surface said VERY HIGH). Thread the just-refreshed sidecar into the stage prompt; the
13539
13557
  // deterministic backstop is predelivery-lint's overallTierChecks.
@@ -16015,6 +16033,20 @@ export function reconstructCtx(job, opts) { // @internal
16015
16033
  return ctx;
16016
16034
  }
16017
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
+
16018
16050
  // Copy a stage's about-to-be-overwritten output (+ its per-stage telemetry) into _history/<ts>-<reason>/ so a
16019
16051
  // prior result is NEVER lost and stays comparable. Returns the snapshot dir (or null if there was no output).
16020
16052
  //
@@ -16068,6 +16100,7 @@ export async function repairStale(job, opts = {}) { // @internal
16068
16100
  const labels = Array.isArray(rec?.labels) ? rec.labels : [];
16069
16101
  if (!labels.length) return null;
16070
16102
  attachFramework(ctx, { write: true });
16103
+ await attachJxAim(ctx); // the aim count the cold pass sets, as --experiment sets it on its rebuilt context
16071
16104
  // RE-DERIVE THE ORDER rather than trusting the file's. The recorded order was right when it was
16072
16105
  // written, and a code change since could have moved an edge — the file names WHAT, the live graph says
16073
16106
  // WHEN. (the file names WHAT, the live graph says WHEN)
@@ -16211,6 +16244,7 @@ async function runExperimentInner(job, opts) {
16211
16244
  if (opts.dispatchTrigger != null && !DISPATCH_TRIGGERS.includes(opts.dispatchTrigger))
16212
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(", ")}`);
16213
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
16214
16248
  const model = opts.model ?? (axis ? axisTier(axis).model : STAGES[name].model);
16215
16249
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
16216
16250
  const tag = [name, axis, opts.label ? kebab(opts.label) : null].filter(Boolean).join("-");
@@ -16742,7 +16776,7 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16742
16776
  // rate-limit catch already finds the run to postpone; the same hook lets a THROWN exit name the run.
16743
16777
  const opts = a.experiment
16744
16778
  ? { ...base, codename: a.codename, experiment: a.experiment, model: a.model, instructions: a.instructions, axis: a.axis, label: a.label, dispatchTrigger: a.dispatchTrigger }
16745
- : { ...base, codename: a.codename, fromStage: a.fromStage };
16779
+ : { ...base, codename: a.codename, fromStage: a.fromStage, ...(a.job ? {} : { jobRebuiltFromStatus: true }) };
16746
16780
  const script = resolve(fileURLToPath(import.meta.url));
16747
16781
  // `jobPath` is null on a rebuilt resume, so the advice composes a command that does not name a file
16748
16782
  // the box does not have — which is the whole defect this path exists to close, and printing it again
@@ -16922,3 +16956,67 @@ export function injectMeaningGapCoverage(P, runDir, note) { // @internal — e
16922
16956
  note(`[common-law] meaning-gap coverage skipped: ${String(e?.message || e).replace(/\s+/g, " ").slice(0, 100)}`);
16923
16957
  }
16924
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
+ }
16996
+
16997
+ // ── THE SALVAGE FOR A FOREIGN RECORD LINK: BLANK IT, RE-CHECK, DELIVER ──────────────────────────────────
16998
+ //
16999
+ // A seat that writes an office's page, or another register's, into a record link is refused by the
17000
+ // findings gate (`finding_record_url_foreign_host`). The salvage lane below takes that token, because it is
17001
+ // a `finding_…` token, but its lenient re-parse does not judge hosts, so it found nothing to repair and
17002
+ // failed the run: R2 on 0.4.1-beta.1 delivered no report over eleven copied office links. Here the driver
17003
+ // blanks every foreign link itself (blankForeignRecordLinks says how), keeps what it changed beside the run,
17004
+ // and re-checks the file with the stage's own validator. A pass delivers; anything still refused falls
17005
+ // through to the lanes below exactly as before, with fewer defects in the file.
17006
+ export function foreignRecordLinksBlanked(r, ctx, name) { // exported for its test
17007
+ const fail = String(r?.fail || "").replace(/^max_tokens_no_output:/, "");
17008
+ if (!fail.includes(FOREIGN_HOST_TOKEN) || !existsSync(ctx.paths.findings)) return false;
17009
+ let doc;
17010
+ try { doc = JSON.parse(readFileSync(ctx.paths.findings, "utf8")); } catch { return false; }
17011
+ const { doc: repaired, blanked, kept } = blankForeignRecordLinks(doc, activeRecordOrigins());
17012
+ if (!blanked.length) return false;
17013
+ atomicWrite(ctx.paths.findings, JSON.stringify(repaired, null, 2) + "\n");
17014
+ recordBlanking(ctx.paths.runDir, { stage: name, layer: "salvage", blanked, kept, fail });
17015
+ note(`[${name}] ${blanked.length} record link(s) on a host this register does not publish were blanked by the driver; the originals are in _driver/foreign-record-links.json`);
17016
+ const def = STAGES[name];
17017
+ const out = def?.out ? def.out(ctx.paths, ctx.axis) : null;
17018
+ const v = out && existsSync(out) && def.validate ? def.validate(out, readFileSync(out, "utf8")) : null;
17019
+ if (v?.ok) return true;
17020
+ runLog(ctx.paths.runDir, { event: "foreign-record-links-recheck", stage: name, ok: false, fail: String(v?.reason ?? "no validator").slice(0, 200) });
17021
+ return false;
17022
+ }
@@ -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
@@ -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.
@@ -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
  //
@@ -121,8 +121,8 @@ export const NOT_READ_BY_NAME = {
121
121
  // absent one is the caller's error and already throws. Declared so the source scan can tell "argument"
122
122
  // from "undeclared store" instead of demanding a gating for something that has none.
123
123
  export const CALLER_SUPPLIED = {
124
- "report.md": "the reportMd ARGUMENT — read unconditionally at index.mjs:653; an unreadable one throws, which is correct (there is no report to publish)",
125
- "audit.md": "the auditMd ARGUMENT — existence-gated at index.mjs:942-943; a run with no audit markdown legitimately publishes without a workbook",
124
+ "report.md": "the reportMd ARGUMENT — read unconditionally by publishReport() in index.mjs; an unreadable one throws, which is correct (there is no report to publish)",
125
+ "audit.md": "the auditMd ARGUMENT — existence-gated in publishReport() in index.mjs; a run with no audit markdown legitimately publishes without a workbook",
126
126
  };
127
127
 
128
128
  /**
@@ -574,7 +574,7 @@ function coverageJudgmentSummaryRows(cj) {
574
574
  const rows = Array.isArray(cj?.rows) ? cj.rows : [];
575
575
  if (!rows.length) return [];
576
576
  return [{ Field: '', Value: '' }, { Field: 'Coverage judgment — slices considered', Value: '', _head: true },
577
- ...rows.map((r) => ({ Field: ` ${plainNote(r.area)}`, Value: plainNote(r.note) }))];
577
+ ...rows.map((r) => ({ Field: ` ${plainNote(r.areaLabel || r.area)}`, Value: plainNote(r.note) }))];
578
578
  }
579
579
  function correctionsSummaryRows(c) {
580
580
  const entries = Array.isArray(c?.entries) ? c.entries : [];
@@ -90,7 +90,7 @@
90
90
  import { existsSync, readFileSync, appendFileSync } from "node:fs";
91
91
  import { join } from "node:path";
92
92
 
93
- import { kebab } from "./search-policy.mjs";
93
+ import { kebab } from "./search-policy.mjs"; import { moreThan } from "../shared/register-floor.mjs";
94
94
  import { resolveRegions } from "./register-plan.mjs";
95
95
  import { reachableRegions } from "./register-availability.mjs"; // — pure; the env binding is injected
96
96
  import { variantForms, VARIANT_RULES, VARIANT_CAP } from "./register-variants.mjs";
@@ -592,7 +592,7 @@ export function disclosedFloor(c) {
592
592
  }
593
593
 
594
594
  /** A floor as every page prints it: the register's own figure, and no sentence around it. */
595
- export const moreThan = (floor) => `more than ${floor.toLocaleString("en-US")}`;
595
+ export { moreThan } from "../shared/register-floor.mjs";
596
596
 
597
597
  /**
598
598
  * THE READER'S LINE FOR A COUNT THAT DID NOT LAND. A count's `unavailable` text is the counts record's: it
@@ -32,7 +32,7 @@
32
32
  // ── AND WHY IT DELEGATES TO resolveRegions RATHER THAN RE-IMPLEMENTING IT ───────────────────────────
33
33
  //
34
34
  // Five vocabularies sit between a display name and a covered office: display → `normalizeTerritory` →
35
- // `canonicalJurisdictionCode` → `offices.translate` → `covered.has`. `register-plan.mjs:206-216` records
35
+ // `canonicalJurisdictionCode` → `offices.translate` → `covered.has`. resolveRegions() in register-plan.mjs records
36
36
  // the copper-bastion incident from re-implementing that chain — the composer submitted display names,
37
37
  // corsearch's ISO-passthrough translate sent them to the wire verbatim, the vendor answered 500 rather
38
38
  // than 400, and recovery burned its park budget re-sending a query that could never succeed.
@@ -375,6 +375,11 @@ export const planExactOrWidth = (capabilities) =>
375
375
  Number.isFinite(capabilities?.exactOrWidth) && capabilities.exactOrWidth >= 1
376
376
  ? Math.floor(capabilities.exactOrWidth) : planMaxOrWidth(capabilities);
377
377
 
378
+ /** The OR-stack width for a SOUND-ALIKE band, by the same rule: `phoneticOrWidth` where a provider declares one. */
379
+ export const planPhoneticOrWidth = (capabilities) =>
380
+ Number.isFinite(capabilities?.phoneticOrWidth) && capabilities.phoneticOrWidth >= 1
381
+ ? Math.floor(capabilities.phoneticOrWidth) : planMaxOrWidth(capabilities);
382
+
378
383
  // The plan emits ONE `wildcard` predicate; the provider contract splits it into three sub-capabilities.
379
384
  // Mirrors the executor's planPredicateParams anchoring exactly (trailing * → prefix/starts-with,
380
385
  // leading * → suffix/ends-with, both/neither → infix over the raw pattern).
@@ -2190,7 +2195,9 @@ export function validatePlanFeasibility(plan, { capabilities = null, maxOrWidth
2190
2195
  if (!PLAN_PREDICATES.includes(e?.predicate)) add("unexecutable", `unknown predicate "${e?.predicate}"`);
2191
2196
  const names = Array.isArray(e?.terms) ? e.terms : e?.term != null ? [e.term] : [];
2192
2197
  if (!names.length || names.some((t) => !String(t ?? "").trim())) add("unexecutable", "empty term(s)");
2193
- const width = e?.predicate === "exact" && capabilities ? Math.max(maxOrWidth, planExactOrWidth(capabilities)) : maxOrWidth;
2198
+ const width = !capabilities ? maxOrWidth
2199
+ : e?.predicate === "exact" ? Math.max(maxOrWidth, planExactOrWidth(capabilities))
2200
+ : e?.predicate === "phonetic" ? Math.max(maxOrWidth, planPhoneticOrWidth(capabilities)) : maxOrWidth;
2194
2201
  if (Array.isArray(e?.terms) && e.terms.length > width) add("repairable", `OR-stack of ${e.terms.length} names exceeds the executor bound (${width}) — the executor runs it chunked`);
2195
2202
  for (const t of names) if (String(t).length > maxNameLength) add("repairable", `name exceeds ${maxNameLength} chars ("${String(t).slice(0, 40)}…") — provider-side truncation risk only`);
2196
2203
  if ((e?.nice_classes ?? []).some((c) => !Number.isFinite(Number(c)))) add("unexecutable", "non-numeric nice_class");
@@ -2433,6 +2440,8 @@ export function joinPlanToBands(plan, bandBlocksByAxis, { released = new Set() }
2433
2440
  // WHAT THE TOTAL COUNTS, when the register said: `records`, one per country a mark covers. Every
2434
2441
  // reader of this row reads the count as the register's own number (ruled 2026-10-02).
2435
2442
  ...(typeof b.total_counts === "string" ? { total_counts: b.total_counts } : {}),
2443
+ // The register's floor when it answered "at least N" instead of a count: total_hits stays null beside it.
2444
+ ...(Number.isFinite(b.total_floor) ? { total_floor: b.total_floor } : {}),
2436
2445
  });
2437
2446
  }
2438
2447
  const planQids = new Set(plan.entries.map((e) => e.qid));
@@ -2831,8 +2840,10 @@ export function openBlocksByAxis(skeleton, bandBlocksByAxis, plan) {
2831
2840
  // under the deleted prose join (the qid, or this number standalone). Both are now written INTO the
2832
2841
  // form's row by the driver, so the equivalence the join had to test for is structural — see
2833
2842
  // coverage-form.mjs. The field stays because the row, the render and the failure token all carry it.
2843
+ // A null total stays off the row: `Number(null)` is 0, which printed "(0 counted)" for a register's floor
2844
+ // ("at least 10,000") and for a count the register refused to give.
2834
2845
  unverified.push({ qid: e.qid,
2835
- ...(Number.isInteger(Number(b.total_hits)) ? { total_hits: Number(b.total_hits) } : {}),
2846
+ ...(b.total_hits != null && Number.isInteger(Number(b.total_hits)) ? { total_hits: Number(b.total_hits) } : {}),
2836
2847
  ...(unaccounted.length ? { unaccounted: unaccounted.slice(0, 8) } : {}),
2837
2848
  ...(unaccountedClasses.length ? { unaccounted_classes: unaccountedClasses.slice(0, 8) } : {}) });
2838
2849
  }
@@ -44,7 +44,7 @@
44
44
  // `verdict` is one of:
45
45
  // "result" — the value states what happened. The name is honest.
46
46
  // "invocation" — the value states that something was CALLED. Fix by renaming, or by adding a sibling
47
- // that carries the result (the remedy `repairs.mjs:724` already uses, keeping
47
+ // that carries the result (the remedy createRepairLedger() in repairs.mjs already uses, keeping
48
48
  // `outcome` for existing readers beside `dispatch` and `verdict`).
49
49
  // "out-of-scope" — never reaches `_driver/*.json` or `run.jsonl`; `atWriteSite` is 0.
50
50
 
@@ -62,8 +62,8 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
62
62
  { file: "driver/pipeline-knockout.mjs", noun: "recovered", sites: 1, atWriteSite: 1, verdict: "result",
63
63
  why: "written after the chunk files were actually removed" },
64
64
  { file: "driver/pipeline-knockout.mjs", noun: "outcome", sites: 3, atWriteSite: 3, verdict: "result",
65
- why: "the reviewing pass's own verdict on itself, read at each writing site: `nothing-flagged` after "
66
- + "the measurement came back empty, `applied` after the rewrite passed the merged gate and was "
65
+ why: "the reviewing pass's own verdict on itself, read at each writing site: `nothing-flagged` when "
66
+ + "the record had no line a reader meets first, `applied` after the rewrite passed the merged gate and was "
67
67
  + "written, and the `keep()` forms after the artifact or the gate refused it. Every one is a fact "
68
68
  + "about what already happened to the record, never the return of the call being logged" },
69
69
  { file: "driver/pipeline.mjs", noun: "closed", sites: 6, atWriteSite: 4, verdict: "result",
@@ -99,7 +99,7 @@ export function checkClearanceScopeRules({ job = null, profile = null, resolved
99
99
  //
100
100
  // Everything a CLIENT reads quotes `named`. This module was the one caller that discarded the
101
101
  // originals, so the wall refused a two-country Full country search naming "GB", "FR" while the door
102
- // refused the identical request naming "United Kingdom", "France" — products.mjs:141 states exactly
102
+ // refused the identical request naming "United Kingdom", "France" — tally() in products.mjs states exactly
103
103
  // that failure ("a refusal that quotes \"EU\" at someone who typed \"European Union\" sends them
104
104
  // hunting for a value they never used") and territory-tiers.mjs restates it for partitionTerritories.
105
105
  // The COUNT is the same either way: canonicalize dedups on code and products.mjs `tally` dedups on
@@ -131,7 +131,7 @@ export function checkClearanceScopeRules({ job = null, profile = null, resolved
131
131
  // `nativeLanguage: true` over a knockout base) and through any account whose defaultProduct does not
132
132
  // carry it.
133
133
  //
134
- // That is the shape products.mjs:293 refuses `caseLaw` for in as many words — "a flag accepted and
134
+ // That is the shape CASE_LAW_NOT_A_REQUEST declared in products.mjs refuses `caseLaw` for in as many words — "a flag accepted and
135
135
  // dropped is the worst available shape: whoever sent it believes they bought the deep reading, and
136
136
  // nothing anywhere disagrees" — and it was this build's own doctrine broken by this build's own code.
137
137
  //
@@ -233,6 +233,15 @@ const UNDECLARED = {
233
233
  synthesis: (P) => [
234
234
  { path: P.enforcerSignals, kind: "conditional",
235
235
  why: "named in the prompt only when ctx.enforcerSignals > 0; aim-attention telemetry, deliberately outside the freshness map" },
236
+ // The job's own scope. The prompt never names it, so nothing declared it, and the sandbox rebuilt the
237
+ // declination spec without it: no marks and no classes, so both of the tool's refusals were off in every
238
+ // synthesis arm while the canonical pass had them on.
239
+ { path: P.instructedScope, kind: "driver-side",
240
+ why: "prepareDeclinationSpec writes the declination spec's scope from it; without it the tool's refusals rest on nothing and accept what the canonical pass refused" },
241
+ // The Chinese-evidence flags, named in the prompt only when ctx.jxAim is set (stages.mjs synthesis
242
+ // message). Undeclared, an arm on a run that had them dispatched without the block.
243
+ { path: driverDir(P.runDir, "jx", "aim-attention.json"), kind: "conditional",
244
+ why: "named in the prompt only when ctx.jxAim > 0 — the zh lane's aim-attention flags; aim-attention only, deliberately outside the freshness map" },
236
245
  ],
237
246
  };
238
247
 
@@ -560,9 +560,10 @@ export const KO_STAGES = {
560
560
  // be composed by anything except the production call site, which is how a dispatch goes unexamined
561
561
  // — and the promise has to branch. A stage message that says "the lines are below" and then lists
562
562
  // none has handed the seat an absence dressed as a pass, which is the reading this whole rule
563
- // exists to stop. The driver does not dispatch on an empty measurement; the branch is here because
564
- // "it cannot happen" is not a property a message should depend on.
565
- message: ({ K, evidenceLines = [], exclusionNote = "" }) => lines(
563
+ // exists to stop. A record the driver's read flags nowhere is handed its first-read lines instead,
564
+ // by the owner's ruling of 2026-10-10, so both branches have rows under them; a record with no such
565
+ // line is not dispatched at all.
566
+ message: ({ K, evidenceLines = [], firstReadLines = [], exclusionNote = "" }) => lines(
566
567
  // THE CLIENT'S QUESTION LEADS, in the owner's words (ruling 2026-09-24), ahead of the rewrite job.
567
568
  `Does the report answer the client's question: who could object, how strong they are, what the client should do? Are the marks a lawyer would list present, and is each position written from the record that matters? Answer that before any check on wording.`,
568
569
  reads(["skills/clearance-search/report-prose.md"]),
@@ -571,8 +572,8 @@ export const KO_STAGES = {
571
572
  `The rated record: ${K.findings}. Read it — the lines named below are quoted from it and you are rewriting them in place.`,
572
573
  evidenceLines.length
573
574
  ? `THE DRIVER MEASURED THESE LINES. Each row is the line, the ADDRESS that names it, and what a deterministic read found. THIS IS EVIDENCE, NOT A VERDICT: judge each in context and pass over any where the word is the subject rather than the profession's shorthand. ${exclusionNote}`
574
- : `THE DRIVER'S READ FLAGGED NO LINE ON THIS RECORD, so there is no table below and nothing here is evidence of a fault. Read the record yourself against the rule above; if it is already plain, send one \`declined\` row saying so rather than inventing a rewrite to fill the call.`,
575
- ...evidenceLines,
575
+ : `THE DRIVER'S READ FLAGGED NO LINE ON THIS RECORD, so nothing below is evidence of a fault. These are the lines a reader meets first, each with the ADDRESS that names it. Rewrite one only where it would answer the client's question more plainly; if every line already does, send one \`declined\` row at one of these addresses saying so, rather than inventing a rewrite to fill the call.`,
576
+ ...(evidenceLines.length ? evidenceLines : firstReadLines),
576
577
  // ── THE DICTATION NAMES THE FIELD, NOT AN EFFECT ────────────────────────────────
577
578
  //
578
579
  // "Make the report plainer" is read and not applied, and nothing refuses — it simply does not