clearotron 0.3.3 → 0.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/INSTALL.md +1 -14
- package/bin/brandowner.mjs +5 -5
- package/bin/connect.mjs +4 -4
- package/bin/onboard.mjs +5 -7
- package/bin/start.mjs +20 -5
- package/bin/update.mjs +6 -1
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +2 -6
- package/driver/CHANGELOG.md +28 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/band-shape.mjs +8 -8
- package/driver/blind-frame-model.mjs +1 -1
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/commonlaw-carry.mjs +2 -2
- package/driver/company-bundle.mjs +11 -18
- package/driver/connotation-search.mjs +4 -4
- package/driver/contract-e3-backlog.mjs +3 -3
- package/driver/declination-call.mjs +1 -1
- package/driver/declination-tool.mjs +1 -1
- package/driver/dev-portal.mjs +1 -1
- package/driver/door-call-verdict.mjs +27 -0
- package/driver/driver.config.mjs +17 -11
- package/driver/e2e/README.md +1 -1
- package/driver/engine/mcp/clarivate-server.mjs +2 -1
- package/driver/engine/mcp/corsearch-server.mjs +1 -0
- package/driver/engine/mcp/euipo-server.mjs +1 -0
- package/driver/engine/mcp/free-tier-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/mcp/perplexity-server.mjs +1 -1
- package/driver/engine/mcp/recording-server.mjs +1 -1
- package/driver/engine/mcp/signa-server.mjs +1 -0
- package/driver/engine/mcp/supplemental.mjs +1 -1
- package/driver/engine/mcp/uspto-local-server.mjs +1 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/feedback-issues.mjs +1 -1
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +3 -3
- package/driver/flag-snapshot.mjs +1 -1
- package/driver/floor-duty.mjs +2 -2
- package/driver/form-neighbourhood.mjs +47 -15
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/gateway.mjs +5 -5
- package/driver/jx-lanes.mjs +1 -1
- package/driver/known-conflicts.mjs +18 -0
- package/driver/log.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +129 -95
- package/driver/pipeline.mjs +69 -32
- package/driver/placement-carry.mjs +2 -2
- package/driver/placement-form.mjs +1 -1
- package/driver/portal-mcp-client.mjs +1 -1
- package/driver/portal-request-origin.mjs +79 -0
- package/driver/portal-service.mjs +45 -17
- package/driver/predelivery-lint.mjs +10 -10
- package/driver/profile-page.html +9 -13
- package/driver/profile-service.mjs +25 -13
- package/driver/profiles.mjs +17 -4
- package/driver/progress.mjs +1 -1
- package/driver/provider-usage.mjs +24 -1
- package/driver/publish/index.mjs +17 -5
- package/driver/publish/knockout.mjs +3 -2
- package/driver/publish/render-knockout.mjs +1 -1
- package/driver/publish/render.mjs +14 -3
- package/driver/publish/search-depth.mjs +4 -2
- package/driver/recall-reconciliation.mjs +1 -1
- package/driver/record-carry.mjs +6 -6
- package/driver/recording-agreement.mjs +2 -2
- package/driver/reference-score.mjs +27 -27
- package/driver/register-count.mjs +56 -1
- package/driver/register-digest-record.mjs +1 -1
- package/driver/register-plan.mjs +5 -5
- package/driver/register-records.mjs +10 -1
- package/driver/registry-fidelity.mjs +4 -4
- package/driver/repair-composers.mjs +6 -6
- package/driver/run-economics.mjs +8 -19
- package/driver/screen-gate.mjs +1 -1
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/perplexity-prompts.md +4 -4
- package/driver/skills/clearance-register/digest.md +1 -1
- package/driver/skills/clearance-register/unit.md +1 -1
- package/driver/skills/clearance-search/report-prose.md +5 -5
- package/driver/skills/clearance-search/synthesis-rules.md +4 -4
- package/driver/skills/clearance-variants/SKILL.md +2 -2
- package/driver/skills/clearance-variants/transliteration-scripts.md +1 -1
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/skills/knockout-assess/SKILL.md +9 -9
- package/driver/skills/matter-frame/watchlist-reference.md +1 -1
- package/driver/skills/narrative-refutation/SKILL.md +2 -2
- package/driver/skills/placement-inquiry/SKILL.md +1 -1
- package/driver/stage-context.mjs +4 -4
- package/driver/stages.mjs +23 -15
- package/driver/suite-census.json +138 -42
- package/driver/systemd/clearotron-client-mcp.service +24 -0
- package/driver/systemd/clearotron-mcp-face.service +24 -0
- package/driver/systemd/clearotron-portal.service +24 -0
- package/driver/systemd/clearotron-worker.service +24 -0
- package/driver/tokens.mjs +26 -17
- package/driver/turnaround-bands.mjs +1 -1
- package/driver/unit-inventory.mjs +3 -3
- package/driver/variant-manifest-model.mjs +1 -1
- package/driver/verify.mjs +2 -2
- package/driver/whatif-memo-run.mjs +1 -1
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/lib/audit.mjs +9 -2
- package/mcp-server/lib/http-handler.mjs +7 -3
- package/mcp-server/mint-token.mjs +8 -6
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +11 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-GBbbyQxc.js → index-D_O_55vK.js} +59 -9
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -1
- package/providers/_shared/answer-memory.mjs +199 -0
- package/providers/_shared/ledger-path.mjs +1 -1
- package/providers/_shared/ledger.mjs +47 -5
- package/providers/_shared/script-form.mjs +24 -5
- package/providers/_shared/term-shape.mjs +5 -5
- package/providers/clarivate/src/capabilities.js +11 -0
- package/providers/clarivate/src/core.js +140 -13
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/capabilities.js +24 -0
- package/providers/signa/src/core.js +66 -0
- package/scripts/deprecate-below.mjs +114 -2
- package/scripts/freeze-example-run.mjs +1 -1
- package/scripts/live-surface-check.mjs +11 -2
- package/scripts/release-entry-catch-up.mjs +211 -0
- package/scripts/release-note-required.mjs +102 -6
- package/scripts/release-rehearsal-version.mjs +60 -0
- package/scripts/release-sbom.mjs +104 -0
- package/scripts/release-visible-check.mjs +7 -5
- package/scripts/score.mjs +3 -3
- package/shared/brand.mjs +1 -1
- package/shared/client-door.mjs +15 -8
- package/shared/driver-dir.mjs +20 -9
- package/shared/names-in-force.mjs +2 -0
- package/shared/scope.mjs +25 -10
- package/shared/store-in-repo.mjs +38 -17
|
@@ -54,6 +54,7 @@ import { capabilitiesFor } from "./register-capabilities.mjs";
|
|
|
54
54
|
// — the ONE binding of the office split to this box's env, shared with the plan lane.
|
|
55
55
|
import { registerUnavailableOffices } from "./register-unreachable.mjs";
|
|
56
56
|
import { runRecordLogPath } from "../providers/_shared/ledger-path.mjs"; // — this run's record log
|
|
57
|
+
import { beginAnswerMemory, endAnswerMemory } from "../providers/_shared/answer-memory.mjs";
|
|
57
58
|
import { validators as koValidators, validateMergedFindings, worstBand, registerSurfacedFilings, raterCaveats, SURVIVOR_BOUNDARY_RE } from "./verify-knockout.mjs";
|
|
58
59
|
import { reviewAbout, reviewEvidence, reviewEvidenceLines, applyKnockoutReview, knockoutReviewFile } from "./knockout-review-record.mjs";
|
|
59
60
|
import { stripNextStepSections } from "./knockout-next-step.mjs";
|
|
@@ -646,6 +647,12 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
646
647
|
...(ctx.stateReset ? { __stateReset: true } : {}),
|
|
647
648
|
});
|
|
648
649
|
rollupStatus(run.studioRoot);
|
|
650
|
+
// THE SIGNA RUN MEMORY'S ATTEMPT (providers/_shared/answer-memory.mjs): whatever an earlier attempt
|
|
651
|
+
// held is dropped, so a re-run asks the register again, and the mode is fixed for this attempt.
|
|
652
|
+
ctx.answerMemory = beginAnswerMemory(run.runDir, REGISTER_PROVIDER);
|
|
653
|
+
if (ctx.answerMemory.applies)
|
|
654
|
+
runLog(run.runDir, { event: "answer-memory", mode: ctx.answerMemory.mode, ...(ctx.answerMemory.unknown ? { unrecognised: ctx.answerMemory.unknown } : {}) });
|
|
655
|
+
if (ctx.answerMemory.unknown) note(`answer memory: ${ctx.answerMemory.switch}="${ctx.answerMemory.unknown}" is not off, watch or on, so this register's default applies (${ctx.answerMemory.mode})`);
|
|
649
656
|
note(`=== KNOCKOUT batch (${policy.stageLabel}) — ${markNames.length} mark(s), framework ${ctx.framework.framework_key} ===`);
|
|
650
657
|
|
|
651
658
|
// 1 — frame
|
|
@@ -661,6 +668,125 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
661
668
|
if (probeWanted) {
|
|
662
669
|
koStep(ctx, KO_STEP_REGISTER_COUNT);
|
|
663
670
|
ensureDriverDir(run.runDir);
|
|
671
|
+
// The listing lane, as a step the count lane can run before or after. It returns the listing it
|
|
672
|
+
// wrote, or null when it was refused or failed; either way it has already recorded why.
|
|
673
|
+
const listFilings = async () => {
|
|
674
|
+
let listedDoc = null;
|
|
675
|
+
// ── 1.6 — THE FILINGS BEHIND THE NARROW COUNTS ( part 5) ─────────────────────────────────
|
|
676
|
+
//
|
|
677
|
+
// NEVER TERMINAL, and that is the difference between this lane and the count lane. The counts
|
|
678
|
+
// ARE the product — none of them means no product, so the run dies. The listing EXPLAINS the
|
|
679
|
+
// counts: a batch that got its numbers and could not fetch the faces behind them is a smaller
|
|
680
|
+
// deliverable, not a wrong one, and killing the run would throw away the part that was paid for.
|
|
681
|
+
// Every failure lands as a recorded reason on the surfaces instead.
|
|
682
|
+
const recExec = resolveRecordExecutor({
|
|
683
|
+
lister: opts?.recordLister ?? null, adapter: activeProvider(),
|
|
684
|
+
agentId: agent, sessionKey: `clearance-${run.slug}-${run.codename}`,
|
|
685
|
+
// — this run's record log, not the box's. The knockout lane never reads the record bodies
|
|
686
|
+
// its batch-screen hydration writes; before this they were pure growth on a global file.
|
|
687
|
+
recordLog: runRecordLogPath(run.runDir),
|
|
688
|
+
// The $0 guarantee, threaded rather than re-derived: if the COUNTS ran on fixtures, this lane
|
|
689
|
+
// may not reach a live register. resolveRecordExecutor refuses instead of falling through.
|
|
690
|
+
offline: countExec.source.startsWith("fixtures:"),
|
|
691
|
+
fixtureDir: job?.registerFixtures?.records ?? null,
|
|
692
|
+
});
|
|
693
|
+
const recRefusal = countExec.source.startsWith("fixtures:") && recExec.source === "none"
|
|
694
|
+
// NAMES WHAT THE READER CAN ACT ON. This said `CLEAROTRON_KNOCKOUT_RECORD_FIXTURES` — a variable that
|
|
695
|
+
// no longer exists — so the sentence now names the job field that does.
|
|
696
|
+
? "this run counted from fixtures and its job declares no `registerFixtures.records` directory, so the "
|
|
697
|
+
+ "filings behind the counts were not listed — a fixture run never reaches a live register."
|
|
698
|
+
: recordsPreflight({ capabilities: ctx.registerCaps, hasAdapter: recExec.source !== "none" });
|
|
699
|
+
if (recRefusal) {
|
|
700
|
+
// A structural refusal is RECORDED, not swallowed. Without a sidecar the surfaces cannot tell
|
|
701
|
+
// "this provider does not list filings" from "nobody asked", and the second reads as an
|
|
702
|
+
// omission — the same silence part 2 closed one section up.
|
|
703
|
+
atomicWrite(K.registerRecords, JSON.stringify({
|
|
704
|
+
schema: 1, provider: REGISTER_PROVIDER, providerLabel: ctx.registerCaps?.label ?? REGISTER_PROVIDER,
|
|
705
|
+
takenAt: new Date().toISOString(), unavailable: recRefusal, marks: [],
|
|
706
|
+
}, null, 2) + "\n");
|
|
707
|
+
note(`register filings not listed: ${recRefusal}`);
|
|
708
|
+
runLog(run.runDir, { event: "knockout-register-records-refused", provider: REGISTER_PROVIDER, reason: recRefusal });
|
|
709
|
+
} else {
|
|
710
|
+
try {
|
|
711
|
+
const recDoc = await listRegisterRecords({
|
|
712
|
+
marks: markRows.map((m) => ({ name: String(m.name), classes: Array.isArray(m.classes) ? m.classes : null })),
|
|
713
|
+
classes: job.classes ?? null, jurisdictions: job.jurisdictions ?? null,
|
|
714
|
+
provider: REGISTER_PROVIDER, capabilities: ctx.registerCaps,
|
|
715
|
+
lister: recExec.list, ledgerPath: K.countLedger,
|
|
716
|
+
// — the SAME split the counts used. Re-deriving it here would let the two halves of one
|
|
717
|
+
// report narrow to different scopes; `recExec` can also be a fixture lister while the counts
|
|
718
|
+
// ran live, and the offices a fixture holds are not this box's.
|
|
719
|
+
unreachable: recExec.source === "provider" ? (ctx.registerUnreachable ?? []) : [],
|
|
720
|
+
concurrency: 3, // step 3 — the same constant as the sibling call above
|
|
721
|
+
...(process.env.CLEAROTRON_KNOCKOUT_RECORD_CAP ? { cap: Number(process.env.CLEAROTRON_KNOCKOUT_RECORD_CAP) } : {}),
|
|
722
|
+
...(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP ? { variantCap: Number(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP) } : {}),
|
|
723
|
+
});
|
|
724
|
+
atomicWrite(K.registerRecords, JSON.stringify(recDoc, null, 2) + "\n");
|
|
725
|
+
listedDoc = recDoc;
|
|
726
|
+
runLog(run.runDir, { event: "knockout-register-records", provider: REGISTER_PROVIDER, executor: recExec.source,
|
|
727
|
+
marks: recDoc.marks.length, listed: listedMarks(recDoc), records: recDoc.marks.reduce((n, m) => n + m.records.length, 0) });
|
|
728
|
+
} catch (e) {
|
|
729
|
+
// The lane does not throw per mark, so reaching here means something structural broke. It
|
|
730
|
+
// still must not take the counts down with it — but it must not vanish either.
|
|
731
|
+
note(`register filings listing failed (non-fatal): ${e.message}`);
|
|
732
|
+
runLog(run.runDir, { event: "knockout-register-records-failed", provider: REGISTER_PROVIDER, cause: String(e?.message ?? e).slice(0, 300) });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return listedDoc;
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// ── THE OWNER LOOKUP, ONCE THE LISTING HAS NAMED THE OWNER AND A COUNT HAS LANDED ──────────────
|
|
739
|
+
//
|
|
740
|
+
// Run after the count gate below, whichever lane went first. A batch where not one mark got a
|
|
741
|
+
// number stops at that gate; on a register that lists first the lookups used to run before it,
|
|
742
|
+
// research calls the batch paid for and then threw away.
|
|
743
|
+
const checkOwners = async (recDoc) => {
|
|
744
|
+
if (!recDoc) return;
|
|
745
|
+
// On the run that produced the issue, the owner's name was on disk 48 seconds before the sweep
|
|
746
|
+
// started and no pass ever searched it: every sweep keys on the TERM, and nothing re-swept on an
|
|
747
|
+
// OWNER once the register named one. The assessment then inferred what the owner sold from the
|
|
748
|
+
// owner's name, and deferred the real answer to a document the run did not hold.
|
|
749
|
+
//
|
|
750
|
+
// Bounded to promoted filings and deduplicated per owner — on the issue's own run that is ONE
|
|
751
|
+
// extra query. Never throws: a failure produces rows saying the lookup did not answer, and the
|
|
752
|
+
// run publishes (ruling A, 2026-09-07).
|
|
753
|
+
try {
|
|
754
|
+
const owed = ownersOwedACheck(recDoc);
|
|
755
|
+
if (owed.length) {
|
|
756
|
+
const checks = await runOwnerChecks({
|
|
757
|
+
owners: owed, exec: sweep.exec, runDir: run.runDir, ledgerPath: K.ownerCheckLedger,
|
|
758
|
+
preset: process.env.CLEAROTRON_KNOCKOUT_PRESET || "pro-search",
|
|
759
|
+
});
|
|
760
|
+
atomicWrite(K.ownerChecks, JSON.stringify({ schema: 1, checks }, null, 2) + "\n");
|
|
761
|
+
runLog(run.runDir, {
|
|
762
|
+
event: "knockout-owner-checks", owners: owed.length,
|
|
763
|
+
answered: checks.filter((c) => c.ok).length,
|
|
764
|
+
// The count is logged because the ruling is about COST: one promoted filing on the
|
|
765
|
+
// issue's run means one query. A number climbing here is the bound having widened.
|
|
766
|
+
unanswered: checks.filter((c) => !c.ok).length,
|
|
767
|
+
});
|
|
768
|
+
} else {
|
|
769
|
+
runLog(run.runDir, { event: "knockout-owner-checks", owners: 0, answered: 0, unanswered: 0,
|
|
770
|
+
detail: "no promoted filing names a proprietor to search" });
|
|
771
|
+
}
|
|
772
|
+
} catch (e) {
|
|
773
|
+
// Structural only — runOwnerChecks itself never throws. Non-fatal by the same rule as the
|
|
774
|
+
// listing above: a report is never withheld for a check that could not run.
|
|
775
|
+
note(`owner use-check failed (non-fatal): ${e.message}`);
|
|
776
|
+
runLog(run.runDir, { event: "knockout-owner-checks-failed", cause: String(e?.message ?? e).slice(0, 300) });
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// ── THE LISTING FIRST, ON A REGISTER WHOSE LISTING HOLDS THE COUNT ──────────────────────────────
|
|
781
|
+
//
|
|
782
|
+
// The identical and close columns are the exact predicate over a term, and the listing asks exactly
|
|
783
|
+
// that. On a register that declares its listing carries the register's own total for that question
|
|
784
|
+
// (`listingAnswersCount`), the listing runs first and the count lane takes those two columns from it
|
|
785
|
+
// (register-count.mjs listingAnswers) instead of paying for them again. It still asks what the
|
|
786
|
+
// listing never asks — `containing` — and any term the listing did not answer. Every other register
|
|
787
|
+
// counts first and lists second, as it always has.
|
|
788
|
+
const listFirst = ctx.registerCaps?.listingAnswersCount === true;
|
|
789
|
+
const listedFirst = listFirst ? await listFilings() : null;
|
|
664
790
|
// resume: a settled prior sidecar is reused cell by cell, so a re-run never re-bills a count that
|
|
665
791
|
// already landed — including across a build that ADDED a predicate, where only the new column is
|
|
666
792
|
// bought (register-count.mjs cellSettled).
|
|
@@ -683,6 +809,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
683
809
|
// to VARIANT_CAP, so this knob can only ever narrow. Unset ⇒ the code cap, which is the answer
|
|
684
810
|
// for every deployment that has not thought about it.
|
|
685
811
|
...(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP ? { variantCap: Number(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP) } : {}),
|
|
812
|
+
...(listedFirst ? { listed: listedFirst } : {}),
|
|
686
813
|
});
|
|
687
814
|
const counted = countedMarks(doc);
|
|
688
815
|
// Not one mark got a number. The count IS this product — a batch published now would be a plain
|
|
@@ -698,101 +825,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
698
825
|
runLog(run.runDir, { event: "knockout-register-counts", provider: REGISTER_PROVIDER, executor: countExec.source, marks: doc.marks.length, counted, regions: doc.scope.regions ?? "worldwide" });
|
|
699
826
|
if (counted < doc.marks.length) note(`register counts: ${doc.marks.length - counted}/${doc.marks.length} mark(s) unavailable — the batch continues (they publish as "not available", never as zero)`);
|
|
700
827
|
|
|
701
|
-
|
|
702
|
-
//
|
|
703
|
-
// NEVER TERMINAL, and that is the difference between this lane and the one above it. The counts
|
|
704
|
-
// ARE the product — none of them means no product, so the run dies. The listing EXPLAINS the
|
|
705
|
-
// counts: a batch that got its numbers and could not fetch the faces behind them is a smaller
|
|
706
|
-
// deliverable, not a wrong one, and killing the run would throw away the part that was paid for.
|
|
707
|
-
// Every failure lands as a recorded reason on the surfaces instead.
|
|
708
|
-
const recExec = resolveRecordExecutor({
|
|
709
|
-
lister: opts?.recordLister ?? null, adapter: activeProvider(),
|
|
710
|
-
agentId: agent, sessionKey: `clearance-${run.slug}-${run.codename}`,
|
|
711
|
-
// — this run's record log, not the box's. The knockout lane never reads the record bodies
|
|
712
|
-
// its batch-screen hydration writes; before this they were pure growth on a global file.
|
|
713
|
-
recordLog: runRecordLogPath(run.runDir),
|
|
714
|
-
// The $0 guarantee, threaded rather than re-derived: if the COUNTS ran on fixtures, this lane
|
|
715
|
-
// may not reach a live register. resolveRecordExecutor refuses instead of falling through.
|
|
716
|
-
offline: countExec.source.startsWith("fixtures:"),
|
|
717
|
-
fixtureDir: job?.registerFixtures?.records ?? null,
|
|
718
|
-
});
|
|
719
|
-
const recRefusal = countExec.source.startsWith("fixtures:") && recExec.source === "none"
|
|
720
|
-
// NAMES WHAT THE READER CAN ACT ON. This said `CLEAROTRON_KNOCKOUT_RECORD_FIXTURES` — a variable that
|
|
721
|
-
// no longer exists — so the sentence now names the job field that does.
|
|
722
|
-
? "this run counted from fixtures and its job declares no `registerFixtures.records` directory, so the "
|
|
723
|
-
+ "filings behind the counts were not listed — a fixture run never reaches a live register."
|
|
724
|
-
: recordsPreflight({ capabilities: ctx.registerCaps, hasAdapter: recExec.source !== "none" });
|
|
725
|
-
if (recRefusal) {
|
|
726
|
-
// A structural refusal is RECORDED, not swallowed. Without a sidecar the surfaces cannot tell
|
|
727
|
-
// "this provider does not list filings" from "nobody asked", and the second reads as an
|
|
728
|
-
// omission — the same silence part 2 closed one section up.
|
|
729
|
-
atomicWrite(K.registerRecords, JSON.stringify({
|
|
730
|
-
schema: 1, provider: REGISTER_PROVIDER, providerLabel: ctx.registerCaps?.label ?? REGISTER_PROVIDER,
|
|
731
|
-
takenAt: new Date().toISOString(), unavailable: recRefusal, marks: [],
|
|
732
|
-
}, null, 2) + "\n");
|
|
733
|
-
note(`register filings not listed: ${recRefusal}`);
|
|
734
|
-
runLog(run.runDir, { event: "knockout-register-records-refused", provider: REGISTER_PROVIDER, reason: recRefusal });
|
|
735
|
-
} else {
|
|
736
|
-
try {
|
|
737
|
-
const recDoc = await listRegisterRecords({
|
|
738
|
-
marks: markRows.map((m) => ({ name: String(m.name), classes: Array.isArray(m.classes) ? m.classes : null })),
|
|
739
|
-
classes: job.classes ?? null, jurisdictions: job.jurisdictions ?? null,
|
|
740
|
-
provider: REGISTER_PROVIDER, capabilities: ctx.registerCaps,
|
|
741
|
-
lister: recExec.list, ledgerPath: K.countLedger,
|
|
742
|
-
// — the SAME split the counts used. Re-deriving it here would let the two halves of one
|
|
743
|
-
// report narrow to different scopes; `recExec` can also be a fixture lister while the counts
|
|
744
|
-
// ran live, and the offices a fixture holds are not this box's.
|
|
745
|
-
unreachable: recExec.source === "provider" ? (ctx.registerUnreachable ?? []) : [],
|
|
746
|
-
concurrency: 3, // step 3 — the same constant as the sibling call above
|
|
747
|
-
...(process.env.CLEAROTRON_KNOCKOUT_RECORD_CAP ? { cap: Number(process.env.CLEAROTRON_KNOCKOUT_RECORD_CAP) } : {}),
|
|
748
|
-
...(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP ? { variantCap: Number(process.env.CLEAROTRON_KNOCKOUT_VARIANT_CAP) } : {}),
|
|
749
|
-
});
|
|
750
|
-
atomicWrite(K.registerRecords, JSON.stringify(recDoc, null, 2) + "\n");
|
|
751
|
-
runLog(run.runDir, { event: "knockout-register-records", provider: REGISTER_PROVIDER, executor: recExec.source,
|
|
752
|
-
marks: recDoc.marks.length, listed: listedMarks(recDoc), records: recDoc.marks.reduce((n, m) => n + m.records.length, 0) });
|
|
753
|
-
|
|
754
|
-
// ── THE OWNER LOOKUP, HERE BECAUSE HERE IS WHERE THE OWNER BECOMES KNOWN ──────────────────────
|
|
755
|
-
//
|
|
756
|
-
// On the run that produced the issue, the owner's name was on disk 48 seconds before the sweep
|
|
757
|
-
// started and no pass ever searched it: every sweep keys on the TERM, and nothing re-swept on an
|
|
758
|
-
// OWNER once the register named one. The assessment then inferred what the owner sold from the
|
|
759
|
-
// owner's name, and deferred the real answer to a document the run did not hold.
|
|
760
|
-
//
|
|
761
|
-
// Bounded to promoted filings and deduplicated per owner — on the issue's own run that is ONE
|
|
762
|
-
// extra query. Never throws: a failure produces rows saying the lookup did not answer, and the
|
|
763
|
-
// run publishes (ruling A, 2026-09-07).
|
|
764
|
-
try {
|
|
765
|
-
const owed = ownersOwedACheck(recDoc);
|
|
766
|
-
if (owed.length) {
|
|
767
|
-
const checks = await runOwnerChecks({
|
|
768
|
-
owners: owed, exec: sweep.exec, runDir: run.runDir, ledgerPath: K.ownerCheckLedger,
|
|
769
|
-
preset: process.env.CLEAROTRON_KNOCKOUT_PRESET || "pro-search",
|
|
770
|
-
});
|
|
771
|
-
atomicWrite(K.ownerChecks, JSON.stringify({ schema: 1, checks }, null, 2) + "\n");
|
|
772
|
-
runLog(run.runDir, {
|
|
773
|
-
event: "knockout-owner-checks", owners: owed.length,
|
|
774
|
-
answered: checks.filter((c) => c.ok).length,
|
|
775
|
-
// The count is logged because the ruling is about COST: one promoted filing on the
|
|
776
|
-
// issue's run means one query. A number climbing here is the bound having widened.
|
|
777
|
-
unanswered: checks.filter((c) => !c.ok).length,
|
|
778
|
-
});
|
|
779
|
-
} else {
|
|
780
|
-
runLog(run.runDir, { event: "knockout-owner-checks", owners: 0, answered: 0, unanswered: 0,
|
|
781
|
-
detail: "no promoted filing names a proprietor to search" });
|
|
782
|
-
}
|
|
783
|
-
} catch (e) {
|
|
784
|
-
// Structural only — runOwnerChecks itself never throws. Non-fatal by the same rule as the
|
|
785
|
-
// listing above: a report is never withheld for a check that could not run.
|
|
786
|
-
note(`owner use-check failed (non-fatal): ${e.message}`);
|
|
787
|
-
runLog(run.runDir, { event: "knockout-owner-checks-failed", cause: String(e?.message ?? e).slice(0, 300) });
|
|
788
|
-
}
|
|
789
|
-
} catch (e) {
|
|
790
|
-
// The lane does not throw per mark, so reaching here means something structural broke. It
|
|
791
|
-
// still must not take the counts down with it — but it must not vanish either.
|
|
792
|
-
note(`register filings listing failed (non-fatal): ${e.message}`);
|
|
793
|
-
runLog(run.runDir, { event: "knockout-register-records-failed", provider: REGISTER_PROVIDER, cause: String(e?.message ?? e).slice(0, 300) });
|
|
794
|
-
}
|
|
795
|
-
}
|
|
828
|
+
await checkOwners(listFirst ? listedFirst : await listFilings());
|
|
796
829
|
}
|
|
797
830
|
|
|
798
831
|
// 2 — the sweep: ONE broad code-side research call per mark, receipted, per-mark degrade
|
|
@@ -1098,6 +1131,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
1098
1131
|
// returns. Same seam, same best-effort contract, no lane-specific exception to write down.
|
|
1099
1132
|
const stamp = writeSettleStamp(published.poolRunDir, { state: "delivered", tier: overall, deliveredAt, runId: published.runId ?? run.runId, lane: "knockout" });
|
|
1100
1133
|
if (!stamp.written) note(`delivery: settle stamp not written (${stamp.reason})`);
|
|
1134
|
+
endAnswerMemory(run.runDir); // the held answers do not travel into the archive
|
|
1101
1135
|
const archived = archive(run);
|
|
1102
1136
|
rollupStatus(run.studioRoot);
|
|
1103
1137
|
sentinel(archived ?? run.runDir, ".delivered", { verdict: overall, url: published.url, reports: published.reports.map((r) => ({ mark: r.mark, url: r.url })), notified: "pending", sendPending: true, archived, lane: "knockout" });
|
package/driver/pipeline.mjs
CHANGED
|
@@ -76,7 +76,7 @@ import { armCoverageForm, coverageFormInput, coverageFormPaths, coverageFormStam
|
|
|
76
76
|
import { unionPlacementForm } from "./placement-union.mjs";
|
|
77
77
|
import { readPlacementForm, readPlacementFormInput, writePlacementForm } from "./placement-form-io.mjs";
|
|
78
78
|
import { dictatedPaths, findStrayArtifacts, treeSnapshot, findStrayInTree, matterSiblings, findStrayMatterSiblings } from "./stray-artifacts.mjs"; // — a run dir holds no document no stage dictated; — nor does the doctrine tree
|
|
79
|
-
import { resolveProfile, resolveEffectiveProfile, derivedFloor, derivedBatchSize, applicantMatchesProfile, NEUTRAL_DELIVERY, deliveryForRun, recipeProseGuard, withRunPlatforms, profileStoreResolution } from "./profiles.mjs"; // adds profileStoreResolution — the CONFIG store's identity, beside the doctrine tree's
|
|
79
|
+
import { resolveProfile, resolveEffectiveProfile, derivedFloor, derivedBatchSize, gridBatchFor, applicantMatchesProfile, NEUTRAL_DELIVERY, deliveryForRun, recipeProseGuard, withRunPlatforms, profileStoreResolution } from "./profiles.mjs"; // adds profileStoreResolution — the CONFIG store's identity, beside the doctrine tree's
|
|
80
80
|
import { resolveSearchPolicy, gateResolvedPolicy, loadRecipes, policyFor, isRegisterOnly, reportIdentityFor, depthFor } from "./search-policy.mjs";
|
|
81
81
|
import { profileOrdinals } from "./profile-selection.mjs"; // lever 3 — driver selection
|
|
82
82
|
// THE OFFERING'S own sentence about where the native-language investigation can be bought. It reaches a
|
|
@@ -138,6 +138,7 @@ import { tallyRegisterCalls, DEFAULT_LEDGER_PATH, fetchedRecordUris, countLaneCa
|
|
|
138
138
|
// this path, because this process runs several runs' pipelines at once and a global address would file
|
|
139
139
|
// one run's evidence under another.
|
|
140
140
|
import { runRecordLogPath } from "../providers/_shared/ledger-path.mjs";
|
|
141
|
+
import { beginAnswerMemory, endAnswerMemory } from "../providers/_shared/answer-memory.mjs";
|
|
141
142
|
import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause } from "./screen-gate.mjs";
|
|
142
143
|
import { emptyQueue, coerceQueue, mintItem, pendingItems, markFlushed, receiptKeyFor,
|
|
143
144
|
buildFlushFollowup, runPostFlushGateRepair } from "./digest-queue.mjs"; // (t1cd) — the digest-trigger funnel
|
|
@@ -149,7 +150,7 @@ import { stampTokenRollup } from "./tokens.mjs";
|
|
|
149
150
|
import { recordRunConsumption } from "./consumption-ledger.mjs";
|
|
150
151
|
import { quoteForJob, reconcileTurnaround } from "./run-quote.mjs";
|
|
151
152
|
import { assembleRunRecords, readRecordArtifacts, findRegistryArithmeticIssues, findRegistryViolations, applyRegistryCorrections, extractEnforcerSignals, collectOppositionDeadlines, normalizeRecordUri } from "./registry-fidelity.mjs";
|
|
152
|
-
import { readKnownConflictsFor, writeKnownConflictsFor, markKey, acceptedConflicts } from "./known-conflicts.mjs"; // spec 64 — the workspace-level per-mark recall store
|
|
153
|
+
import { readKnownConflictsFor, writeKnownConflictsFor, markKey, acceptedConflicts, recallReceiptForOwnCompany } from "./known-conflicts.mjs"; // spec 64 — the workspace-level per-mark recall store
|
|
153
154
|
import { runLint, flagLines, properNameCandidates } from "./predelivery-lint.mjs";
|
|
154
155
|
import { parsePlacementsJson } from "./placement-model.mjs"; // B2 — the structured tier mirror; the lint reads it only to flag an EMPTY one
|
|
155
156
|
import { readAnchors } from "./anchor-reader.mjs";
|
|
@@ -979,7 +980,12 @@ function deriveGridSpec(ctx) {
|
|
|
979
980
|
terms: null,
|
|
980
981
|
spec_inputs: { registerOnly: Boolean(ctx.registerOnly), gridVariants: ctx.gridVariants?.length ?? 0, profilePlatforms: ctx.profile?.platforms?.length ?? 0 },
|
|
981
982
|
};
|
|
982
|
-
|
|
983
|
+
// AN EMPTY MARKETPLACE LIST STILL WRITES A GRID. A company may pick no marketplaces (the owner's ruling of
|
|
984
|
+
// 2026-09-23), and the general-web cell and the meaning sweep ride in this spec: gating it on a non-empty
|
|
985
|
+
// list switched both off with the stores, and the downgrade clamp then read the missing spec as a failed
|
|
986
|
+
// sweep. The spec below is then the web cell plus whatever channels the matter frame names. Only a profile
|
|
987
|
+
// with no list at all (legacy) takes the spec-less path.
|
|
988
|
+
if (!ctx.registerOnly && ctx.gridVariants?.length && Array.isArray(ctx.profile?.platforms)) {
|
|
983
989
|
const gridSpecPath = P.gridSpec;
|
|
984
990
|
// #5 — required channels: a NAMED profile's curated platforms are authoritative. The GENERIC fallback
|
|
985
991
|
// derives the channels from the MATTER FRAME's industry/goods reasoning (its "Search channels:" line) so
|
|
@@ -1041,7 +1047,8 @@ function deriveGridSpec(ctx) {
|
|
|
1041
1047
|
terms: ctx.gridVariants,
|
|
1042
1048
|
platforms: [...channels, "web"], // the dictated channels + the general-web cell
|
|
1043
1049
|
output_path: P.commonLawGrid,
|
|
1044
|
-
|
|
1050
|
+
// SIZED BY THE CELLS THIS GRID RUNS, never larger than the profile's own figure (gridBatchFor).
|
|
1051
|
+
batch: gridBatchFor(ctx.profile, channels.length + 1),
|
|
1045
1052
|
// disposition_required (P2-C §8b leg 2): the receipt-presence stamp arming the commonLaw validator's
|
|
1046
1053
|
// receipts-disposition arm (the D1 ledger_required pattern — every fresh spec carries it; pre-P2-C
|
|
1047
1054
|
// archived specs lack it, so replay verdicts never flip). splitGridSpec spreads the connotation
|
|
@@ -2179,7 +2186,7 @@ function deriveScopeLedgerJson(ctx) {
|
|
|
2179
2186
|
// 2026-08-03 — the SEED SET is the union of every distinctive element the manifest names, and falls
|
|
2180
2187
|
// back to the job's own mark when it names none, so the floor stops moving with the model's choice of anchor
|
|
2181
2188
|
// and can no longer be empty. See the VARIANT FLOOR section of form-neighbourhood.mjs.
|
|
2182
|
-
function deriveFormNeighbourhood(ctx) {
|
|
2189
|
+
export function deriveFormNeighbourhood(ctx) { // @internal — exported for its call-site test
|
|
2183
2190
|
const P = ctx.paths;
|
|
2184
2191
|
// — an ABSENT prose manifest no longer returns without an artifact. It used to, and that was the
|
|
2185
2192
|
// zero-semantics hole at its widest: no manifest ⇒ no form-neighbourhood.json ⇒ compileRegisterPlan's
|
|
@@ -2207,14 +2214,19 @@ function deriveFormNeighbourhood(ctx) {
|
|
|
2207
2214
|
// The MARK is the floor's fallback seed and the only input to it that is not model output —
|
|
2208
2215
|
// resolved exactly as every other job-mark consumer resolves it (stages.mjs blind-frame).
|
|
2209
2216
|
const mark = ctx.job.marks ?? ctx.job.markName ?? ctx.job.name ?? "";
|
|
2210
|
-
|
|
2217
|
+
// The active register's own declaration: `false` leaves the mixed-alphabet look-alike spellings out of
|
|
2218
|
+
// the band and lists them as not searched, with the reason its index gives. Undeclared changes nothing.
|
|
2219
|
+
const caps = registerCapabilities();
|
|
2220
|
+
const mixedScriptQuery = caps?.mixedScriptQuery ?? null;
|
|
2221
|
+
const ow = loadOrdinaryWords("en"), json = renderFormNeighbourhoodJson(manifestMd, { markets, droppedAxes, model, mark, ordinaryWords: ow.words, mixedScriptQuery, nativeScriptIndex: caps?.nativeScriptIndex ?? null }); // an unloadable word list removes nothing
|
|
2211
2222
|
const tmp = P.formNeighbourhood + ".tmp";
|
|
2212
2223
|
writeFileSync(tmp, json);
|
|
2213
2224
|
renameSync(tmp, P.formNeighbourhood);
|
|
2214
|
-
let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [], notSearched = 0;
|
|
2225
|
+
let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [], notSearched = 0, mixedNotSearched = 0;
|
|
2215
2226
|
try {
|
|
2216
2227
|
const o = JSON.parse(json);
|
|
2217
2228
|
exact = o.elements.reduce((a, e) => a + (e.band?.exactQueries?.length ?? 0), 0); notSearched = o.elements.reduce((a, e) => a + (e.band?.ordinaryWordDifferentSound?.length ?? 0), 0);
|
|
2229
|
+
mixedNotSearched = o.elements.reduce((a, e) => a + (e.band?.mixedScriptNotSearched?.length ?? 0), 0);
|
|
2218
2230
|
floor = o.variant_floor?.counts?.floor ?? 0;
|
|
2219
2231
|
added = o.variant_floor?.counts?.model_added ?? 0;
|
|
2220
2232
|
seededFrom = String(o.seeded_from ?? "");
|
|
@@ -2222,7 +2234,7 @@ function deriveFormNeighbourhood(ctx) {
|
|
|
2222
2234
|
} catch { /* counts are telemetry only */ }
|
|
2223
2235
|
// The SEED SET is logged by name, because it is the input that decides whether two runs of the same
|
|
2224
2236
|
// matter can produce the same floor — a floor count alone cannot tell a stable run from a re-anchored one.
|
|
2225
|
-
runLog(P.runDir, { event: "form-neighbourhood-derived", exact, floor, modelAdded: added, droppedAxes, seeds, seededFrom, ordinaryWordNotSearched: notSearched, ...(ow.error ? { ordinaryWordListError: ow.error } : {}) });
|
|
2237
|
+
runLog(P.runDir, { event: "form-neighbourhood-derived", exact, floor, modelAdded: added, droppedAxes, seeds, seededFrom, ordinaryWordNotSearched: notSearched, ...(mixedScriptQuery === false ? { mixedScriptNotSearched: mixedNotSearched } : {}), ...(ow.error ? { ordinaryWordListError: ow.error } : {}) });
|
|
2226
2238
|
// The fallback is not a quiet degrade: it means this run's variant floor rests on the mark alone
|
|
2227
2239
|
// because the stage named no usable element. Loud, so the absence is a finding and not a shrug.
|
|
2228
2240
|
if (seededFrom.startsWith("job mark"))
|
|
@@ -3134,7 +3146,7 @@ function recordSynthesisSeam(ctx, r, trigger = null) {
|
|
|
3134
3146
|
evidence: r?.fail ? String(r.fail).slice(0, 80) : "",
|
|
3135
3147
|
reasonFor: (rec, uri) => {
|
|
3136
3148
|
// — THE 102. This callback used to be zero-arity and constant: every record this pass
|
|
3137
|
-
// discarded got the same sentence and `step-silent`, whatever it was. One of them was
|
|
3149
|
+
// discarded got the same sentence and `step-silent`, whatever it was. One of them was KORPHIC,
|
|
3138
3150
|
// a lawyer-named gold mark placed on sheet-2 and stamped `carry: "carried"`, and the trace could
|
|
3139
3151
|
// say only that synthesis had not delivered it. The sibling seam one stage earlier
|
|
3140
3152
|
// (`recordDigestSeam`) has read its stage's own output per record since it was written; this one
|
|
@@ -3324,7 +3336,7 @@ export function deriveRecordCarry(ctx, trigger, { findings = null } = {}) { //
|
|
|
3324
3336
|
// DIFFERENT Play Store apps. That collision concentrates in the non-Latin population this exists for,
|
|
3325
3337
|
// because a Han-script marketplace query returns `?q=…` search URLs.
|
|
3326
3338
|
//
|
|
3327
|
-
// This is the path that lost
|
|
3339
|
+
// This is the path that lost WAVO TWIST and WAVO TROPICS on R3 — both sat in the run's own records and
|
|
3328
3340
|
// reached no findings list. It has already cost a round.
|
|
3329
3341
|
//
|
|
3330
3342
|
// Derived at publish, like the register trace and for the same reason: everything it joins is final by
|
|
@@ -9070,6 +9082,12 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9070
9082
|
ctx.forceFrom = opts.fromStage != null ? stageOrdinal(opts.fromStage) : null;
|
|
9071
9083
|
if (opts.fromStage != null && ctx.forceFrom < 0) throw new Error(`--from: unknown stage "${opts.fromStage}"`);
|
|
9072
9084
|
runLog(run.runDir, { event: "start", agent, resume: isResume, fromStage: opts.fromStage ?? null, job: { id: job.id, slug: run.slug, codename: run.codename, customer: run.customer, forwarder: job.forwarder } });
|
|
9085
|
+
// THE SIGNA RUN MEMORY'S ATTEMPT (providers/_shared/answer-memory.mjs): whatever an earlier attempt held
|
|
9086
|
+
// is dropped, so a resume asks the register again, and the mode is fixed for this attempt.
|
|
9087
|
+
ctx.answerMemory = beginAnswerMemory(run.runDir, REGISTER_PROVIDER);
|
|
9088
|
+
if (ctx.answerMemory.applies)
|
|
9089
|
+
runLog(run.runDir, { event: "answer-memory", mode: ctx.answerMemory.mode, ...(ctx.answerMemory.unknown ? { unrecognised: ctx.answerMemory.unknown } : {}) });
|
|
9090
|
+
if (ctx.answerMemory.unknown) note(`answer memory: ${ctx.answerMemory.switch}="${ctx.answerMemory.unknown}" is not off, watch or on, so this register's default applies (${ctx.answerMemory.mode})`);
|
|
9073
9091
|
// item 11 — WHICH ARM DID THIS RUN RUN UNDER? Unconditional and three-valued in the AD-4 sense: the row
|
|
9074
9092
|
// is always written, `seed: null` meaning the ordinary production ordering. A probe arm that is not on
|
|
9075
9093
|
// the record is an arm whose result cannot be attributed later, and the probe exists to attribute.
|
|
@@ -9161,6 +9179,9 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9161
9179
|
const candidates = [], candDirectives = [], overflow = [];
|
|
9162
9180
|
const seen = new Set();
|
|
9163
9181
|
const mintedQids = new Set(); // qid uniqueness inside the lane — the fold below dedupes on it
|
|
9182
|
+
// term → every company whose delivery remembered it. The searches read every company's rows; the
|
|
9183
|
+
// receipt names whose they were, so an audit lists only its own (recallReceiptForOwnCompany).
|
|
9184
|
+
const companiesOf = new Map();
|
|
9164
9185
|
for (const r of ranked) {
|
|
9165
9186
|
const mark = String(r.mark_text).trim();
|
|
9166
9187
|
if (searchedKeys.has(markKey(mark))) continue; // the primary sweep already owns the searched mark itself
|
|
@@ -9176,13 +9197,17 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9176
9197
|
// characters", and it answered both with a silent `continue`. The first is a genuine repeat
|
|
9177
9198
|
// and is still skipped; the second is a distinct mark and now gets a distinct qid.
|
|
9178
9199
|
const termKey = `${predicate}:${term.toLowerCase()}`;
|
|
9200
|
+
if (!companiesOf.has(termKey)) companiesOf.set(termKey, new Set());
|
|
9201
|
+
companiesOf.get(termKey).add(r.customer || null);
|
|
9179
9202
|
if (seen.has(termKey)) continue;
|
|
9180
9203
|
seen.add(termKey);
|
|
9181
9204
|
const qid = mintSupplementalQid({ prefix, term, used: mintedQids });
|
|
9182
9205
|
candidates.push({ qid, axis: "primary-sweep", predicate, term, nice_classes: inScope, regions: [], expected_kind: "enumerate" });
|
|
9183
|
-
candDirectives.push({ qid, uri: r.uri ?? null, mark_text: mark, owner: r.owner ?? null });
|
|
9206
|
+
candDirectives.push({ qid, uri: r.uri ?? null, mark_text: mark, owner: r.owner ?? null, customers: companiesOf.get(termKey) });
|
|
9184
9207
|
}
|
|
9185
9208
|
}
|
|
9209
|
+
for (const d of candDirectives) d.customers = [...d.customers];
|
|
9210
|
+
const customersOfQid = new Map(candDirectives.map((d) => [d.qid, d.customers]));
|
|
9186
9211
|
// ── — THE SAME DEFECT, AND THE OWNER BUDGET IS WHERE IT BITES ───────────
|
|
9187
9212
|
//
|
|
9188
9213
|
// The cross-check lane's fix, applied here because this lane has the identical shape: the caps
|
|
@@ -9201,7 +9226,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9201
9226
|
for (const e of screened.entries) {
|
|
9202
9227
|
const isOwner = e.predicate === "owner";
|
|
9203
9228
|
if (isOwner ? ownerUsed >= RECALL_CAP_OWNER : markUsed >= RECALL_CAP_MARK) {
|
|
9204
|
-
overflow.push({ qid: e.qid, term: String(e.term).slice(0, 60) }); continue;
|
|
9229
|
+
overflow.push({ qid: e.qid, term: String(e.term).slice(0, 60), customers: customersOfQid.get(e.qid) ?? [] }); continue;
|
|
9205
9230
|
}
|
|
9206
9231
|
if (isOwner) ownerUsed++; else markUsed++;
|
|
9207
9232
|
entries.push(e);
|
|
@@ -9234,7 +9259,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
9234
9259
|
note(`recall probes: ${refusedDirectives.length} probe(s) REFUSED as un-searchable terms — recorded in _driver/register-recall.json refused[], never dispatched`);
|
|
9235
9260
|
}
|
|
9236
9261
|
if (kept.length || overflow.length || refusedDirectives.length) {
|
|
9237
|
-
writeFileSync(`${receiptPath}.tmp`, JSON.stringify({ schema_version: 2, ts: new Date().toISOString(), cap: { mark: RECALL_CAP_MARK, owner: RECALL_CAP_OWNER }, directives: kept, overflow, ...(refusedDirectives.length ? { refused: refusedDirectives } : {}) }, null, 2) + "\n");
|
|
9262
|
+
writeFileSync(`${receiptPath}.tmp`, JSON.stringify({ schema_version: 2, ts: new Date().toISOString(), customer: ctx.profile?.profileKey || null, cap: { mark: RECALL_CAP_MARK, owner: RECALL_CAP_OWNER }, directives: kept, overflow, ...(refusedDirectives.length ? { refused: refusedDirectives } : {}) }, null, 2) + "\n");
|
|
9238
9263
|
renameSync(`${receiptPath}.tmp`, receiptPath);
|
|
9239
9264
|
}
|
|
9240
9265
|
// The RECEIPT partitions; what the seat is TOLD does not. This feeds the primary-sweep prompt's
|
|
@@ -10410,7 +10435,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
10410
10435
|
// plan-unexecuted StageFailure below still holds the line for a genuine hole.
|
|
10411
10436
|
runLog(run.runDir, { event: "plan-qids-missing", axis: a, qids: entries.map((e) => e.qid), action: "fresh-execute-plan" });
|
|
10412
10437
|
note(`register-unit ${a}: ${entries.length} dictated plan entr${entries.length === 1 ? "y" : "ies"} unexecuted on a resumed-past axis — one fresh execute_plan-only call`);
|
|
10413
|
-
const freshMsg = repairFollowup("register-unit:plan-join-fresh", { axis: a, registerPlan: P.registerPlan, bandPath: P.registerBand(a) });
|
|
10438
|
+
const freshMsg = repairFollowup("register-unit:plan-join-fresh", { axis: a, registerPlan: P.registerPlan, bandPath: P.registerBand(a), entries });
|
|
10414
10439
|
const rf = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: freshMsg, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-plan-join-fresh`, trigger: "plan-join-fresh" });
|
|
10415
10440
|
if (!rf.ok) note(`register-unit ${a}: fresh execute_plan call failed (${rf.fail}) — the plan-unexecuted StageFailure below holds the line`);
|
|
10416
10441
|
continue;
|
|
@@ -10591,10 +10616,13 @@ async function pipelineInner(job, opts = {}) {
|
|
|
10591
10616
|
// back-compat: a pre-doc-35 receipt keyed only on the cell set (no source channels)
|
|
10592
10617
|
(priorReceipt.sig == null && !sourceChannels.length &&
|
|
10593
10618
|
JSON.stringify((priorReceipt.requested ?? []).map(cellKey).sort()) === JSON.stringify(closable.map(cellKey).sort())));
|
|
10619
|
+
// SIZED BY THE CELLS A CLOSURE GRID RUNS, as the main grid is (gridBatchFor): its spec carries only the
|
|
10620
|
+
// platforms of the cells it closes.
|
|
10621
|
+
const closureBatch = (cells) => gridBatchFor(ctx.profile, new Set(cells.map((c) => c.platform)).size);
|
|
10594
10622
|
if ((closable.length || sourceChannels.length) && !alreadyAttempted) {
|
|
10595
10623
|
const requested = closable;
|
|
10596
10624
|
const variants = [...new Set(closable.map((c) => c.variant))];
|
|
10597
|
-
const batchSize =
|
|
10625
|
+
const batchSize = closureBatch(closable);
|
|
10598
10626
|
const batches = Math.max(1, Math.ceil(variants.length / batchSize));
|
|
10599
10627
|
const gridCalls = batches + (sourceChannels.length ? 1 : 0); // grid call(s) + a channel-sweep call (tokens-only: no $ estimate)
|
|
10600
10628
|
note(`coverage closure: ${closable.length} machine-closable cell(s)${sourceChannels.length ? ` + ${sourceChannels.length} un-swept in-scope channel(s) [${sourceChannels.join(", ")}]` : ""} — one supplementary grid pass (${gridCalls} grid call(s))`);
|
|
@@ -10707,7 +10735,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
10707
10735
|
}
|
|
10708
10736
|
if (closable.length || exempt.length) {
|
|
10709
10737
|
// Tokens-only (owner directive 2026-07-11): the offer names the work (grid calls), never a $ figure.
|
|
10710
|
-
const batchesLeft = Math.max(1, Math.ceil([...new Set(closable.map((c) => c.variant))].length / (
|
|
10738
|
+
const batchesLeft = Math.max(1, Math.ceil([...new Set(closable.map((c) => c.variant))].length / closureBatch(closable)));
|
|
10711
10739
|
const offer = ` — closable on instruction (${batchesLeft} supplementary grid call(s))`;
|
|
10712
10740
|
ctx.coverageNote = `marketplace cells not executed: ${[...closable, ...exempt].map(cellKey).join("; ")}` +
|
|
10713
10741
|
(closable.length ? ` — attempted in-loop and still unreachable${offer}` : "") +
|
|
@@ -11060,7 +11088,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
11060
11088
|
directiveQids.set(key, []);
|
|
11061
11089
|
// Per-case wording (A2): the variant fallback now also DISCLOSES a label-shaped item
|
|
11062
11090
|
// (term-shape lint) — dispatching the display label verbatim was the 2026-07-28
|
|
11063
|
-
// nil-search class ("Reverse-order
|
|
11091
|
+
// nil-search class ("Reverse-order WAVO composites (…)" searched as exact → 0 → read
|
|
11064
11092
|
// as clean). The remedy contract (SKILL.md) is the fix the wording points at.
|
|
11065
11093
|
regDeferReason.set(key, String(d.layer ?? "").toLowerCase() === "field"
|
|
11066
11094
|
? "no-code-remedy: a field class-gap with no searchable term×class pair (closing it in the matter's own classes only re-runs the primary sweep — disclosed)"
|
|
@@ -11152,8 +11180,8 @@ async function pipelineInner(job, opts = {}) {
|
|
|
11152
11180
|
}
|
|
11153
11181
|
// Dispatch → derive band → VERIFY per directive (qid-landed + non-collapse + class-scope).
|
|
11154
11182
|
// A byte-changed band with only a wrong-scope/empty/error block closes NOTHING.
|
|
11155
|
-
const dispatchAndVerify = async () => {
|
|
11156
|
-
const outcome = await dispatchPlanQids(a,
|
|
11183
|
+
const dispatchAndVerify = async (qids) => {
|
|
11184
|
+
const outcome = await dispatchPlanQids(a, qids, "frame-reopen-sweep", 2);
|
|
11157
11185
|
refreshSupplementalExecution(ctx);
|
|
11158
11186
|
try { deriveNamedBand(ctx); } catch (e) { regMechFail = `named-band:${String(e.message).slice(0, 80)}`; }
|
|
11159
11187
|
const bands = readRegisterBands(P, axes);
|
|
@@ -11166,7 +11194,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
11166
11194
|
executedQids: ev.executedQids, blocksByQid: ev.blocksByQid,
|
|
11167
11195
|
});
|
|
11168
11196
|
const mintedDirectives = regDirectives.filter((d) => (directiveQids.get(reopenKey(d)) ?? []).length);
|
|
11169
|
-
let ev = await dispatchAndVerify();
|
|
11197
|
+
let ev = await dispatchAndVerify(added);
|
|
11170
11198
|
regLastJoin = ev;
|
|
11171
11199
|
let outcome = ev.outcome;
|
|
11172
11200
|
const retryTargets = [];
|
|
@@ -11179,16 +11207,24 @@ async function pipelineInner(job, opts = {}) {
|
|
|
11179
11207
|
// correct scope from attempt 1, so this recovers a transient (an error / collapsed / unlanded
|
|
11180
11208
|
// slice), never re-runs a wrong query. dispatchPlanQids' own repair ledger (max 2) refuses a
|
|
11181
11209
|
// third — so this cannot loop or thrash the paid pass.
|
|
11210
|
+
//
|
|
11211
|
+
// ONLY THE FAILING DIRECTIVES' OWN ENTRIES GO AGAIN. A directive that verified closed on the first
|
|
11212
|
+
// attempt is not asked twice: re-sending its qids re-fetches records the run already holds, and
|
|
11213
|
+
// on a register that bills per request it pays for them again.
|
|
11182
11214
|
if (retryTargets.length) {
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11215
|
+
const retryQids = [...new Set(retryTargets.flatMap((d) => directiveQids.get(reopenKey(d)) ?? []))]
|
|
11216
|
+
.filter((q) => added.includes(q));
|
|
11217
|
+
note(`frame-reopen: ${retryTargets.length} directive(s) not verified-closed — ONE re-attempt of their own ${retryQids.length} entr${retryQids.length === 1 ? "y" : "ies"} with the same structured remedy`);
|
|
11218
|
+
runLog(run.runDir, { event: "frame-reopen-reattempt", directives: retryTargets.map(reopenKey), qids: retryQids });
|
|
11219
|
+
if (retryQids.length) {
|
|
11220
|
+
ev = await dispatchAndVerify(retryQids);
|
|
11221
|
+
regLastJoin = ev;
|
|
11222
|
+
outcome = ev.outcome ?? outcome;
|
|
11223
|
+
for (const d of retryTargets) {
|
|
11224
|
+
const v = verifyOne(d, ev);
|
|
11225
|
+
if (v.closed) { regSwept.add(reopenKey(d)); regDeferReason.delete(reopenKey(d)); }
|
|
11226
|
+
else regDeferReason.set(reopenKey(d), v.reason);
|
|
11227
|
+
}
|
|
11192
11228
|
}
|
|
11193
11229
|
}
|
|
11194
11230
|
const afterBand = existsSync(P.registerBand(a)) ? readFileSync(P.registerBand(a), "utf8") : null;
|
|
@@ -12557,7 +12593,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
12557
12593
|
jurisdictions: (() => { try { return JSON.parse(readFileSync(P.instructedScope, "utf8"))?.jurisdictions ?? null; } catch { return null; } })(),
|
|
12558
12594
|
});
|
|
12559
12595
|
if (caseLaw.declined)
|
|
12560
|
-
note(`[case-law] the draft narrative turns on "${caseLaw.trigger}", and a ${caseLaw.product ?? "run with no resolved product"} does not carry the case-law reading — recorded, not run
|
|
12596
|
+
note(`[case-law] the draft narrative turns on "${caseLaw.trigger}", and a ${caseLaw.product ?? "run with no resolved product"} does not carry the case-law reading — recorded, not run`);
|
|
12561
12597
|
if (needCaseLaw) runLog(run.runDir, { event: "case-law-trigger", requested: caseLaw.requested, detected: caseLaw.detected });
|
|
12562
12598
|
// T7 (E5): feed the finding index so the case-law stage stamps "- ord:" on each grounded
|
|
12563
12599
|
// profile — the render + report-card joins become deterministic (copper-spire's prompt-fished join
|
|
@@ -13500,7 +13536,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
13500
13536
|
// by construction, which is the intended shape: a BLOCKING with an empty reasons array is
|
|
13501
13537
|
// structurally impossible, and any future path that reintroduces one dies here instead of shipping.
|
|
13502
13538
|
if (verdict === "BLOCKING" && !reasonsOut.length)
|
|
13503
|
-
throw new Error("verdict BLOCKING with no reasons — a blocking decision without its grounds; verdict.json is the artifact whose whole purpose is carrying them
|
|
13539
|
+
throw new Error("verdict BLOCKING with no reasons — a blocking decision without its grounds; verdict.json is the artifact whose whole purpose is carrying them");
|
|
13504
13540
|
// doc 50 — a v4 record derives off the run's FROZEN framework (band words); deriveDisplayVerdict
|
|
13505
13541
|
// fail-louds if banded findings arrive with no manifest (never silently badge a rated matter LOW).
|
|
13506
13542
|
const derived = deriveDisplayVerdict({ verdict, reasons: reasonsOut, kinds: kindsOut, findings: findingsArr,
|
|
@@ -14096,7 +14132,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
14096
14132
|
screenGateUnresolved: readJson(driverDir(run.runDir, "screen-gate-unresolved.json"))?.unresolved ?? null,
|
|
14097
14133
|
supplementalPlans,
|
|
14098
14134
|
xcheck: readJson(driverDir(run.runDir, "register-xcheck.json")),
|
|
14099
|
-
recall: readJson(driverDir(run.runDir, "register-recall.json")),
|
|
14135
|
+
recall: recallReceiptForOwnCompany(readJson(driverDir(run.runDir, "register-recall.json"))), // never another company's recall line
|
|
14100
14136
|
planExecution: ctx.planExecution ?? readJson(P.planExecution),
|
|
14101
14137
|
}, { ts: askTs });
|
|
14102
14138
|
} catch (e) {
|
|
@@ -15476,6 +15512,7 @@ async function pipelineInner(job, opts = {}) {
|
|
|
15476
15512
|
// and the run is already settled, so a failure to stamp is logged and the delivery proceeds.
|
|
15477
15513
|
const stamp = writeSettleStamp(published.poolRunDir, { state: "delivered", signoff: verdict, deliveredAt, runId: published.runId ?? run.runId, lane: "clearance" });
|
|
15478
15514
|
if (!stamp.written) note(`delivery: settle stamp not written (${stamp.reason})`);
|
|
15515
|
+
endAnswerMemory(run.runDir); // the held answers do not travel into the archive
|
|
15479
15516
|
const archived = archive(run);
|
|
15480
15517
|
rollupStatus(run.studioRoot);
|
|
15481
15518
|
// `notified: "pending"` and `sendPending: true` unconditionally, which is what the KNOCKOUT lane has
|
|
@@ -71,7 +71,7 @@ const isSepRow = (t) => /^\|[\s:|-]+\|$/.test(t);
|
|
|
71
71
|
* BOTH reach every line of the section, prose included, and that is deliberate. The first cut held
|
|
72
72
|
* the text path to pipe-TABLE rows only, on the theory that digest.md puts carried candidates in
|
|
73
73
|
* tables. Run against the real 2026-07-29 findings it manufactured a loss: the watchlist annex there
|
|
74
|
-
* is written as `- **W-1 ·
|
|
74
|
+
* is written as `- **W-1 · WAVO Brand outdoor-living portfolio — …**` bullets, so a candidate the
|
|
75
75
|
* digest had plainly carried came back `uncarried`. A join that invents losses is worth nothing, and
|
|
76
76
|
* the section shape is the digest's to choose. So the reach is the same as parseFindingsEndings
|
|
77
77
|
* (recall-reconciliation.mjs), which has always counted a prose mention as an ending — one
|
|
@@ -216,7 +216,7 @@ export function adjudicationOf(entry, surfaces) {
|
|
|
216
216
|
// Measured on `placement-carry-2026-07-29`, whose delivered digest discharges its one borderline
|
|
217
217
|
// entry in as many words —
|
|
218
218
|
//
|
|
219
|
-
// | R-8 (my override) — placement-inquiry placed `
|
|
219
|
+
// | R-8 (my override) — placement-inquiry placed `WAVO LOVERS` at sheet-2 (S2-C) while flagging
|
|
220
220
|
// it as a borderline headline call | OVERRODE — promoted to headline. …
|
|
221
221
|
//
|
|
222
222
|
// — and names no URI anywhere in that row. A URI-only branch reports that discharged obligation as
|
|
@@ -103,7 +103,7 @@ export const SELECT_ROW_CONTRACT = Object.freeze({
|
|
|
103
103
|
unresolved: "A `select` the band does not hold is reported back to you by id, never silently dropped.",
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
const PROVENANCE = "driver-written form
|
|
106
|
+
const PROVENANCE = "driver-written form. You SELECT and you JUDGE; the driver COPIES. For each "
|
|
107
107
|
+ "register candidate you are placing, add a row naming one of its record ids in `select` plus your "
|
|
108
108
|
+ "`tier` and `reason` (and `borderline` if the call is close) — the driver resolves the id against the "
|
|
109
109
|
+ "register's own exact-identity fold and fills mark, owner, records, territories and classes from it, "
|
|
@@ -83,7 +83,7 @@ function post(urlStr, { headers = {}, body = "", wantId = null, timeoutMs = 3000
|
|
|
83
83
|
});
|
|
84
84
|
// — a timeout got no status and therefore no answer; it is marked at the throw rather than
|
|
85
85
|
// recognised later by its message, so nothing has to keep a regex in step with a sentence.
|
|
86
|
-
req.setTimeout(timeoutMs, () => { req.destroy(transportError(`MCP request timed out after ${timeoutMs}ms`, null)); });
|
|
86
|
+
req.setTimeout(timeoutMs, () => { req.destroy(Object.assign(transportError(`MCP request timed out after ${timeoutMs}ms`, null), { timedOut: true })); });
|
|
87
87
|
req.on("error", (e) => reject(isSocketFailure(e) ? transportError(e.message, null) : e));
|
|
88
88
|
req.end(body);
|
|
89
89
|
});
|