clearotron 0.3.2-beta.6 → 0.3.2-beta.8

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 (98) hide show
  1. package/.env.example +24 -23
  2. package/INSTALL.md +142 -75
  3. package/README.md +3 -3
  4. package/bin/onboard.mjs +637 -216
  5. package/bin/start.mjs +133 -23
  6. package/bin/update.mjs +82 -11
  7. package/build-info.json +2 -2
  8. package/docs/architecture/04-configuration-reference.md +26 -11
  9. package/docs/architecture/05-config-governance.md +17 -7
  10. package/driver/CHANGELOG.md +83 -0
  11. package/driver/band-size.mjs +59 -0
  12. package/driver/config-inventory.mjs +112 -9
  13. package/driver/connotation-search.mjs +45 -0
  14. package/driver/contract-arm2-baseline.json +1 -3
  15. package/driver/contract-e3-backlog.mjs +29 -29
  16. package/driver/contract-vocabulary.mjs +59 -25
  17. package/driver/door-gates.mjs +41 -7
  18. package/driver/driver.config.mjs +272 -59
  19. package/driver/engine/CONTRACT.md +10 -3
  20. package/driver/engine/README.md +2 -2
  21. package/driver/engine/anthropic-agent.mjs +77 -21
  22. package/driver/engine/auth.mjs +129 -10
  23. package/driver/engine/jx-turn.mjs +7 -6
  24. package/driver/engine/mcp/recording-server.mjs +13 -0
  25. package/driver/engine/openai-agent.mjs +4 -2
  26. package/driver/engine/probe.mjs +110 -23
  27. package/driver/findings-model.mjs +1 -1
  28. package/driver/flag-snapshot.mjs +28 -5
  29. package/driver/gateway.mjs +30 -21
  30. package/driver/jx-lanes.mjs +21 -2
  31. package/driver/jx-units.mjs +6 -3
  32. package/driver/jx.mjs +4 -2
  33. package/driver/matter-frame-record.mjs +90 -1
  34. package/driver/named-band.mjs +34 -2
  35. package/driver/package.json +1 -1
  36. package/driver/pipeline.mjs +391 -26
  37. package/driver/portal-config-view.mjs +30 -1
  38. package/driver/portal-report.mjs +15 -1
  39. package/driver/portal-service.mjs +46 -6
  40. package/driver/predelivery-lint.mjs +12 -2
  41. package/driver/publish/index.mjs +46 -5
  42. package/driver/publish/knockout.mjs +10 -1
  43. package/driver/publish/render-knockout.mjs +69 -7
  44. package/driver/publish/render.mjs +170 -59
  45. package/driver/publish/report-data.mjs +4 -1
  46. package/driver/publish/report-topbar.mjs +58 -0
  47. package/driver/publish/templates/report.css +28 -2
  48. package/driver/publish/xlsx.mjs +13 -1
  49. package/driver/register-availability.mjs +2 -2
  50. package/driver/register-coverage.mjs +94 -1
  51. package/driver/register-digest-record.mjs +236 -11
  52. package/driver/register-plan.mjs +170 -0
  53. package/driver/result-noun-fields.mjs +7 -4
  54. package/driver/run-economics.mjs +41 -10
  55. package/driver/run-requirements.mjs +173 -9
  56. package/driver/runner.mjs +3 -3
  57. package/driver/stages.mjs +12 -8
  58. package/driver/suite-census.json +162 -72
  59. package/driver/systemd/README.md +7 -4
  60. package/driver/terminal-clamp.mjs +107 -1
  61. package/driver/tokens.mjs +169 -3
  62. package/driver/unit-environment.mjs +42 -15
  63. package/driver/unit-inventory.mjs +19 -2
  64. package/driver/verify.mjs +50 -5
  65. package/mcp-server/CHANGELOG.md +8 -0
  66. package/mcp-server/http-server.mjs +4 -0
  67. package/mcp-server/lib/audit.mjs +11 -1
  68. package/mcp-server/lib/http-handler.mjs +6 -2
  69. package/mcp-server/package.json +1 -1
  70. package/mcp-server/server.mjs +16 -2
  71. package/package.json +1 -1
  72. package/portal-ui/dist/assets/{index-5UyqAyNM.js → index-6jzO9HiX.js} +155 -79
  73. package/portal-ui/dist/index.html +1 -1
  74. package/portal-ui/package.json +1 -1
  75. package/providers/clarivate/src/capabilities.js +5 -5
  76. package/providers/clarivate/src/core.js +1 -1
  77. package/providers/corsearch/src/core.js +2 -2
  78. package/providers/jx/README.md +2 -1
  79. package/providers/jx/src/turn-envelope.mjs +8 -3
  80. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  81. package/providers/oauth-mcp-bridge/package.json +1 -1
  82. package/providers/perplexity/src/core.js +3 -3
  83. package/providers/signa/src/capabilities.js +5 -6
  84. package/providers/signa/src/core.js +1 -1
  85. package/providers/uspto-local/README.md +1 -1
  86. package/scripts/authority-boundary-probe.mjs +4 -2
  87. package/scripts/env-audit.mjs +12 -6
  88. package/scripts/freeze-example-run.mjs +49 -16
  89. package/scripts/generated-files-are-current.mjs +69 -4
  90. package/scripts/release-duplicate-notes.mjs +246 -0
  91. package/scripts/release-publish-guard.mjs +64 -6
  92. package/scripts/report-print-check.mjs +194 -0
  93. package/scripts/settings-render-check.mjs +75 -2
  94. package/scripts/test-full.mjs +96 -3
  95. package/scripts/test-run.mjs +10 -0
  96. package/shared/deployment-box.mjs +7 -2
  97. package/shared/driver-dir.mjs +1 -1
  98. package/shared/names-in-force.mjs +1 -1
@@ -10,7 +10,7 @@ import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, copyFil
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
12
12
  import { driverDir, driverRel, ensureDriverDir } from "../shared/driver-dir.mjs"; // — one definition of where `_driver/` is
13
- import { terminalClampDecision, orderClausesForLede, clientConditions } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
13
+ import { terminalClampDecision, orderClausesForLede, clientConditions, clauseForDefect } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
14
14
  import { recordSpan } from "./attributed-span.mjs"; // — driver work the decomposition can attribute
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { runStage, correctionHint, gridLedgerNameFor, draftCarryEligible, toolWrittenArtifact, selectEngine } from "./gateway.mjs";
@@ -31,7 +31,7 @@ import { buildRunContext, deriveSlug, kebab } from "./phase0.mjs";
31
31
  import { paths, STAGES, axisTier, decideAxes, assertTierSanity, assertEffectiveTier, lines, AGENT_WHATSAPP, whatsappRouting,
32
32
  chainEntries, stageOrdinal, stageInputs, stageOutputs, dependencyOrder, REGISTER_AXES, REGISTER_ENUMERATE_TOOL,
33
33
  buildEscalationFollowup, buildEnvelopeCloseFollowup, buildFrameReopenFollowup,
34
- buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage } from "./band-size.mjs";
34
+ buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage, derivedLimitSec, limitExceedsCeiling, ceilingRefusal } from "./band-size.mjs";
35
35
  import { IDENTITY_FILE as REPORT_IDENTITY_FILE } from "./report-overview-record.mjs";
