clearotron 0.4.1 → 0.5.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/README.md +1 -0
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/bin/start.mjs +33 -8
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +32 -0
- package/driver/ask-ledger.mjs +90 -16
- package/driver/band-shape.mjs +65 -11
- package/driver/band-size.mjs +10 -0
- package/driver/close-verify.mjs +29 -2
- package/driver/common-law-coverage-status.mjs +1 -1
- package/driver/connotation-search.mjs +7 -9
- package/driver/contract-e3-backlog.mjs +34 -34
- package/driver/contract-e3-baseline.json +5 -5
- package/driver/contract-vocabulary.mjs +12 -12
- package/driver/corrections-delivered.mjs +113 -0
- package/driver/corrections-feedforward.mjs +16 -6
- package/driver/corrective-arm.mjs +11 -8
- package/driver/coverage-form-io.mjs +2 -1
- package/driver/coverage-form.mjs +17 -2
- package/driver/coverage-union.mjs +11 -2
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-call.mjs +1 -1
- package/driver/document-coverage.mjs +3 -2
- package/driver/effective-scope.mjs +12 -7
- package/driver/engine/mcp/band-server.mjs +77 -13
- package/driver/engine/mcp/dispositions-server.mjs +2 -2
- package/driver/engine/mcp/held-answers.mjs +128 -0
- package/driver/engine/mcp/recording-server.mjs +10 -6
- package/driver/engine/mcp/supplemental.mjs +74 -12
- package/driver/findings-model.mjs +16 -4
- package/driver/floor-duty.mjs +9 -0
- package/driver/gate-metrics.mjs +7 -1
- package/driver/gateway.mjs +28 -14
- package/driver/goods-evidence.mjs +295 -0
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/narrative-refutation-record.mjs +40 -2
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +1994 -323
- package/driver/placement-decisions.mjs +243 -0
- package/driver/placement-form.mjs +42 -8
- package/driver/placement-union.mjs +14 -6
- package/driver/provider-usage.mjs +10 -7
- package/driver/publish/audit-from-spine.mjs +5 -2
- package/driver/publish/index.mjs +19 -4
- package/driver/publish/xlsx.mjs +6 -6
- package/driver/recall-reconciliation.mjs +15 -5
- package/driver/record-carry.mjs +70 -6
- package/driver/record-discard.mjs +7 -1
- package/driver/record-fold.mjs +14 -0
- package/driver/record-goods.mjs +101 -5
- package/driver/register-plan.mjs +221 -6
- package/driver/registry-fidelity.mjs +34 -3
- package/driver/remedy-accounting.mjs +52 -14
- package/driver/repair-composers.mjs +265 -105
- package/driver/repair-scope.mjs +327 -0
- package/driver/repair-targets.mjs +92 -0
- package/driver/result-noun-fields.mjs +3 -3
- package/driver/scope-facts.mjs +6 -2
- package/driver/screen-gate.mjs +96 -0
- package/driver/skills/clearance-common-law/SKILL.md +8 -0
- package/driver/skills/clearance-register/SKILL.md +29 -21
- package/driver/skills/clearance-register/digest.md +22 -17
- package/driver/skills/clearance-register/providers/clarivate.md +4 -4
- package/driver/skills/clearance-register/providers/corsearch.md +1 -1
- package/driver/skills/clearance-register/providers/euipo.md +4 -4
- package/driver/skills/clearance-register/providers/free-tier.md +4 -4
- package/driver/skills/clearance-register/providers/uspto-local.md +3 -3
- package/driver/skills/clearance-register/register-recipes.md +6 -6
- package/driver/skills/clearance-register/unit.md +29 -22
- package/driver/skills/clearance-search/delivery-contract.md +2 -58
- package/driver/skills/clearance-search/report-prose.md +12 -3
- package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
- package/driver/skills/clearance-search/synthesis-rules.md +10 -13
- package/driver/skills/clearance-variants/SKILL.md +4 -4
- package/driver/skills/knockout-assess/SKILL.md +9 -14
- package/driver/skills/knockout-frame/SKILL.md +5 -5
- package/driver/skills/narrative-refutation/SKILL.md +1 -1
- package/driver/skills/placement-inquiry/SKILL.md +1 -1
- package/driver/skills/whatif-memo/SKILL.md +1 -1
- package/driver/stage-context.mjs +2 -1
- package/driver/stages-knockout.mjs +1 -1
- package/driver/stages.mjs +96 -61
- package/driver/suite-census.json +415 -25
- package/driver/synthesis-record.mjs +310 -8
- package/driver/terminal-clamp.mjs +53 -2
- package/driver/verify-knockout.mjs +22 -4
- package/driver/verify.mjs +23 -3
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/lib/driver.mjs +5 -0
- package/mcp-server/lib/events.mjs +25 -4
- package/mcp-server/lib/findings.mjs +4 -2
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/enumerate.mjs +12 -6
- package/providers/_shared/execute-plan.mjs +25 -1
- package/providers/_shared/follow-up-searches.mjs +139 -0
- package/providers/jx/src/core.js +3 -4
- package/providers/jx/src/judge.js +2 -2
- package/providers/jx/src/nativeread.js +1 -2
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/e2e-first-time.mjs +30 -13
- package/scripts/release-await-cut.mjs +4 -2
|
@@ -32,10 +32,18 @@
|
|
|
32
32
|
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
|
|
33
33
|
import { dirname, join } from "node:path";
|
|
34
34
|
import { driverDir } from "../../../shared/driver-dir.mjs"; //
|
|
35
|
-
import { PLAN_PREDICATES, PLAN_MAX_OR_WIDTH, PLAN_MAX_NAME_LENGTH, fingerprint, ownerIntersectionGap, goodsTextGap, resolveRegions, houseElementOf, withoutHouseElementTerms, containsFormSubstitution } from "../../register-plan.mjs";
|
|
35
|
+
import { PLAN_PREDICATES, PLAN_MAX_OR_WIDTH, PLAN_MAX_NAME_LENGTH, planExactOrWidth, planPhoneticOrWidth, fingerprint, ownerIntersectionGap, goodsTextGap, resolveRegions, houseElementOf, withoutHouseElementTerms, containsFormSubstitution } from "../../register-plan.mjs";
|
|
36
36
|
import { entryTermIssues } from "../../../providers/_shared/term-shape.mjs";
|
|
37
|
+
import { OUTSTANDING_REASON } from "../../../providers/_shared/follow-up-searches.mjs";
|
|
38
|
+
import { heldAnswers, questionKey } from "./held-answers.mjs";
|
|
39
|
+
import { readWithheldFamilies } from "../../withheld-families.mjs";
|
|
37
40
|
import { isNonLatinTerm, romanizationRefusal, romanizationSpellings, nativeScriptIndexGap } from "../../../providers/_shared/script-form.mjs";
|
|
38
41
|
|
|
42
|
+
// THE ONE SENTENCE THE MODEL READS WHEN THE RUN'S SAFETY NET HOLDS A FOLLOW-UP BACK (follow-up-searches.mjs). It names
|
|
43
|
+
// no limit and no cost: the model is never told of the net. It rides the reply whenever a question was not sent, now
|
|
44
|
+
// or by an earlier decision held for the run.
|
|
45
|
+
export const OUTSTANDING_NOTE = "Not asked in this run. Keep it as an open question with your reason, and continue with what you have read.";
|
|
46
|
+
|
|
39
47
|
const slug = (s) => String(s ?? "").toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "q";
|
|
40
48
|
|
|
41
49
|
const PREVIEW_FIELDS = ["record_id", "mark_text", "classes", "status", "owner_name", "owner_country", "screen_verdict"];
|
|
@@ -57,17 +65,20 @@ const compactProposal = (p) => ({
|
|
|
57
65
|
const inPriority = (p, prio) =>
|
|
58
66
|
(Array.isArray(p?.nice_classes) ? p.nice_classes : []).some((c) => prio.has(String(c).trim()));
|
|
59
67
|
|
|
60
|
-
// A
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
68
|
+
// A BATCH OF SPELLINGS GOES AS THE PLANNER'S LISTS where the register takes one: exact spellings up to
|
|
69
|
+
// `exactOrWidth`, sound-alike ones up to `phoneticOrWidth` within the register's word budget (ruled 2026-10-09
|
|
70
|
+
// for sound-alikes; the exact width is the one the plan's own spelling band already uses), each spelling long
|
|
71
|
+
// enough for the ranked search. A spelling below that floor is its own question, and so is every name of a batch
|
|
72
|
+
// with an owner or on any other predicate, as before. Each list is one question with one receipt row, so a list
|
|
73
|
+
// that crowds or fails takes no other list down with it. PURE.
|
|
64
74
|
function splitBatch(p, names, capabilities) {
|
|
65
75
|
const { terms: _batch, ...rest } = p;
|
|
66
76
|
const single = (t) => ({ ...rest, term: t });
|
|
67
|
-
const
|
|
68
|
-
|
|
77
|
+
const predicate = String(p?.predicate ?? "");
|
|
78
|
+
const width = predicate === "exact" ? planExactOrWidth(capabilities) : predicate === "phonetic" ? planPhoneticOrWidth(capabilities) : 1;
|
|
79
|
+
if (!(width >= 2) || (typeof p?.owner === "string" && p.owner.trim())) return names.map(single);
|
|
69
80
|
const floor = Number(capabilities?.rankedMinLength) || 0;
|
|
70
|
-
const budget = Number(capabilities?.listWordBudget) || Infinity;
|
|
81
|
+
const budget = predicate === "phonetic" ? (Number(capabilities?.listWordBudget) || Infinity) : Infinity;
|
|
71
82
|
const words = (t) => t.split(/\s+/).filter(Boolean).length;
|
|
72
83
|
const folded = (t) => [...t.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/\s+/g, "")].length;
|
|
73
84
|
const out = [];
|
|
@@ -427,9 +438,29 @@ export async function proposeSupplemental(params, tctx, deps) {
|
|
|
427
438
|
if (!offered.length)
|
|
428
439
|
return { type: "text", text: JSON.stringify({ minted: [], reused: [], rejected: [], excluded_house_element: excludedHouse, executed: false }, null, 2) };
|
|
429
440
|
const existingQids = new Set(supp.entries.map((e) => e.qid));
|
|
430
|
-
|
|
441
|
+
let { minted, reused, rejected, enriched, narrowed } = mintSupplementalEntries(axis, offered,
|
|
431
442
|
{ existingQids, capabilities: deps.capabilities ?? null, priorityClasses: planClasses });
|
|
432
443
|
|
|
444
|
+
// ── ONE ANSWER PER QUESTION PER RUN (held-answers.mjs) ───────────────────────────────────────────
|
|
445
|
+
// A question another worker or pass has already answered gets that answer, and one the run has decided not to ask
|
|
446
|
+
// gets that decision: it is not minted, not run, and costs no call. A failed answer is no answer, so it is asked again.
|
|
447
|
+
const held = [];
|
|
448
|
+
try {
|
|
449
|
+
const runDir = dirname(dirname(outPath));
|
|
450
|
+
const index = heldAnswers(runDir, { planRegions, withheld: readWithheldFamilies(runDir) });
|
|
451
|
+
if (index.size) {
|
|
452
|
+
const fresh = [];
|
|
453
|
+
for (const e of minted) {
|
|
454
|
+
const h = index.get(questionKey(e, planRegions));
|
|
455
|
+
if (!h || h.qid === e.qid) { fresh.push(e); continue; }
|
|
456
|
+
held.push({ question: compactProposal(e), decision: h.decision, ...(h.qid ? { answered_by: h.qid, axis: h.axis } : {}),
|
|
457
|
+
...(h.block ? { state: h.block.state ?? null, total_hits: h.block.total_hits ?? null,
|
|
458
|
+
count: h.block.count ?? (Array.isArray(h.block.records) ? h.block.records.length : null), records_preview: preview(h.block.records) } : {}) });
|
|
459
|
+
}
|
|
460
|
+
minted = fresh;
|
|
461
|
+
}
|
|
462
|
+
} catch { /* the held index is an economy: without it every question is asked as before */ }
|
|
463
|
+
|
|
433
464
|
// Field-level romanisation enrichment of a REUSED qid (2026-07-30 review round): the natural retry —
|
|
434
465
|
// a bare non-Latin proposal deferred at the wire, the model re-proposes it WITH the romanisation —
|
|
435
466
|
// reuses the same deterministic qid, and without this the stored bare entry re-executed verbatim and
|
|
@@ -486,7 +517,15 @@ export async function proposeSupplemental(params, tctx, deps) {
|
|
|
486
517
|
renameSync(`${suppPath}.tmp`, suppPath);
|
|
487
518
|
}
|
|
488
519
|
|
|
489
|
-
|
|
520
|
+
// A question this worker re-proposes that already holds an answer is not run again: the answer stands. One with no
|
|
521
|
+
// answer, a failed one, or one this call enriched or gave its markets runs as before.
|
|
522
|
+
let reusedHeld = [];
|
|
523
|
+
try {
|
|
524
|
+
const own = new Map();
|
|
525
|
+
for (const b of JSON.parse(readFileSync(outPath, "utf8"))) if (b?.qid) own.set(String(b.qid), b);
|
|
526
|
+
reusedHeld = reused.filter((q) => { const b = own.get(String(q)); return b && b.error !== true && !enrichedQids.includes(q) && !staleOnDisk; });
|
|
527
|
+
} catch { reusedHeld = []; }
|
|
528
|
+
const qids = [...minted.map((e) => e.qid), ...reused.filter((q) => !reusedHeld.includes(q))];
|
|
490
529
|
let summary = null;
|
|
491
530
|
if (qids.length) {
|
|
492
531
|
const r = await executePlan({ plan_path: suppPath, axis, output_path: outPath, qids }, tctx);
|
|
@@ -497,13 +536,36 @@ export async function proposeSupplemental(params, tctx, deps) {
|
|
|
497
536
|
try { summary = JSON.parse(text); } catch { summary = { raw: text.slice(0, 300) }; }
|
|
498
537
|
}
|
|
499
538
|
|
|
539
|
+
// A FOLLOW-UP THE RUN'S SAFETY NET DID NOT SEND never ran and has no answer (follow-up-searches.mjs). One minted by this
|
|
540
|
+
// call leaves the plan the fold reads, so it is never taken for a planned question that went missing, and is
|
|
541
|
+
// kept as a question not asked: a rejected[] row, which the ask ledger keeps open. One that ran before keeps its
|
|
542
|
+
// place and its answer.
|
|
543
|
+
const outstanding = Array.isArray(summary?.outstanding) ? summary.outstanding.map(String) : [];
|
|
544
|
+
const mintedHere = new Map(minted.map((e) => [e.qid, e]));
|
|
545
|
+
const refusedHere = outstanding.filter((q) => mintedHere.has(q));
|
|
546
|
+
if (refusedHere.length) {
|
|
547
|
+
let cur = supp;
|
|
548
|
+
try { cur = JSON.parse(readFileSync(suppPath, "utf8")); } catch { /* the in-memory plan is what was written */ }
|
|
549
|
+
const gone = new Set(refusedHere);
|
|
550
|
+
cur = withRejected({ ...cur, entries: (Array.isArray(cur?.entries) ? cur.entries : []).filter((e) => !gone.has(e?.qid)) },
|
|
551
|
+
// The markets and goods ride on the row: the ask ledger keeps one ask per (issue, proposal), and the compact
|
|
552
|
+
// proposal alone would fold the same question refused for several markets into one ask naming none.
|
|
553
|
+
refusedHere.map((q) => { const e = mintedHere.get(q); return { issue: OUTSTANDING_REASON, proposal: { ...compactProposal(e),
|
|
554
|
+
...(Array.isArray(e?.regions) && e.regions.length ? { regions: e.regions.map(String).slice(0, 200) } : {}),
|
|
555
|
+
...(Array.isArray(e?.goods_text) && e.goods_text.length ? { goods_text: e.goods_text.map((g) => String(g).slice(0, 80)).slice(0, 20) } : {}) } }; }),
|
|
556
|
+
{ ts: new Date().toISOString(), origin: "propose-tool" });
|
|
557
|
+
mkdirSync(dirname(suppPath), { recursive: true });
|
|
558
|
+
writeFileSync(`${suppPath}.tmp`, JSON.stringify(cur, null, 2) + "\n");
|
|
559
|
+
renameSync(`${suppPath}.tmp`, suppPath);
|
|
560
|
+
}
|
|
561
|
+
|
|
500
562
|
// read the results BACK FROM THE BAND — the reasoning payload is never model-transcribed
|
|
501
563
|
const results = {};
|
|
502
564
|
try {
|
|
503
565
|
const band = JSON.parse(readFileSync(outPath, "utf8"));
|
|
504
566
|
const blocks = Array.isArray(band) ? band : [];
|
|
505
567
|
for (const b of blocks) {
|
|
506
|
-
if (!b || typeof b !== "object" || !b.qid || !qids.includes(b.qid)) continue;
|
|
568
|
+
if (!b || typeof b !== "object" || !b.qid || !(qids.includes(b.qid) || reusedHeld.includes(b.qid))) continue;
|
|
507
569
|
results[b.qid] = {
|
|
508
570
|
state: b.state ?? null,
|
|
509
571
|
total_hits: b.total_hits ?? null,
|
|
@@ -517,5 +579,5 @@ export async function proposeSupplemental(params, tctx, deps) {
|
|
|
517
579
|
}
|
|
518
580
|
} catch { /* band unreadable — the executor summary still crosses */ }
|
|
519
581
|
|
|
520
|
-
return { type: "text", text: JSON.stringify({ minted: minted.map((e) => e.qid), reused, ...(enrichedQids.length ? { enriched: enrichedQids } : {}), ...(conflicts.length ? { conflict: conflicts } : {}), rejected, ...(excludedHouse.length ? { excluded_house_element: excludedHouse } : {}), ...(narrowed.length ? { narrowed } : {}), executed: qids.length > 0, summary, results }, null, 2) };
|
|
582
|
+
return { type: "text", text: JSON.stringify({ minted: minted.map((e) => e.qid).filter((q) => !refusedHere.includes(q)), reused, ...(enrichedQids.length ? { enriched: enrichedQids } : {}), ...(conflicts.length ? { conflict: conflicts } : {}), rejected, ...(outstanding.length ? { outstanding } : {}), ...(held.length ? { held } : {}), ...(outstanding.length || held.some((h) => h.decision === "outstanding") ? { outstanding_note: OUTSTANDING_NOTE } : {}), ...(reusedHeld.length ? { held_answer_kept: reusedHeld } : {}), ...(excludedHouse.length ? { excluded_house_element: excludedHouse } : {}), ...(narrowed.length ? { narrowed } : {}), executed: qids.length > 0, summary, results }, null, 2) };
|
|
521
583
|
}
|
|
@@ -1534,7 +1534,7 @@ function validateNetRequired(f, ord, mode) {
|
|
|
1534
1534
|
if (f.ruled_out === true) return; // its ground is ruled_out_reason
|
|
1535
1535
|
if (!POSITION_REQUIRED_DISPOSITIONS.includes(f.disposition)) return; // withdrawn — see the const's doc block
|
|
1536
1536
|
if (typeof f.net === "string" && f.net.trim()) return;
|
|
1537
|
-
throw new Error(`finding_net_missing:${ord} (EVERY finding a reader sees carries the
|
|
1537
|
+
throw new Error(`finding_net_missing:${ord} (EVERY finding a reader sees carries the net — it is the ONLY per-finding summary the report has: the card leads with it, the grouped-negative line states it, the MCP brief lists the finding by it.)`);
|
|
1538
1538
|
}
|
|
1539
1539
|
|
|
1540
1540
|
// ── requirement 2 — the label follows the argument ───────────────────────────────────────────────
|
|
@@ -1734,9 +1734,9 @@ export function netChainMarkers(s) {
|
|
|
1734
1734
|
function validateNet(f, ord, mode) {
|
|
1735
1735
|
if (f.net == null) return; // absent ⇒ v6 has already thrown; below v6 the card falls back as it always did
|
|
1736
1736
|
if (typeof f.net !== "string" || !f.net.trim())
|
|
1737
|
-
throw new Error(`finding_net_invalid:${ord} (net is
|
|
1737
|
+
throw new Error(`finding_net_invalid:${ord} (net is a non-empty string, or omit it)`);
|
|
1738
1738
|
if (PRESCRIPTION_RE.test(f.net))
|
|
1739
|
-
throw new Error(`finding_net_prescriptive:${ord} (the
|
|
1739
|
+
throw new Error(`finding_net_prescriptive:${ord} (the read carries FACTS AND ASSESSMENT, never an action prescription — the reader is a lawyer who layers their own advice on top; let the typed actions register carry what a human must do)`);
|
|
1740
1740
|
validateNetShape(f, ord, mode);
|
|
1741
1741
|
f.net = f.net.trim();
|
|
1742
1742
|
}
|
|
@@ -1805,7 +1805,7 @@ function validateNetShape(f, ord, mode) {
|
|
|
1805
1805
|
if (!POSITION_REQUIRED_DISPOSITIONS.includes(f.disposition)) return; // withdrawn — see above
|
|
1806
1806
|
const markers = netChainMarkers(f.net);
|
|
1807
1807
|
if (!markers.length) return;
|
|
1808
|
-
throw new Error(`findings_net_chained:${ord} (the finding sentence is a CONCLUSION, not a chain — this net carries ${markers.join(" and ")}. Answer the one question a reader asks of this finding: is this a problem for me. Name the parties and the territory and state the outcome as a likelihood
|
|
1808
|
+
throw new Error(`findings_net_chained:${ord} (the finding sentence is a CONCLUSION, not a chain — this net carries ${markers.join(" and ")}. Answer the one question a reader asks of this finding: is this a problem for me. Name the parties and the territory and state the outcome as a likelihood. Then MOVE the chain's clauses — territories, the goods paraphrase, the owner's business, status and use history — into legal_position and practical_position, in full: this is a relocation, never a compression, and a net that got shorter because the reasoning got thinner is the wrong fix.)`);
|
|
1809
1809
|
}
|
|
1810
1810
|
|
|
1811
1811
|
// The framework's own inputs, where it states a method. A finding with a band is a rated conflict and
|
|
@@ -2642,3 +2642,15 @@ export function blankForeignRecordLinks(doc, origins) {
|
|
|
2642
2642
|
});
|
|
2643
2643
|
return { doc: { ...doc, findings }, blanked, kept };
|
|
2644
2644
|
}
|
|
2645
|
+
|
|
2646
|
+
/**
|
|
2647
|
+
* THE TOP-LEVEL KEYS A FINDINGS DOCUMENT OF THIS schema_version MAY CARRY — the ladder the parsers hold it to,
|
|
2648
|
+
* read off the version as they read it. Exported for the synthesis transport, which reads a correction's
|
|
2649
|
+
* list-less findings object as registers only where every key it carries is on the STORED record's own ladder:
|
|
2650
|
+
* a key that ladder does not allow is refused either way, and it must meet the refusal it always met. Derived
|
|
2651
|
+
* from the ladder above, never copied, for the reason FINDING_KEYS_CURRENT is.
|
|
2652
|
+
*/
|
|
2653
|
+
export function topKeysForVersion(schemaVersion) {
|
|
2654
|
+
const v = typeof schemaVersion === "number" ? schemaVersion : 1;
|
|
2655
|
+
return [...topKeysFor({ v4: v >= 4, v5: v >= 5, v6: v >= 6, v7: v >= 7 })];
|
|
2656
|
+
}
|
package/driver/floor-duty.mjs
CHANGED
|
@@ -244,6 +244,15 @@ export function floorDutyBlock(artifact, { armed = false } = {}) {
|
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
/**
|
|
248
|
+
* THE FLOOR'S GAP POSITIVELY CLOSED, judged as `floorDutyBlock` judges it: the floor is armed, its account was
|
|
249
|
+
* computed, and no floor is left undischarged. An account that is absent, unreadable or not computable is no proof
|
|
250
|
+
* that the gap closed, so it is false here, where `floorDutyBlock` is null for the same account. PURE.
|
|
251
|
+
*/
|
|
252
|
+
export function floorDutyClosed(artifact, { armed = false } = {}) {
|
|
253
|
+
return armed === true && artifact?.computable === true && !((artifact?.undischarged_by_seat ?? 0) > 0);
|
|
254
|
+
}
|
|
255
|
+
|
|
247
256
|
export const FLOOR_DUTY_EVENT_FIELDS = ["floors", "accounted", "named_without_ground", "unanswered"];
|
|
248
257
|
|
|
249
258
|
/** The run.jsonl row. `computable:false` carries its reason and NO counts — a zero here would read as
|
package/driver/gate-metrics.mjs
CHANGED
|
@@ -148,6 +148,10 @@ export function scoredFetched(runDir) {
|
|
|
148
148
|
// un-normalised join here would miss ALL of them, not some — and a total miss reads as a clean
|
|
149
149
|
// `fetched: 0`, which is this metric's alert, not its absence.
|
|
150
150
|
const lines = readJson(join(runDir, "register-hit-list.json"))?.lines;
|
|
151
|
+
// NOT COMPUTED WHERE THE CODES ARE NOT THERE. The reading step is not asked for fate codes, and a list whose
|
|
152
|
+
// lines carry none says nothing about what judgment opened: read as a measurement, every scored finding would
|
|
153
|
+
// count as unfetched, an alarm about nothing. It says it could not be computed instead.
|
|
154
|
+
if (Array.isArray(lines) && lines.length && !lines.some((l) => l?.fate != null)) return { notComputed: "the hit list carries no fate codes" };
|
|
151
155
|
if (Array.isArray(lines)) {
|
|
152
156
|
// THREE STATES, NOT TWO, and the third is the one an earlier cut of this got wrong. A line the run
|
|
153
157
|
// deliberately did not open (`NOT_PICKED`) leaves the denominator. A line whose fate this file does
|
|
@@ -292,7 +296,9 @@ export function computeGateMetrics(runDirs) {
|
|
|
292
296
|
for (const d of runDirs) {
|
|
293
297
|
const dr = deferralsRendered(d); if (dr) { agg.deferralsRendered.num += dr.rendered; agg.deferralsRendered.den += dr.total; }
|
|
294
298
|
const sk = skepticConsumed(d); if (sk) { agg.skepticConsumed.num += sk.consumed; agg.skepticConsumed.den += sk.total; }
|
|
295
|
-
const sf = scoredFetched(d);
|
|
299
|
+
const sf = scoredFetched(d);
|
|
300
|
+
if (sf?.notComputed) agg.scoredFetched.notComputed = (agg.scoredFetched.notComputed ?? 0) + 1;
|
|
301
|
+
else if (sf) { agg.scoredFetched.num += sf.fetched; agg.scoredFetched.den += sf.total; }
|
|
296
302
|
const ct = clientTierMatch(d); if (ct) { agg.clientTierMatch.num += ct.match; agg.clientTierMatch.den += ct.total; }
|
|
297
303
|
const lt = lintFirstPassTax(d); if (lt) { lintTaxRuns++; lintTaxFailures += lt.initialFailures; if (!lt.initialFailures) lintCleanFirstPass++; }
|
|
298
304
|
const dg = documentGrowth(d); if (dg) { growthRuns++; growthTrips += dg.count; }
|
package/driver/gateway.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { MODEL_FILE as FRAME_DIFF_MODEL_FILE, PROSE_FILE as FRAME_DIFF_PROSE_FIL
|
|
|
36
36
|
import { MATTER_CONTEXT_FILE } from "./matter-frame-record.mjs";
|
|
37
37
|
import { MODEL_FILE as VARIANT_MODEL_FILE, PROSE_FILE as VARIANT_PROSE_FILE } from "./clearance-variants-record.mjs";
|
|
38
38
|
import { PROSE_FILE as REPORT_OVERVIEW_FILE } from "./report-overview-record.mjs";
|
|
39
|
-
import { NARRATIVE_FILE, FINDINGS_FILE, refusalsFor } from "./synthesis-record.mjs";
|
|
39
|
+
import { NARRATIVE_FILE, FINDINGS_FILE, refusalsFor, synthesisCallPaths } from "./synthesis-record.mjs";
|
|
40
40
|
import { FINDINGS_FILE as REGISTER_FINDINGS_FILE, refusalsFor as registerDigestRefusalsFor } from "./register-digest-record.mjs";
|
|
41
41
|
|
|
42
42
|
// artifact basename -> the tool that is now its only writer. A repair for one of these is a CALL, never a
|
|
@@ -2277,7 +2277,7 @@ export function correctiveMessage(baseMessage, attempt, lastFail, expectFile, {
|
|
|
2277
2277
|
`\n\nCORRECTION: ${budget}${requiredFileClause(inner, { names, toolWritten })}`;
|
|
2278
2278
|
return baseMessage +
|
|
2279
2279
|
`\n\nCORRECTION: your previous attempt failed file validation (${inner}). ${budget}Re-do the task and make sure ` +
|
|
2280
|
-
`${correctionHint(inner, { gridLedgerName: gridLedgerNameFor(expectFile), supplementalLane, undispatchable: frameDiffUndispatchable(expectFile, inner) })}. ` +
|
|
2280
|
+
`${correctionHint(inner, { gridLedgerName: gridLedgerNameFor(expectFile), supplementalLane, undispatchable: frameDiffUndispatchable(expectFile, inner), heldRecord: heldSynthesisRecord(files) })}. ` +
|
|
2281
2281
|
requiredFileClause(inner, { names, toolWritten });
|
|
2282
2282
|
}
|
|
2283
2283
|
|
|
@@ -2362,7 +2362,22 @@ export function frameDiffUndispatchable(expectFile, lastFail = "framediff_direct
|
|
|
2362
2362
|
// clearance writer's.
|
|
2363
2363
|
const CLEARANCE_FINDINGS_TOKEN_RE = /(?<!knockout_|knockoutassess_)findings?_/;
|
|
2364
2364
|
|
|
2365
|
-
|
|
2365
|
+
// THE WRITER'S RETRY, WHEN THE DRIVER HOLDS ITS RECORD. A save the tool accepted can still fail the stage's own check
|
|
2366
|
+
// of the written files, and the driver then holds the record that save left: the retry sends only the change, in the
|
|
2367
|
+
// patch the follow-ups above it ask for. A record nobody saved yet, or one whose version is being replaced, still
|
|
2368
|
+
// takes the complete save.
|
|
2369
|
+
const HELD_RECORD_RETRY = "Fix exactly that in ONE `record_synthesis` PATCH call, sending only the change: `findings_patch` carrying the complete corrected finding object(s), each with the `ordinal` it replaces, and `findings` carrying any register you corrected, complete, and never a `findings[]` list; leave the narrative prose unchanged";
|
|
2370
|
+
const VERSION_FAILURE_RE = /finding_legacy_scale_forbidden|schema_version/;
|
|
2371
|
+
/**
|
|
2372
|
+
* Does the driver hold an accepted synthesis record for the run these files belong to? Read from either of the files
|
|
2373
|
+
* the writer's call renders: the stage's own expected file is the narrative, and a caller may name the findings.
|
|
2374
|
+
*/
|
|
2375
|
+
export const heldSynthesisRecord = (files) => {
|
|
2376
|
+
const f = (Array.isArray(files) ? files : [files]).find((x) => typeof x === "string" && [NARRATIVE_FILE, FINDINGS_FILE].includes(basename(x)));
|
|
2377
|
+
return Boolean(f) && existsSync(synthesisCallPaths(dirname(f)).accepted);
|
|
2378
|
+
};
|
|
2379
|
+
|
|
2380
|
+
export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.json", supplementalLane = false, undispatchable = [], toolWritten = null, heldRecord = false } = {}) {
|
|
2366
2381
|
// The repair act, for the hints that name one. Kept to a single sentence and a single decision so a
|
|
2367
2382
|
// new hint has one thing to append rather than a branch to reproduce.
|
|
2368
2383
|
const resaveClause = toolWritten
|
|
@@ -2457,8 +2472,9 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2457
2472
|
`composite/level/dispute_type are RETIRED and forbidden), owner.registrations[] (ONE entry per ` +
|
|
2458
2473
|
`registration — never overwrite or transpose another's facts; a finding with no fetched record → []), the four ` +
|
|
2459
2474
|
`meters as {token, basis} (basis = verified-from-record | inferred-from-signal), quadrant {x,y} in [0,1], a ` +
|
|
2460
|
-
`typed source, a disposition, and a unique 1-based ordinal. The failed check was: ${tok}.${extra}
|
|
2461
|
-
|
|
2475
|
+
`typed source, a disposition, and a unique 1-based ordinal. The failed check was: ${tok}.${extra} ` +
|
|
2476
|
+
(heldRecord && !VERSION_FAILURE_RE.test(lastFail) ? HELD_RECORD_RETRY
|
|
2477
|
+
: `Fix exactly that, re-save the COMPLETE findings.json, and leave the narrative prose unchanged`);
|
|
2462
2478
|
} else if (/coverage_status_offenum/.test(lastFail)) {
|
|
2463
2479
|
// D1 — the off-enum PROSE Status cell (distinct from coverage_status_invalid, which is the JSON
|
|
2464
2480
|
// mirror's token): the defect lives in register-findings.md's own Coverage-ledger table, so the
|
|
@@ -2827,8 +2843,7 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2827
2843
|
? diagnosis +
|
|
2828
2844
|
`On this run the TOOLS own every band block: if the collapsed slice belongs to a DICTATED plan entry ` +
|
|
2829
2845
|
`(it carries a qid from the frozen plan), close it by calling register_execute_plan ONCE with the exact ` +
|
|
2830
|
-
`{"plan_path", "axis", "output_path"} from your stage instructions
|
|
2831
|
-
`(it takes no per-qid argument); it re-runs this axis's dictated entries and refreshes their blocks, ` +
|
|
2846
|
+
`{"plan_path", "axis", "output_path"} from your stage instructions; it re-runs this axis's dictated entries and refreshes their blocks, ` +
|
|
2832
2847
|
`qid-stamped. If the collapsed slice ` +
|
|
2833
2848
|
`was a judgment addition, RE-PROPOSE that EXACT slice through register_propose_supplemental with the same ` +
|
|
2834
2849
|
`predicate, term(s), nice_classes and regions — the tool re-executes it and merges the corrected block ` +
|
|
@@ -2878,9 +2893,8 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2878
2893
|
`the bridge killed it, so the band was never written. This is NOT a missing-structure defect and ` +
|
|
2879
2894
|
`NOT something you did wrong. Call register_execute_plan ONCE more with the exact ` +
|
|
2880
2895
|
`{"plan_path", "axis", "output_path"} from your stage instructions. If it is killed again, do ` +
|
|
2881
|
-
`NOT author band blocks by hand and do NOT narrate coverage you did not get —
|
|
2882
|
-
`
|
|
2883
|
-
`correct answer to a tool that will not return, and it is what the reader needs`;
|
|
2896
|
+
`NOT author band blocks by hand and do NOT narrate coverage you did not get — the driver's own call ` +
|
|
2897
|
+
`log records that the call never returned`;
|
|
2884
2898
|
} else if (/named_band_missing/.test(lastFail)) {
|
|
2885
2899
|
// 2026-07-14 (copper-keystone): the unit's .md narrated success but the band file was NEVER WRITTEN —
|
|
2886
2900
|
// the model skipped its one register_execute_plan call and fabricated the narrative. Unlike the
|
|
@@ -3516,7 +3530,7 @@ export function warmPatchMessage(lastFail, expectFile, { supplementalLane = fals
|
|
|
3516
3530
|
const band = join(dirname(files[0]), basename(files[0]).replace(/\.md$/i, "-band.json"));
|
|
3517
3531
|
return `You are RESUMING your own session for this stage — your prior work and inputs are already in your context. Do NOT redo your digest prose (${names} already passed validation).\n` +
|
|
3518
3532
|
`Your register tool call WAS MADE and never returned — it was still running when the bridge killed it, so ${band} was never written. Nothing about your turn was wrong. Call register_execute_plan ONCE more with the exact {"plan_path", "axis", "output_path"} from your original stage instructions (output_path = ${band}), then stop.\n` +
|
|
3519
|
-
`If it is killed again: do NOT author band blocks by hand and do NOT narrate coverage you did not obtain.
|
|
3533
|
+
`If it is killed again: do NOT author band blocks by hand and do NOT narrate coverage you did not obtain. The driver's own call log records that the call never returned.`;
|
|
3520
3534
|
}
|
|
3521
3535
|
// Sibling-JSON defects (WS-A coverage mirror / common-law grid): the FINDINGS file already passed
|
|
3522
3536
|
// its prose checks — telling the model to rewrite it "in full" on a patch turn risks degrading
|
|
@@ -3616,7 +3630,7 @@ export function warmPatchMessage(lastFail, expectFile, { supplementalLane = fals
|
|
|
3616
3630
|
? `Do NOT redo the work from scratch. This stage's own output is the driver's — it is rendered from your \`${outIsToolWritten.tool}\` call and you hold no tool that writes it, so there is nothing there for you to rewrite or protect.`
|
|
3617
3631
|
: `Do NOT redo the work from scratch, and do NOT rewrite ${names || "the findings file"} (it already passed validation).`;
|
|
3618
3632
|
return `You are RESUMING your own session for this stage — your prior work and inputs are already in your context. ${standsClause}\n` +
|
|
3619
|
-
`Your saved ${sibling} failed validation (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { gridLedgerName: gridLedgerNameFor(files), supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail) })}.\n` +
|
|
3633
|
+
`Your saved ${sibling} failed validation (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { gridLedgerName: gridLedgerNameFor(files), supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail), heldRecord: heldSynthesisRecord(files) })}.\n` +
|
|
3620
3634
|
`Re-save the COMPLETE corrected JSON at ${sib}. Do not stop until it exists and is valid.`;
|
|
3621
3635
|
}
|
|
3622
3636
|
// — A BLIND-FRAME REPAIR IS A TOOL CALL, NOT A FILE WRITE, and this branch is what stops the two
|
|
@@ -3668,7 +3682,7 @@ export function warmPatchMessage(lastFail, expectFile, { supplementalLane = fals
|
|
|
3668
3682
|
// DIRECTIVES again, which is the 2026-07-29 reopen loss. Caught by frame-diff-model.test.mjs's
|
|
3669
3683
|
// cardinality pin, which asserts the warm path carries the full list without the caller passing
|
|
3670
3684
|
// anything.
|
|
3671
|
-
: `What you sent did not pass (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail), toolWritten: toolWritten.tool })}.\n`) +
|
|
3685
|
+
: `What you sent did not pass (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail), toolWritten: toolWritten.tool, heldRecord: heldSynthesisRecord(files) })}.\n`) +
|
|
3672
3686
|
`Call \`${toolWritten.tool}\` with the values from the work already in your context — never by writing or editing any file. ` +
|
|
3673
3687
|
`Do not stop until the tool accepts the call.`;
|
|
3674
3688
|
}
|
|
@@ -3688,7 +3702,7 @@ export function warmPatchMessage(lastFail, expectFile, { supplementalLane = fals
|
|
|
3688
3702
|
}
|
|
3689
3703
|
const defective = failingTarget(lastFail, files) ?? files[0] ?? null;
|
|
3690
3704
|
return `You are RESUMING your own session for this stage — your prior work and inputs are already in your context. Do NOT redo the work from scratch.\n` +
|
|
3691
|
-
`Your saved ${defective ? rel(defective) : names || "output"} failed validation (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { gridLedgerName: gridLedgerNameFor(files), supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail) })}.\n` +
|
|
3705
|
+
`Your saved ${defective ? rel(defective) : names || "output"} failed validation (${lastFail}). Fix exactly this: ensure ${correctionHint(lastFail, { gridLedgerName: gridLedgerNameFor(files), supplementalLane, undispatchable: frameDiffUndispatchable(files, lastFail), heldRecord: heldSynthesisRecord(files) })}.\n` +
|
|
3692
3706
|
editRepairTail(defective || names || "the stage output");
|
|
3693
3707
|
}
|
|
3694
3708
|
|