36
36
  import { dispatchRows, clearedSignatures } from "./seat-attempts.mjs";
37
37
  import { CONTEXT_DERIVATIONS, DISPATCH_EXTRAS, INLINE_CONTEXT, sandboxManifest, sandboxGaps, derivationsFor } from "./stage-context.mjs"; // — what a stage is actually handed
@@ -45,7 +45,8 @@ import { readRegisterTaint, readActiveTaintAxes } from "./register-taint.mjs";
45
45
  import { parseNamedBand, mergeNamedBands, findCollapsedBands, quarantineUnknownStates, taintQuarantineCleanBlocks, bandRecords } from "./named-band.mjs";
46
46
  import { recordOriginsFor } from "./record-origins.mjs";
47
47
  import { REGISTER_PROVIDER } from "./driver.config.mjs";
48
- import { FACTS_FILE as DIGEST_FACTS_FILE, ACCOUNTING_STAMP as DIGEST_ACCOUNTING_STAMP, recordedFindingUris } from "./register-digest-record.mjs"; // conversion 11 — the render's facts sidecar and the accounting era stamp
48
+ import { FACTS_FILE as DIGEST_FACTS_FILE, ACCOUNTING_STAMP as DIGEST_ACCOUNTING_STAMP, recordedFindingUris,
49
+ digestAccountingGap, digestBatchBrief, batchesOf } from "./register-digest-record.mjs"; // conversion 11 — the render's facts sidecar and the accounting era stamp
49
50
  import { buildBandShape, dominantElementComposites, deriveRegisterPositions, floorTierByMark, floorMarkKey } from "./band-shape.mjs"; // PR-8 — the deterministic reading layer; P2-A — candidates + positions
50
51
  import { deriveOwnerScreen, ownerScreenNegative } from "./owner-screen.mjs"; // P2-B — the owner×element screen's own receipt
51
52
  import { reconcileRecall, parseFindingsEndings, parseCrowdRulings, readOkRecordUris,
@@ -96,8 +97,10 @@ import { publishReport, composeEmailHtml, deliverySubject } from "./publish/inde
96
97
  import { parseCaseLawProfiles, joinCaseLawProfiles } from "./publish/parse.mjs";
97
98
  import { buildAuditMd, parseSpineFindingBlocks } from "./publish/audit-from-spine.mjs";
98
99
  import { deriveRegisterPresence } from "./publish/register-presence.mjs"; // — the audit stores every live in-scope record
99
- import { lastAcceptedMatterFrame, frameIdentifiedClasses } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed; and the classes it judged necessary beyond the instructed ones, which the plan compile unions in
100
- import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs"; // — the stamp the late lanes never met
100
+ import { lastAcceptedMatterFrame, frameIdentifiedClasses, frameHouseElementCandidate } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed; and the classes it judged necessary beyond the instructed ones, which the plan compile unions in
101
+ import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs";
102
+ import { excludeHouseElement, verifyHouseElementOwnership, resolveRegions as resolvePlanRegions, HOUSE_ELEMENT_RECEIPT } from "./register-plan.mjs"; // 647 — the client's own element leaves the conflict analysis only on a verified receipt
103
+ import { resolveRecordExecutor } from "./register-records.mjs"; // — the stamp the late lanes never met
101
104
  import { slimLine, crowdLine } from "./hit-list.mjs"; // — the list the run works from; crowds ride it as a sibling array
102
105
  import { mintCrossCheckDoubts, mintContradictionDoubts, stitchDoubts, applyClosure } from "./doubt-ledger.mjs"; // doubt-stitch + doubt-closure (2026-07-22)
103
106
  // Conversion 6: the two line-form parsers are no longer on the live path — the seat sends typed
@@ -201,6 +204,39 @@ async function defaultRecordFetcher(uri, ctx) {
201
204
  return activeProvider().recordFetch(uri, ctx);
202
205
  }
203
206
 
207
+ /**
208
+ * THE ENGINE RE-ISSUES A DICTATED MEANING SEARCH ITSELF, without asking the assessing model to redo the
209
+ * sweep — the same shape as `defaultRecordFetcher` above, which fetches a register record in code when
210
+ * one was dropped unexamined rather than sending the model back for it.
211
+ *
212
+ * "Through the same tool" is literal and costs no new machinery: the research provider's grid functions
213
+ * are a plain module with no listener, so the component the model calls and this one call the SAME
214
+ * functions on the same provider. There is no second implementation to drift.
215
+ *
216
+ * NARROW BY CONSTRUCTION. The spec handed over carries only the queries that are missing, so this buys
217
+ * those cells rather than re-running a sweep that already cost real money and real minutes.
218
+ *
219
+ * It DECIDES nothing. The caller re-merges and the gate judges, so a re-issue that comes back empty is
220
+ * simply a re-merge that still fails, and is disclosed as such rather than counted as searched.
221
+ */
222
+ async function defaultConnotationReissuer(queries, { spec, modelOverride = null } = {}) {
223
+ const apiKey = process.env.PERPLEXITY_API_KEY || "";
224
+ if (!apiKey) return { ok: false, cause: "no research credential in the driver environment", rows: [] };
225
+ const core = await import("../providers/perplexity/src/core.js");
226
+ const narrow = { ...spec, connotation: { ...(spec.connotation ?? {}), queries: queries.slice() } };
227
+ try {
228
+ const task = core.buildGridProgramTask(narrow);
229
+ const data = await core.callAgentAPI(apiKey,
230
+ core.buildRequestBody({ task, preset: "pro-search", modelOverride, enableSandbox: true }));
231
+ const cap = core.captureGridFromResponse(data, narrow);
232
+ if (!cap?.ok) return { ok: false, cause: String(cap?.error ?? "capture failed").slice(0, 160), rows: [] };
233
+ const rows = parsePrRiskResults(cap.ledgerJson).filter((r) => queries.includes(r.query));
234
+ return { ok: true, rows, requested: queries.length };
235
+ } catch (e) {
236
+ return { ok: false, cause: String(e?.message ?? e).slice(0, 160), rows: [] };
237
+ }
238
+ }
239
+
204
240
  // The ACTIVE provider's capability contract and the offices this DEPLOYMENT cannot reach both moved to
205
241
  // register-unreachable.mjs, unchanged, because Depth 2's count and record lanes need the same
206
242
  // two answers and a second binding of the member→variable lookup is how two lanes come to disagree
@@ -2225,6 +2261,69 @@ function logFullyDeferredAxes(plan, P, source) {
2225
2261
  // `frozenOnly` (item): read the FROZEN plan or nothing. reconstructCtx is an introspection path —
2226
2262
  // it must never compile and freeze a plan into an existing run as a side effect of being asked what the
2227
2263
  // run already holds. (attachProfile / attachFramework take the same posture via `write:false`.)
2264
+ /**
2265
+ * Ask the register whether the client owns the element the frame proposed, and record the answer.
2266
+ *
2267
+ * ASYNC AND SEPARATE FROM THE COMPILE, which is why it is its own step rather than a branch inside
2268
+ * `attachRegisterPlan`: that function is synchronous and is called on the resume path too. The shape is
2269
+ * the digest's — an async driver step writes a receipt, a synchronous consumer reads it — and it buys
2270
+ * the property that matters here: the compile cannot accidentally exclude anything by reaching for a
2271
+ * promise it does not await.
2272
+ *
2273
+ * NOT A REGISTERED CONTEXT DERIVATION, and the reason is worth stating because the obvious reading is
2274
+ * that it should be. `stage-context.mjs` declares the derivations a sandbox can REPLAY, and its runners
2275
+ * are synchronous. This one asks a live register a question; there is no offline re-derivation of that,
2276
+ * and declaring it would promise the `--experiment` rig something no runner can deliver — which the
2277
+ * guard catches by name, loudly, as it should. A sandbox instead inherits the receipt with the rest of
2278
+ * `_driver/`, and a sandbox that has none excludes nothing, which is the same answer every other
2279
+ * unverified run gets.
2280
+ *
2281
+ * NEVER-KILL. Every failure writes an unverified receipt and the run plans exactly as it does today.
2282
+ * The element being searched in full is the safe direction and the status quo; the only thing that can
2283
+ * narrow a client's search is a register answer naming their own live registration.
2284
+ */
2285
+ async function verifyAndRecordHouseElement(ctx, opts = {}) {
2286
+ const P = ctx.paths;
2287
+ try {
2288
+ const proposed = frameHouseElementCandidate(P.runDir);
2289
+ if (!proposed) return; // the ordinary case: the frame proposed nothing and there is no question
2290
+
2291
+ const classes = [...new Set([...inScopeClassList(ctx.job, ctx.profile).map(String), ...frameIdentifiedClasses(P.runDir)])];
2292
+ // THE CLIENT'S OWN NAMES. The profile's trading names are the curated list; the matter's customer is
2293
+ // the free-text one. Both, because a profile may be absent on a one-off matter and a customer string
2294
+ // may be a person where the filings are held by a company.
2295
+ const owners = [...new Set([...(ctx.profile?.selfExclusionOwners ?? []), ctx.job?.customer].map((o) => String(o ?? "").trim()).filter(Boolean))];
2296
+ const caps = registerCapabilities();
2297
+ const { regions } = resolvePlanRegions(registerJurisdictions(ctx.job, ctx.profile), caps);
2298
+ const rec = resolveRecordExecutor({
2299
+ lister: opts?.recordLister ?? null, adapter: activeProvider(),
2300
+ agentId: ctx.agentId ?? null, sessionKey: `prelim-${ctx.run.slug}-${ctx.run.codename}`,
2301
+ recordLog: runRecordLogPath(P.runDir),
2302
+ fixtureDir: ctx.job?.registerFixtures?.records ?? null,
2303
+ });
2304
+ // AN EXACT-NAME LISTING OF THE ELEMENT, filtered to the client's own live in-class registrations by
2305
+ // the verifier. The owner join is done on REAL RECORDS the register returned, which is what
2306
+ // "verified by owner, not asserted" means — there is no owner-predicate listing on this interface
2307
+ // and inventing one would be a second way to ask the same question.
2308
+ const lookup = typeof rec.list !== "function" ? null : async ({ element, classes: cls }) => {
2309
+ const r = await rec.list(element, { classes: cls, regions, limit: 50 });
2310
+ if (!r || r.ok === false) return { ok: false, reason: String(r?.reason ?? "the register listing did not answer") };
2311
+ return { ok: true, records: Array.isArray(r.records) ? r.records : [] };
2312
+ };
2313
+
2314
+ const receipt = await verifyHouseElementOwnership({ element: proposed.element, classes, owners, lookup });
2315
+ receipt.remainder = proposed.remainder; // carried so the compile needs only this one file
2316
+ ensureDriverDir(P.runDir);
2317
+ atomicWrite(driverDir(P.runDir, HOUSE_ELEMENT_RECEIPT), JSON.stringify(receipt, null, 2) + "\n");
2318
+ runLog(P.runDir, { event: "house-element-ownership", verified: receipt.verified, reason: receipt.reason,
2319
+ records: receipt.records.length, owners: owners.length, executor: rec.source });
2320
+ if (!receipt.verified) note(`house element: ${proposed.element} stays in the search — ${receipt.reason}`);
2321
+ } catch (e) {
2322
+ // A THROW HERE MUST NOT COST A REPORT. No receipt means no exclusion, which is today's plan.
2323
+ runLog(P.runDir, { event: "house-element-ownership-failed", reason: String(e?.message ?? e).slice(0, 160) });
2324
+ }
2325
+ }
2326
+
2228
2327
  function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2229
2328
  const P = ctx.paths;
2230
2329
  if (frozenOnly) {
@@ -2248,7 +2347,30 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2248
2347
  ctx.registerPlan = null;
2249
2348
  let compiled;
2250
2349
  try {
2251
- const manifest = parseVariantManifestModel(readFileSync(P.variantManifestModel, "utf8"));
2350
+ let manifest = parseVariantManifestModel(readFileSync(P.variantManifestModel, "utf8"));
2351
+ // ── 647 — THE CLIENT'S OWN ELEMENT LEAVES THE CONFLICT ANALYSIS, ON EVIDENCE OR NOT AT ALL ─────
2352
+ //
2353
+ // The receipt is the ONLY thing that arms this. An absent, unreadable or unverified receipt plans
2354
+ // exactly as this compile did before the rule existed, which is the element searched in full — the
2355
+ // safe direction, and the one a client is never harmed by.
2356
+ let houseConfirmation = null;
2357
+ let houseReceipt = null;
2358
+ try { houseReceipt = JSON.parse(readFileSync(driverDir(P.runDir, HOUSE_ELEMENT_RECEIPT), "utf8")); }
2359
+ catch { houseReceipt = null; }
2360
+ if (houseReceipt?.verified === true) {
2361
+ const r = excludeHouseElement(manifest, { element: houseReceipt.element, remainder: houseReceipt.remainder });
2362
+ if (r.refused) {
2363
+ // VERIFIED OWNERSHIP AND STILL NOT APPLIED. The receipt answers who owns the element; the
2364
+ // transform answers whether this manifest's mark can survive the cut. Both must hold.
2365
+ runLog(P.runDir, { event: "house-element-not-applied", reason: r.refused, element: houseReceipt.element });
2366
+ } else {
2367
+ manifest = r.manifest;
2368
+ houseConfirmation = r.confirmation;
2369
+ runLog(P.runDir, { event: "house-element-excluded", element: houseReceipt.element,
2370
+ remainder: houseReceipt.remainder, dominant_element: manifest.dominant_element,
2371
+ evidence: (houseReceipt.records ?? []).length });
2372
+ }
2373
+ }
2252
2374
  let form = null;
2253
2375
  try { form = JSON.parse(readFileSync(P.formNeighbourhood, "utf8")); } catch { /* form band optional */ }
2254
2376
  compiled = compileRegisterPlan({
@@ -2275,6 +2397,19 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2275
2397
  // rides the plan as a disclosed deferral, never as a silently narrower search.
2276
2398
  unavailableOffices: registerUnavailableOffices(),
2277
2399
  });
2400
+ // REQUIREMENT 3 — the element is still checked ONCE, as a confirmation of the client's own live
2401
+ // registrations rather than as a conflict sweep, so the exclusion is evidenced on the report by a
2402
+ // query that ran. Appended after the compile because it is not derived from the manifest: it is the
2403
+ // one question about the element the plan still owes.
2404
+ if (houseConfirmation && compiled?.entries) {
2405
+ const used = new Set(compiled.entries.map((e) => e.qid));
2406
+ compiled.entries.push({
2407
+ ...houseConfirmation,
2408
+ qid: mintSupplementalQid({ prefix: "house", term: houseConfirmation.term, used }),
2409
+ nice_classes: compiled.nice_classes ?? [],
2410
+ regions: compiled.regions ?? [],
2411
+ });
2412
+ }
2278
2413
  } catch (e) {
2279
2414
  // NEVER-KILL at mint time: no/malformed sibling or a class-less matter degrades to the legacy
2280
2415
  // path with a logged reason — the plan improves the run, it never turns delivery off.
@@ -3615,6 +3750,27 @@ export function digestDispatchExtra(ctx, { trigger = "fresh", willRun = true, ex
3615
3750
  }
3616
3751
  }
3617
3752
  } catch (e) { note(`coverage-form brief skipped (non-fatal — the form is in _driver/ and the validator refuses an unsettled row): ${String(e.message).slice(0, 80)}`); }
3753
+ // — THE BATCH BLOCK: how the driver split this run's band, and which batches are still outstanding.
3754
+ //
3755
+ // The seat is told the split HERE rather than in the stage's dictation because the numbers are the
3756
+ // run's, not the contract's: a band of 40 records is one batch and a band of 1,161 is twelve, and on a
3757
+ // resume the outstanding set is what the killed attempt did not reach. The dictation says the rule;
3758
+ // this says the arithmetic.
3759
+ //
3760
+ // BEST-EFFORT, AND SAFE BY CONSTRUCTION rather than by argument — the coverage brief's precedent above,
3761
+ // for its reason. Nothing here enforces anything: a call naming a batch is judged against the driver's
3762
+ // own split whether or not this block composed, the tool's refusal re-states what is outstanding, and
3763
+ // the stage's exit gate refuses a document that ends fewer records than the run carried in. A brief
3764
+ // that fails to compose costs a corrective round; it cannot cost a record.
3765
+ try {
3766
+ const gap = digestAccountingGap(P.runDir);
3767
+ const block = digestBatchBrief(gap);
3768
+ if (block) {
3769
+ out = out ? `${out}\n\n${block}` : block;
3770
+ runLog(P.runDir, { event: "digest-batch-brief", trigger, owed: gap.owed.length,
3771
+ batches: batchesOf(gap.owed).length, unaccounted: gap.unaccounted.length });
3772
+ }
3773
+ } catch (e) { note(`digest batch brief skipped (non-fatal — the tool judges every batch against the driver's own split regardless): ${String(e.message).slice(0, 80)}`); }
3618
3774
  // AD-2 A9 (E2E-R2) + the P5 review (2026-07-31): a corrective/repair pass is told NOT to re-read the
3619
3775
  // whole placement file — the per-candidate tiers are in placements.json — but the RULINGS TAIL (band
3620
3776
  // reconciliation, disagreements, coverage rulings, open questions) lives ONLY in the md. Leaving it to
@@ -4309,6 +4465,25 @@ async function stageOnce(name, ctx, opts = {}) {
4309
4465
  // Evaluated on `effThinking`, not `thinking`: the anthropic gate above nulls the tier for a
4310
4466
  // cross-provider model, and a guard that reads the nulled value would go blind on the same overrides.
4311
4467
  assertEffectiveTier(label, { model, thinking: effThinking });
4468
+ // ── THE TIME LIMIT IS DERIVED FROM THE BAND THIS DISPATCH IS HANDED ─────────────────────────────
4469
+ //
4470
+ // `def.timeoutSec` was a constant chosen once against a band nobody recorded beside it, and on a
4471
+ // dense matter the two band-reading stages died AT their walls having written nothing. The base is
4472
+ // still the stage's own number; what the band decides is how far above it this dispatch may go. A
4473
+ // band at or under the reference returns the base untouched, so nothing already verified moves.
4474
+ const dispatchBand = bandSizeForStage(name, P);
4475
+ const derivedLimit = derivedLimitSec(def.timeoutSec, dispatchBand);
4476
+ // REFUSED BEFORE A SEAT IS DISPATCHED, not after the wall. A limit past the ceiling is a prediction
4477
+ // that this stage will be killed; starting it spends the whole prediction to arrive where the refusal
4478
+ // already is, and the refusal NAMES the band size, which is the finding a killed attempt never gives.
4479
+ if (limitExceedsCeiling(derivedLimit.sec)) {
4480
+ const reason = ceilingRefusal(name, derivedLimit);
4481
+ runLog(P.runDir, { event: "stage-input-over-ceiling", stage: name,
4482
+ inputBytes: derivedLimit.inputBytes, derivedLimitSec: derivedLimit.sec });
4483
+ throw new StageFailure(name, reason, undefined, { failClass: "deterministic" });
4484
+ }
4485
+ runLog(P.runDir, { event: "stage-limit-derived", stage: name, base: def.timeoutSec,
4486
+ inputBytes: derivedLimit.inputBytes, derivedLimitSec: derivedLimit.sec, basis: derivedLimit.basis });
4312
4487
  const r = await runStage(label, {
4313
4488
  agent: execAgent,
4314
4489
  message,
@@ -4317,11 +4492,12 @@ async function stageOnce(name, ctx, opts = {}) {
4317
4492
  // opts.sessionKey lets a followup RESUME the exact key a prior run won on (winning-key hardening); else
4318
4493
  // the canonical base key. (A followup with a stale base key would resume a failed attempt — see runStage.)
4319
4494
  sessionKey: opts.sessionKey ?? `prelim-${ctx.run.slug}-${ctx.run.codename}-${name}${keyAxis}`,
4320
- timeoutSec: def.timeoutSec,
4495
+ timeoutSec: derivedLimit.sec ?? def.timeoutSec,
4496
+ derivedLimit, // recorded on every attempt row: the size it was derived from, and the number
4321
4497
  stallSec: def.stallSec, // per-stage stall override (heavy stages); undefined → global CLEAROTRON_STALL_MS
4322
4498
  expectFile: out,
4323
4499
  validate: def.validate,
4324
- runDir: P.runDir, bandSize: bandSizeForStage(name, P),
4500
+ runDir: P.runDir, bandSize: dispatchBand,
4325
4501
  // ── — THE PRESENTING SIDE, RECOMPOSED PER ATTEMPT ────────────────────────────────────────
4326
4502
  //
4327
4503
  // A stage that declares `refreshCtx` is saying its dispatch text depends on state the run CHANGES
@@ -7296,7 +7472,7 @@ export function fullProseOrdinals(findings) {
7296
7472
  .filter((o) => o != null);
7297
7473
  }
7298
7474
  // ASSEMBLE report.md = the overview shell (front-matter + Actions/Coverage/Methodology) + `# Marks` + the
7299
- // per-card files in render order (composite desc, ordinal asc — mirrors render.mjs:502), `open: true` on the
7475
+ // per-card files in render order (composite desc, ordinal asc — mirrors render.mjs `sortedAll`), `open: true` on the
7300
7476
  // single top card. Findings with no card file (a failed report-card, or a secondary finding) are NOT emitted —
7301
7477
  // render.mjs synthesizes them structured-only from findings.json, so nothing is silently dropped. Pure file IO.
7302
7478
  // spec 64 — "### Only you can close these" is CODE-BUILT from the typed actions register (the report-
@@ -8816,6 +8992,7 @@ async function pipelineInner(job, opts = {}) {
8816
8992
  must(await stage("prelim-variants", ctx), "prelim-variants");
8817
8993
  deriveScopeLedgerJson(ctx); // frame-omission design: code-derive scope-ledger.json from the validated prose (never-kill)
8818
8994
  deriveFormNeighbourhood(ctx); // mechanical FORM band: code-derive form-neighbourhood.json from the manifest's distinctive element(s) — the model-free form floor the register funnel searches (never-kill)
8995
+ await verifyAndRecordHouseElement(ctx, opts); // ask the register who owns the frame's proposed house element, BEFORE the plan is compiled from it
8819
8996
  attachRegisterPlan(ctx); // WS2 (B3): compile/freeze/reuse the deterministic register plan (flag-gated; frozen plan wins on resume; never-kill on mint)
8820
8997
 
8821
8998
  // spec 64 (B2) — proactive recall probes: prior-confirmed conflicts for THIS mark (the workspace
@@ -9170,6 +9347,64 @@ async function pipelineInner(job, opts = {}) {
9170
9347
  runLog(run.runDir, { event: "common-law-half-quarantined", half: h, fail: String(clHalves[h].fail).slice(0, 140) });
9171
9348
  }
9172
9349
  }
9350
+ // The engine's own re-issue, spent at most once per run — the bound spans BOTH the half-stage
9351
+ // hook below and the merge gate's disclosure, so a run never buys the same search twice.
9352
+ let clConnotationReissueSpent = false;
9353
+ // ── THE MEANING HALF FELL SHORT BY A SEARCH OR TWO: RE-ISSUE, THEN DELIVER ───────────────────
9354
+ //
9355
+ // This is where the clearance actually died. The half's own validator refuses when a dictated
9356
+ // meaning search is missing from its receipts, and every attempt refused identically — eight of
9357
+ // them — so the run was thrown away with fifty-nine of sixty searches done and recorded.
9358
+ //
9359
+ // Quarantine above cannot help here: that is for a half that failed wholesale on transient
9360
+ // infrastructure, where re-running the half converges. This half SUCCEEDED at almost everything.
9361
+ // So the engine re-issues the missing searches itself, through the same provider, and re-judges the
9362
+ // half on its own artifact. If they landed, the half is simply valid and nothing is disclosed.
9363
+ //
9364
+ // If they did not, the half is accepted WITH THE SHORTFALL RECORDED — an honest gap per unfinished
9365
+ // search, carrying its term, which the merge gate then discloses and the coverage row reads. That is
9366
+ // the owner's ruling of 2026-09-17 and it is not the false-clean the gates guard: false-clean is
9367
+ // silence, and this says on the client's own page which search did not complete.
9368
+ if (clSplit && !clHalves[MEANING_SEAT].ok && /connotation_query_unrecorded/.test(String(clHalves[MEANING_SEAT].fail ?? ""))) {
9369
+ const h = MEANING_SEAT;
9370
+ let short = [];
9371
+ try {
9372
+ const spec = JSON.parse(readFileSync(P.gridSpecHalf(h), "utf8"));
9373
+ const halfLedger = JSON.parse(readFileSync(P.commonLawGridHalf(h), "utf8"));
9374
+ short = findDroppedConnotationQueries(spec, halfLedger);
9375
+ if (short.length) {
9376
+ runLog(run.runDir, { event: "connotation-reissue", half: h, count: short.length });
9377
+ note(`the meaning half recorded all but ${short.length} of its dictated searches — the engine re-issues `
9378
+ + `${short.length === 1 ? "it" : "them"} itself rather than asking for the sweep again`);
9379
+ clConnotationReissueSpent = true;
9380
+ const reissuer = opts.connotationReissuer ?? defaultConnotationReissuer;
9381
+ const r = await reissuer(short, { spec });
9382
+ runLog(run.runDir, { event: "connotation-reissue-result", ok: !!r?.ok,
9383
+ recovered: Array.isArray(r?.rows) ? r.rows.length : 0,
9384
+ cause: r?.ok ? null : String(r?.cause ?? "").slice(0, 160) });
9385
+ const rows = (r?.ok && Array.isArray(r.rows)) ? r.rows : [];
9386
+ halfLedger.extras = halfLedger.extras ?? {};
9387
+ halfLedger.extras.pr_risk = [...(Array.isArray(halfLedger.extras.pr_risk) ? halfLedger.extras.pr_risk : []), ...rows];
9388
+ const stillShort = findDroppedConnotationQueries(spec, halfLedger);
9389
+ if (stillShort.length) {
9390
+ halfLedger.gaps = Array.isArray(halfLedger.gaps) ? halfLedger.gaps : [];
9391
+ for (const q of stillShort)
9392
+ halfLedger.gaps.push({ term: q, platform: "connotation",
9393
+ error: "the search did not complete after a re-ask and a re-issue by the engine" });
9394
+ runLog(run.runDir, { event: "connotation-unfinished-disclosed", count: stillShort.length, half: h });
9395
+ note(`${stillShort.length} dictated meaning quer${stillShort.length === 1 ? "y" : "ies"} did not complete — `
9396
+ + "recorded as honest gaps and DISCLOSED on the coverage row; the run delivers the rest");
9397
+ }
9398
+ atomicWrite(P.commonLawGridHalf(h), JSON.stringify(halfLedger, null, 2) + "\n");
9399
+ // The half is accepted on its re-judged artifact. Same shape as quarantine above: the stage is
9400
+ // not re-run and the model is not asked again.
9401
+ clHalves[h].ok = true;
9402
+ gatherResults[clHalves[h].i] = { ...gatherResults[clHalves[h].i], ok: true, fail: null, clHalfShortfall: stillShort.length };
9403
+ }
9404
+ } catch (err) {
9405
+ runLog(run.runDir, { event: "connotation-reissue-failed", error: String(err?.message ?? err).slice(0, 160) });
9406
+ }
9407
+ }
9173
9408
  gather.forEach((g, i) => must(gatherResults[i], g.name + (g.axis ? `:${g.axis}` : "")));
9174
9409
  // winning session key per axis → a later escalation resumes the EXACT key the unit succeeded on.
9175
9410
  // (register units only — a common-law half's axis is its half id, tracked in clHalves, never here.)
@@ -9184,7 +9419,7 @@ async function pipelineInner(job, opts = {}) {
9184
9419
  // synthesis, replay) reads exactly the artifacts a single-member run would have written. Re-run after
9185
9420
  // every routed half followup (closure / frame-reopen) — the half files stay the source of truth and
9186
9421
  // the canonical pair is always derived, which also makes the merge resume-idempotent.
9187
- const mergeCommonLawArtifacts = !clSplit ? null : () => {
9422
+ const mergeCommonLawArtifacts = !clSplit ? null : ({ discloseUnfinished = false } = {}) => {
9188
9423
  // Each half's coverage truth = its plugin-written main ledger FOLDED with any plugin-written
9189
9424
  // supplementary sibling(s) the closure lane produced. Returns the batches as an ARRAY (mergeGrids'
9190
9425
  // batchesOf flattens it, cells union across all of them); a missing/corrupt main leaves its cells to
@@ -9355,6 +9590,40 @@ async function pipelineInner(job, opts = {}) {
9355
9590
  // behaviour was zero retries after full spend, with a re-run that failed identically; the new
9356
9591
  // behaviour is one retry, then the same honest death.
9357
9592
  const dropped = findDroppedConnotationQueries(fullSpec, mergedGrid);
9593
+ if (dropped.length && discloseUnfinished) {
9594
+ // ── THE LAST STEP OF THE OWNER'S RULING: a run that completed 59 of 60 dictated searches
9595
+ // DELIVERS, and says on the client's own page that it was partial and for which term.
9596
+ //
9597
+ // This is NOT the false-clean the note below guards against, and the difference is the whole
9598
+ // reason it is allowed here. False-clean is SILENCE — a hole counted as searched, nobody able to
9599
+ // see what was not done. This records each unfinished query as an honest gap carrying its own
9600
+ // term, which is the vocabulary the note above says dictated queries did not have, and that gap
9601
+ // is what the coverage row reads. A reader is told the investigation was partial and which term
9602
+ // did not complete.
9603
+ //
9604
+ // It is reached ONLY after the bounded attempts are spent: the model's single routed remedy and
9605
+ // the engine's own re-issue of the search. On the first pass this branch does not exist — the
9606
+ // gate throws exactly as before and the park loop gets its retries. Disclosure is the last
9607
+ // resort, never the first answer.
9608
+ const errored = findErroredConnotationQueries(fullSpec, mergedGrid);
9609
+ const explained = new Set(errored.map((e) => e.query));
9610
+ const gaps = Array.isArray(mergedGrid.gaps) ? mergedGrid.gaps : (mergedGrid.gaps = []);
9611
+ for (const q of dropped) {
9612
+ if (explained.has(q)) continue; // the provider already said why; its row stands
9613
+ gaps.push({ term: q, platform: "connotation",
9614
+ error: "the search did not complete after a re-ask and a re-issue by the engine" });
9615
+ }
9616
+ // THE CANONICAL LEDGER IS WRITTEN ABOVE, BEFORE THIS GATE RUNS, so mutating the merged object
9617
+ // here reaches nothing: the report reads the file. Re-write it with the gaps in place, or the
9618
+ // disclosure exists only in memory and the coverage row has nothing to read — which is the
9619
+ // silent half of the very defect this branch exists to prevent.
9620
+ atomicWrite(P.commonLawGrid, JSON.stringify(mergedGrid, null, 2) + "\n");
9621
+ runLog(run.runDir, { event: "connotation-unfinished-disclosed", count: dropped.length,
9622
+ explained: errored.length, terms: dropped.length });
9623
+ note(`${dropped.length} dictated meaning quer${dropped.length === 1 ? "y" : "ies"} did not complete after `
9624
+ + "the bounded attempts — recorded as honest gaps and DISCLOSED on the coverage row; the run delivers");
9625
+ return { mergedGrid, fullSpec, connotationUnfinished: dropped.slice() };
9626
+ }
9358
9627
  if (dropped.length) {
9359
9628
  const errored = findErroredConnotationQueries(fullSpec, mergedGrid);
9360
9629
  const allExplained = errored.length === dropped.length;
@@ -9385,6 +9654,54 @@ async function pipelineInner(job, opts = {}) {
9385
9654
  // retrying a defect buys nothing; this is one dictated repair, not a retry). Quarantined-half shapes
9386
9655
  // keep their transient park path untouched (the remedy never runs for them).
9387
9656
  let clConnotationRemedySpent = false;
9657
+ /**
9658
+ * THE ENGINE'S OWN ATTEMPT, THEN DELIVERY WITH THE GAP DISCLOSED.
9659
+ *
9660
+ * Reached only after the model has had its single routed repair turn. The engine re-issues the
9661
+ * missing searches itself through the same provider, appends whatever comes back to the half's own
9662
+ * ledger, and re-merges. If the searches are there now, the run is simply clean and nothing is
9663
+ * disclosed. If they are still missing, the run DELIVERS and the coverage row says the investigation
9664
+ * was partial and which term did not complete.
9665
+ *
9666
+ * A clearance that completed 59 of 60 dictated searches used to throw the whole report away. The
9667
+ * failure it is replacing is not a quality gate doing its job: the work was done, one search of
9668
+ * sixty did not land, and the client lost everything including the 59.
9669
+ */
9670
+ const reissueThenDisclose = async (e) => {
9671
+ if (!clConnotationReissueSpent) {
9672
+ clConnotationReissueSpent = true;
9673
+ let missing = [];
9674
+ try {
9675
+ const spec = JSON.parse(readFileSync(P.gridSpecHalf(MEANING_SEAT), "utf8"));
9676
+ const merged = JSON.parse(readFileSync(P.commonLawGrid, "utf8"));
9677
+ missing = findDroppedConnotationQueries(spec, merged);
9678
+ if (missing.length) {
9679
+ runLog(run.runDir, { event: "connotation-reissue", count: missing.length });
9680
+ note(`${missing.length} dictated meaning quer${missing.length === 1 ? "y" : "ies"} missing after the `
9681
+ + "model's one repair turn — the engine re-issues them itself through the same tool");
9682
+ const reissuer = opts.connotationReissuer ?? defaultConnotationReissuer;
9683
+ const r = await reissuer(missing, { spec });
9684
+ runLog(run.runDir, { event: "connotation-reissue-result", ok: !!r?.ok,
9685
+ recovered: Array.isArray(r?.rows) ? r.rows.length : 0,
9686
+ cause: r?.ok ? null : String(r?.cause ?? "").slice(0, 160) });
9687
+ if (r?.ok && r.rows?.length) {
9688
+ // Append into the half's OWN ledger, so the ordinary re-merge picks them up and nothing
9689
+ // downstream needs to know this call happened.
9690
+ const half = JSON.parse(readFileSync(P.commonLawGridHalf(MEANING_SEAT), "utf8"));
9691
+ half.extras = half.extras ?? {};
9692
+ half.extras.pr_risk = [...(Array.isArray(half.extras.pr_risk) ? half.extras.pr_risk : []), ...r.rows];
9693
+ atomicWrite(P.commonLawGridHalf(MEANING_SEAT), JSON.stringify(half, null, 2) + "\n");
9694
+ }
9695
+ }
9696
+ } catch (err) {
9697
+ // Never fatal: a re-issue that cannot even be attempted leaves the run exactly where it was,
9698
+ // and the disclosure below is what the ruling actually guarantees.
9699
+ runLog(run.runDir, { event: "connotation-reissue-failed", error: String(err?.message ?? err).slice(0, 160) });
9700
+ }
9701
+ try { return mergeCommonLawArtifacts(); } catch { /* still short — disclose below */ }
9702
+ }
9703
+ return mergeCommonLawArtifacts({ discloseUnfinished: true });
9704
+ };
9388
9705
  const mergeWithConnotationRemedy = !clSplit ? null : async () => {
9389
9706
  try { return mergeCommonLawArtifacts(); }
9390
9707
  catch (e) {
@@ -9408,7 +9725,21 @@ async function pipelineInner(job, opts = {}) {
9408
9725
  // the token inline must keep opening this channel.
9409
9726
  const tok = connotationRemedyToken(e);
9410
9727
  const anyQuarantined = GRID_SEATS.some((h) => clHalves[h].quarantined);
9411
- if (!tok || anyQuarantined || clConnotationRemedySpent) throw e;
9728
+ // A quarantined half keeps its transient park path: that half is re-run whole and the re-merge
9729
+ // converges, so there is nothing here to disclose yet.
9730
+ // THE DROPPED-QUERY FAILURE CARRIES NO FORM TOKEN, and matching on the form tokens alone is why
9731
+ // this channel never opened for it: `connotationRemedyToken` reads the connotation FORM
9732
+ // vocabulary, and "merged half-grids dropped N dictated connotation queries" is not in it. The
9733
+ // run therefore died at the merge with the recovery path sitting unused beside it.
9734
+ const droppedQueries = /dropped \d+ dictated connotation quer/i.test(String(e?.message ?? ""));
9735
+ if (anyQuarantined) throw e;
9736
+ if (droppedQueries) return await reissueThenDisclose(e);
9737
+ if (!tok) throw e;
9738
+ // ── ONE RE-ASK OF THE MODEL, THEN THE ENGINE TAKES OVER ──────────────────────────────────
9739
+ // The loop stops being a loop. The model gets exactly one routed repair turn; after that the
9740
+ // engine re-issues the search itself and, failing that, the run delivers with the gap disclosed.
9741
+ // Before this, a second arrival here re-threw and the clearance died after a full paid gather.
9742
+ if (clConnotationRemedySpent) return await reissueThenDisclose(e);
9412
9743
  clConnotationRemedySpent = true;
9413
9744
  runLog(run.runDir, { event: "connotation-remedy", detail: tok.slice(0, 200) });
9414
9745
  // — WHICH HALVES OWE ROWS, READ FROM THE FORM. This used to re-derive the violation set here,
@@ -9442,7 +9773,8 @@ async function pipelineInner(job, opts = {}) {
9442
9773
  const r = await stage("common-law-half", { ...ctx, axis: h }, { force: true, followup, sessionKey: clHalves[h].sessionKey, trigger: "connotation-remedy" });
9443
9774
  if (!r.ok) note(`connotation remedy (half ${h}) failed mechanically (${r.fail}) — the re-merge below decides`);
9444
9775
  });
9445
- return mergeCommonLawArtifacts();
9776
+ try { return mergeCommonLawArtifacts(); }
9777
+ catch (e2) { return await reissueThenDisclose(e2); }
9446
9778
  }
9447
9779
  };
9448
9780
  if (clSplit) await mergeWithConnotationRemedy();
@@ -12684,7 +13016,11 @@ async function pipelineInner(job, opts = {}) {
12684
13016
  reason: `synthesis_unaccounted_delivered:${duty.unaccounted.length} of ${duty.totals.owed} record(s) reached the findings surface and the delivered document accounts for none of them — neither a finding that names them nor a declination with a ground: ${sample}${duty.unaccounted.length > 4 ? " …" : ""}. The report ships with this named rather than being withheld; these records are open points a reader must weigh.`,
12685
13017
  // — the READER's sentence: what is open, in a lawyer's nouns. Counts survive;
12686
13018
  // the token, the record ids and the engine's nouns stay in `reason` and the run record.
12687
- clause: `${duty.unaccounted.length} of the ${duty.totals.owed} register records this search surfaced are neither addressed as findings nor expressly set aside in this report — they remain open points a reader must weigh`,
13019
+ // COMPOSED BY THE AUTHORITY, NOT SPELLED HERE. The same sentence has to be reachable from a
13020
+ // run that stored no clause, where these two objects are long gone and only the reason's own
13021
+ // counts survive; `terminal-clamp.mjs` composes it from the counts either way, so the fresh
13022
+ // run and the republished archive cannot drift apart.
13023
+ clause: clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
12688
13024
  });
12689
13025
  }
12690
13026
  }
@@ -12710,7 +13046,9 @@ async function pipelineInner(job, opts = {}) {
12710
13046
  // — the READER's sentence. "Floor row" is an engine noun; what the fact IS for a
12711
13047
  // lawyer: live registrations identical or near-identical to the mark that the report does not
12712
13048
  // individually address. Counts survive; token, ids and engine nouns stay in `reason`.
12713
- clause: `${block.undischarged} of the ${block.floors} live registrations identical or near-identical to the mark are not individually addressed in this report — each remains an open point a reader must weigh`,
13049
+ // Composed by the authority in `terminal-clamp.mjs` for the reason the sibling site above
13050
+ // gives: a republished pre-split run has to reach the same sentence from the counts alone.
13051
+ clause: clauseForDefect("floor_duty_undischarged", block.undischarged, block.floors),
12714
13052
  });
12715
13053
  }
12716
13054
  }
@@ -12752,7 +13090,7 @@ async function pipelineInner(job, opts = {}) {
12752
13090
  for (const c of fresh) clampClauses.push(conditionClauses[conditions.indexOf(c)] ?? c);
12753
13091
  clampReasons.push(...fresh);
12754
13092
  if (verdict === "CLEAR") {
12755
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", legalActions: conditions.length });
13093
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "forward-actions", from: "CLEAR", to: "CONDITIONAL", legalActions: conditions.length });
12756
13094
  note(`deliver-conditional floor: the opinion names ${conditions.length} forward legal action(s) a human must take (${conditions[0].slice(0, 140)}${conditions.length > 1 ? `; +${conditions.length - 1} more` : ""}) — clamping CLEAR→CONDITIONAL (spec 64: the disposition is derived from the findings' named actions).`);
12757
13095
  verdict = "CONDITIONAL";
12758
13096
  writeRunStatus(ctx, { verdict });
@@ -12791,7 +13129,7 @@ async function pipelineInner(job, opts = {}) {
12791
13129
  // The CLAMP is outside that guard on purpose: a re-ask can flip a clamped verdict back to CLEAR,
12792
13130
  // and it must meet this floor again with the reason already recorded.
12793
13131
  if (droppedConditions.length && verdict === "CLEAR") {
12794
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", actionsDropped: droppedConditions.length });
13132
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "dropped-actions", from: "CLEAR", to: "CONDITIONAL", actionsDropped: droppedConditions.length });
12795
13133
  verdict = "CONDITIONAL";
12796
13134
  writeRunStatus(ctx, { verdict });
12797
13135
  }
@@ -12882,22 +13220,49 @@ async function pipelineInner(job, opts = {}) {
12882
13220
  // could-not-examine record, and an unfinished register slice — CONDITIONAL carries
12883
13221
  // lawyer-judged/disclosed residue only.
12884
13222
  if (coverageInsufficient || frameResidual || screenGateGap || seniorGap || registerGap || deadlineGap) {
13223
+ // THESE SENTENCES REACH A CLIENT AND THEY ARE NOT OURS TO WRITE (owner, 2026-09-17). One of
13224
+ // them — the screen-gate line, which says a mark "could not be record_fetched" — carries an
13225
+ // engine identifier into the list a client reads as the conditions on their result, by a route
13226
+ // `terminalClampDecision` guards and this one does not. A reader's sentence for it was written
13227
+ // here and refused: the objection was the class, not the wording. It is on the owner's design
13228
+ // table as audit item 25, and until he rules, this stays exactly as it was rather than carrying
13229
+ // a caveat a developer composed.
12885
13230
  const reasons = [];
12886
- if (coverageInsufficient) reasons.push(`the lawyer judged a material slice not fully cleared: ${coverageJudgment.reason || "register coverage gap"}`);
12887
- if (frameGap) reasons.push("the blind frame-diff flagged a dominant-element omission the reopen pass did not close");
12888
- else if (frameDeferrals.length) reasons.push(`follow-ups left open this run: ${frameDeferrals.map((d) => plainDirective(d.directive)).slice(0, 3).join(", ")}`);
12889
- if (screenGateGap) reasons.push(`${sgUnresolved.length} in-scope mark(s) dropped on goods could not be record_fetched (unverified): ${sgUnresolved.map((g) => g.mark).join(", ")}`);
12890
- if (seniorGap) reasons.push(`the oldest registration in a verdict-driving family could not be retrieved (policy: clamp): ${(ctx.seniorRights?.rows ?? []).filter((r) => r.applicable && !r.verified).map((r) => r.mark).join(", ")}`);
13231
+ const machinery = (reason) => reasons.push(reason);
13232
+ if (coverageInsufficient) machinery(`the lawyer judged a material slice not fully cleared: ${coverageJudgment.reason || "register coverage gap"}`);
13233
+ if (frameGap) machinery("the blind frame-diff flagged a dominant-element omission the reopen pass did not close");
13234
+ else if (frameDeferrals.length) machinery(`follow-ups left open this run: ${frameDeferrals.map((d) => plainDirective(d.directive)).slice(0, 3).join(", ")}`);
13235
+ if (screenGateGap) machinery(`${sgUnresolved.length} in-scope mark(s) dropped on goods could not be record_fetched (unverified): ${sgUnresolved.map((g) => g.mark).join(", ")}`);
13236
+ if (seniorGap) machinery(`the oldest registration in a verdict-driving family could not be retrieved (policy: clamp): ${(ctx.seniorRights?.rows ?? []).filter((r) => r.applicable && !r.verified).map((r) => r.mark).join(", ")}`);
12891
13237
  if (registerGap) {
12892
- if (regGap.deferred.length) reasons.push(`register coverage deferred on ${[...new Set(regGap.deferred.map((g) => g.axis))].join(", ")} — the search did not finish and must be re-run before this can be relied on`);
12893
- if (regGap.taintAxes.length) reasons.push(`the ${regGap.taintAxes.join(", ")} register pass was cut down at the timeout wall and its self-reported coverage is unverified`);
13238
+ if (regGap.deferred.length) machinery(`register coverage deferred on ${[...new Set(regGap.deferred.map((g) => g.axis))].join(", ")} — the search did not finish and must be re-run before this can be relied on`);
13239
+ if (regGap.taintAxes.length) machinery(`the ${regGap.taintAxes.join(", ")} register pass was cut down at the timeout wall and its self-reported coverage is unverified`);
12894
13240
  // Named regressions (2026-07-22): `<MARK> (<owner> — <canonical uri>)` — a bare mark name
12895
13241
  // shipped "ION, ION, ION" (three indistinguishable strings); the identity is front-loaded
12896
13242
  // because the delivered statement truncates from the tail.
12897
- if (regGap.recallRegressions.length) reasons.push(`a prior-confirmed live conflict was neither carried nor justified this run: ${regGap.recallRegressions.slice(0, 3).map(formatRecallRegression).join(", ")}`);
13243
+ if (regGap.recallRegressions.length) machinery(`a prior-confirmed live conflict was neither carried nor justified this run: ${regGap.recallRegressions.slice(0, 3).map(formatRecallRegression).join(", ")}`);
12898
13244
  }
12899
- if (deadlineGap) reasons.push(`a recorded opposition deadline was delivered without its date: ${ctx.deadlineCarryMaterial.map((v) => `${v.mark_text ?? v.uri} (window closes ${v.opposition_end})`).slice(0, 3).join(", ")}`);
12900
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", coverageInsufficient: coverageInsufficient || undefined, frameGap: frameGap || undefined, frameDeferred: frameDeferrals.length || undefined, screenGate: screenGateGap ? sgUnresolved.length : undefined, seniorRight: seniorGap || undefined, registerGap: registerGap ? { deferred: regGap.deferred.length, taint: regGap.taintAxes.length, recall: regGap.recallRegressions.length } : undefined, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : undefined });
13245
+ if (deadlineGap) machinery(`a recorded opposition deadline was delivered without its date: ${ctx.deadlineCarryMaterial.map((v) => `${v.mark_text ?? v.uri} (window closes ${v.opposition_end})`).slice(0, 3).join(", ")}`);
13246
+ // ── THE THIRD CLAMP SITE NAMES ITSELF, AND NAMES WHICH OF ITS SEVEN INPUTS FIRED ────────────────
13247
+ //
13248
+ // All three clamp sites emitted this event under one name with the same from/to, distinguishable
13249
+ // only by which optional payload key happened to be present. Two of them fired three milliseconds
13250
+ // apart on a production run — the first without `frameDeferred`, the second with it — which reads
13251
+ // as one decision logged twice. It was two different decisions wearing one name, and the test lane
13252
+ // reasonably discounted one of them. `cause` makes the event self-describing.
13253
+ //
13254
+ // THE EVENT NAME IS DELIBERATELY NOT SPLIT. It is a true statement about the effect — the verdict
13255
+ // was clamped — and something downstream may already count clamps in aggregate. A discriminator is
13256
+ // additive; three names would not be.
13257
+ //
13258
+ // This site is itself seven causes under one name, so it also lists WHICH fired rather than
13259
+ // leaving a reader to key on field presence and guess.
13260
+ const clampInputs = Object.entries({
13261
+ coverageInsufficient, frameGap, frameDeferred: frameDeferrals.length,
13262
+ screenGate: screenGateGap ? sgUnresolved.length : 0, seniorRight: seniorGap,
13263
+ registerGap, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : 0,
13264
+ }).filter(([, v]) => Boolean(v)).map(([k]) => k);
13265
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "coverage", causes: clampInputs, from: "CLEAR", to: "CONDITIONAL", coverageInsufficient: coverageInsufficient || undefined, frameGap: frameGap || undefined, frameDeferred: frameDeferrals.length || undefined, screenGate: screenGateGap ? sgUnresolved.length : undefined, seniorRight: seniorGap || undefined, registerGap: registerGap ? { deferred: regGap.deferred.length, taint: regGap.taintAxes.length, recall: regGap.recallRegressions.length } : undefined, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : undefined });
12901
13266
  note(`deliver-conditional floor: ${reasons.join("; ")} — clamping CLEAR→CONDITIONAL so the delivered status carries the gap (never withheld, never halted).`);
12902
13267
  verdict = "CONDITIONAL";
12903
13268
  // APPEND (dedup by exact text) — the legalActions arm may already have recorded conditions,