clearotron 0.4.1 → 0.5.0-beta.0
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 +23 -0
- package/driver/ask-ledger.mjs +86 -15
- package/driver/band-shape.mjs +61 -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.mjs +3 -0
- 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 +2 -2
- package/driver/engine/mcp/band-server.mjs +77 -13
- package/driver/engine/mcp/dispositions-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +10 -6
- 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 +1945 -314
- 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 +89 -0
- 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/screen-gate.mjs +96 -0
- package/driver/skills/clearance-common-law/SKILL.md +8 -0
- package/driver/skills/clearance-register/SKILL.md +5 -5
- package/driver/skills/clearance-register/digest.md +14 -14
- package/driver/skills/clearance-register/unit.md +3 -3
- 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 +95 -60
- package/driver/suite-census.json +367 -19
- 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 +4 -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/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 +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/e2e-first-time.mjs +29 -12
package/driver/coverage-form.mjs
CHANGED
|
@@ -420,6 +420,8 @@ export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null
|
|
|
420
420
|
// it was not asked. The row arrives settled when the turn recorded that, and open when nobody did,
|
|
421
421
|
// so an unjudged family is an obligation the digest's gate refuses to pass. `family` rows stay out of
|
|
422
422
|
// the ledger the report is built from; the reason is the run's record and the audit workbook's.
|
|
423
|
+
// A row that arrives settled names the record its judgment came from (`reason_from`), so the union can keep
|
|
424
|
+
// that judgment the row's own: a later digest may add its reading beside it, never in place of it.
|
|
423
425
|
for (const f of (Array.isArray(awaiting) ? awaiting : [])) {
|
|
424
426
|
const qid = String(f?.qid ?? "").trim();
|
|
425
427
|
if (!qid || String(f?.axis ?? "").trim() !== axis) continue;
|
|
@@ -432,6 +434,7 @@ export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null
|
|
|
432
434
|
open: true,
|
|
433
435
|
open_because: "a waiting family the reading turn did not ask — it was never searched, and its only judgment is withheld-by-judgment with the reason it was not asked",
|
|
434
436
|
status: w?.reason ? "withheld-by-judgment" : null, reason: w?.reason ? String(w.reason) : null,
|
|
437
|
+
...(w?.reason ? { reason_from: `_driver/withheld-families-${String(w.axis ?? axis)}.json` } : {}),
|
|
435
438
|
});
|
|
436
439
|
}
|
|
437
440
|
// ── AN OFFICE THIS DEPLOYMENT COULD NOT REACH ────────────────────────────────────────
|
|
@@ -104,8 +104,17 @@ export function unionCoverageForm(prior, submitted, input, { parkedIds = null }
|
|
|
104
104
|
const p = findPrior(row), s = findSubmitted(row);
|
|
105
105
|
const sOk = s && rowIsSettled(s, row), pOk = p && rowIsSettled(p, row), dOk = rowIsSettled(row, row);
|
|
106
106
|
let fields;
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// A FAMILY THE READING TURN WITHHELD ARRIVES SETTLED WITH THE TURN'S OWN JUDGMENT, and that judgment stays the
|
|
108
|
+
// row's. Every later reader is told the row carries the turn's decision, so a digest that re-words it would
|
|
109
|
+
// make that untrue: its own reading is kept beside the turn's, as `digest_reason`, and never replaces it. A
|
|
110
|
+
// release takes the family out of the turn's record, so the next pass builds the row without that judgment
|
|
111
|
+
// and the digest settles it as any row.
|
|
112
|
+
if (row.kind === "family" && row.reason_from && dOk) {
|
|
113
|
+
fields = seatFields(row);
|
|
114
|
+
const theirs = sOk ? seatFields(s).reason : (p?.digest_reason ?? null);
|
|
115
|
+
if (theirs && theirs !== fields.reason) row.digest_reason = theirs;
|
|
116
|
+
} else if (sOk) fields = seatFields(s);
|
|
117
|
+
else if (pOk) fields = seatFields(p); else if (dOk) fields = seatFields(row); // a judgment the driver row arrived with
|
|
109
118
|
else {
|
|
110
119
|
const sf = seatFields(s ?? {}), pf = seatFields(p ?? {});
|
|
111
120
|
fields = { status: sf.status || pf.status, reason: sf.reason || pf.reason };
|
package/driver/digest-queue.mjs
CHANGED
|
@@ -156,7 +156,7 @@ export const DIGEST_OWN_PASS_EXEMPTIONS = Object.freeze([
|
|
|
156
156
|
// The standard digest-resume preamble every warm re-digest opens with (verbatim from the legacy
|
|
157
157
|
// escalation/envelope/frame-reopen followups — shared so the OFF path stays byte-identical).
|
|
158
158
|
export const DIGEST_RESUME_PREAMBLE =
|
|
159
|
-
"
|
|
159
|
+
"This is a fresh session: your earlier register-digest work is not in your context, and the unit files are among the inputs listed below. Your prior digest is the findings file named below. Do NOT redo it from scratch.";
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
162
|
* The standard settlement repair contract: every consolidated flush ends with THIS.
|
|
@@ -881,7 +881,7 @@ export function callAnswer({ accepted, refused, overflow }, outstanding) {
|
|
|
881
881
|
const owed = !r.evidence_owed ? ""
|
|
882
882
|
: r.evidence_unsatisfiable === true
|
|
883
883
|
? " — this row CANNOT be evidenced: no passage on its candidates is long enough to quote from, so no `fragment` will ever bind. Do not keep re-sending it. Rule it and send `obstacle` saying the snippet carries nothing quotable."
|
|
884
|
-
: (r.ruled ? " — RULED, but this row now also needs `segment_index` (the number of the passage you relied on, from the ruled receipt's numbered passages)
|
|
884
|
+
: (r.ruled ? " — RULED, but this row now also needs `segment_index` (the number of the passage you relied on, from the ruled receipt's numbered passages)"
|
|
885
885
|
: " — needs `segment_index` (the number of the passage you rely on, from the ruled receipt's numbered passages) and `fragment` (a few characters copied exactly out of that passage)");
|
|
886
886
|
lines.push(` ${name(r)}${owed}`);
|
|
887
887
|
}
|
|
@@ -94,8 +94,9 @@ export function renderDocumentCoverageSection(summary) {
|
|
|
94
94
|
: `**Registry documents were retrieved for this search:** ${s.recordsWithBody} record`
|
|
95
95
|
+ `${s.recordsWithBody === 1 ? "" : "s"}${s.recordsTotal != null && s.recordsTotal !== s.recordsWithBody
|
|
96
96
|
? ` of the ${s.recordsTotal} held for this matter` : ""} carr${s.recordsWithBody === 1 ? "ies" : "y"} `
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// Holding a record is not reading it: a judging step opens few of the records a register holds, so the
|
|
98
|
+
// sentence says what was retrieved and claims no reading (the owner's approved wording, 2026-10-18).
|
|
99
|
+
+ "the document itself.");
|
|
99
100
|
if (s.findingsOnRecord != null && s.recordsWithBody !== 0)
|
|
100
101
|
parts.push(s.findingsOnRecord === 0
|
|
101
102
|
? "No finding in this report rests on wording read from a retrieved document; where a specification "
|
|
@@ -203,8 +203,8 @@ export function narrowingOrderLine(job, profile, inScopeClasses = null) {
|
|
|
203
203
|
`the major markets (${major.join(", ")})`,
|
|
204
204
|
].filter(Boolean).join(", else ")}`;
|
|
205
205
|
return `NARROWING A CROWD, in this order: first the goods and the classes, the client's goods words in `
|
|
206
|
-
+ `${inScopeClasses ? `the order's classes [${inScopeClasses}]` : "the order's classes"}, then
|
|
207
|
-
+ `
|
|
206
|
+
+ `${inScopeClasses ? `the order's classes [${inScopeClasses}]` : "the order's classes"}, then one of those classes at a time; `
|
|
207
|
+
+ `still a crowd, then the markets, ${markets}. From the narrowed lists, read what a lawyer would raise. `
|
|
208
208
|
+ `What was counted and not read stays on its coverage row with its count: set aside, never clean.`;
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -21,10 +21,12 @@ import { join, dirname } from "node:path";
|
|
|
21
21
|
import { driverDir } from "../../../shared/driver-dir.mjs"; //
|
|
22
22
|
import { serve } from "./stdio-server.mjs";
|
|
23
23
|
import { answerBudget, quotedBytes } from "../../../shared/answer-parts.mjs";
|
|
24
|
-
import { classifyRecord, prepareTargets, SHAPE_TIERS, isLiveRecord, niceClassKey,
|
|
24
|
+
import { classifyRecord, prepareTargets, SHAPE_TIERS, isLiveRecord, niceClassKey, withoutPlacementOrders } from "../../band-shape.mjs";
|
|
25
25
|
import { recordQids } from "../../named-band.mjs";
|
|
26
26
|
import { normalizeRecordUri } from "../../registry-fidelity.mjs";
|
|
27
|
-
import { bandGoodsIndex, goodsFor, goodsWordCounter, goodsWordsMatched, rawGoodsCarried, untaggedIsEnglish } from "../../record-goods.mjs";
|
|
27
|
+
import { bandGoodsIndex, goodsFor, goodsWordCounter, goodsWordsMatched, rawGoodsCarried, untaggedIsEnglish, rowGoods } from "../../record-goods.mjs";
|
|
28
|
+
import { recordGoodsEvidence, heldRecordProvenance, recordFetchOutcomes, readRunCallRows, goodsFromLabel } from "../../goods-evidence.mjs";
|
|
29
|
+
import { DEFAULT_LEDGER_PATH } from "../../provider-usage.mjs";
|
|
28
30
|
|
|
29
31
|
const RUN_DIR = process.env.CLEAROTRON_BAND_RUN_DIR || "";
|
|
30
32
|
const SESSION = process.env.CLEAROTRON_GATHER_SESSION_KEY || "";
|
|
@@ -299,14 +301,31 @@ const shapeGoodsTest = () => {
|
|
|
299
301
|
testCache = { words, matched: goodsWordsMatched(words, { stems: true }) };
|
|
300
302
|
return testCache;
|
|
301
303
|
};
|
|
302
|
-
// The record's body first;
|
|
303
|
-
|
|
304
|
+
// The record's body first; where the run holds none, the statements the row carries, as the shape reads them
|
|
305
|
+
// (`rowGoods`, a statement the register cut marked so); a row that carries its goods as one string stands in last.
|
|
306
|
+
const goodsOfRow = (r) => goodsFor(goodsIndex(), r) ?? rowGoods(r)
|
|
304
307
|
?? (typeof r?.goods === "string" && r.goods.trim() ? [{ classes: [], text: r.goods.trim(), ...(untaggedIsEnglish(r?.record_id) ? {} : { en: false }) }] : null);
|
|
308
|
+
// WHAT A RECORD'S GOODS REST ON, from the run's own files and its fetches, by the shared reader's one rule
|
|
309
|
+
// (goods-evidence.mjs `recordGoodsEvidence`): the supporting evidence the record holds, never the last call made
|
|
310
|
+
// for it. A fetch counts as failed only where the register's call ledger records it so; missing telemetry is not a
|
|
311
|
+
// recorded failure. Read once per server, as the band and the goods index are.
|
|
312
|
+
let evidenceCache = null;
|
|
313
|
+
const runEvidence = () => evidenceCache ??= (() => {
|
|
314
|
+
let prefix = null;
|
|
315
|
+
try { const st = loadJson(join(RUN_DIR, "status.json")); if (st?.slug && st?.codename) prefix = `clearance-${st.slug}-${st.codename}-`; } catch { prefix = null; }
|
|
316
|
+
const calls = prefix ? readRunCallRows(DEFAULT_LEDGER_PATH, prefix) : { rows: [], error: "no run prefix" };
|
|
317
|
+
return { held: heldRecordProvenance(RUN_DIR), fetches: calls.error ? null : recordFetchOutcomes(calls.rows, { runPrefix: prefix }) };
|
|
318
|
+
})();
|
|
319
|
+
const evidenceOfRow = (r) => {
|
|
320
|
+
const { held, fetches } = runEvidence();
|
|
321
|
+
return recordGoodsEvidence(r, { goods: { of: goodsOfRow, held: (x) => goodsFor(goodsIndex(), x) != null }, held, fetches });
|
|
322
|
+
};
|
|
305
323
|
const withGoods = (r) => {
|
|
306
324
|
const { words, matched } = shapeGoodsTest();
|
|
307
325
|
const m = words.length ? matched(goodsOfRow(r)) : null;
|
|
308
326
|
const { goods: _text, ...row } = project(r); // a row's own goods text stands in for the test, and is not served
|
|
309
|
-
return { ...row, goods_words: m == null ? null : `${m.length} of ${words.length}${m.length ? `: ${m.join(", ")}` : ""}
|
|
327
|
+
return { ...row, goods_words: m == null ? null : `${m.length} of ${words.length}${m.length ? `: ${m.join(", ")}` : ""}`,
|
|
328
|
+
goods_from: goodsFromLabel(evidenceOfRow(r)) };
|
|
310
329
|
};
|
|
311
330
|
|
|
312
331
|
// A GROUP, PAGED AS THE SHAPE LISTS IT. The shape lists a group of a thousand records or more by its first
|
|
@@ -525,14 +544,59 @@ function bandRecord(params = {}) {
|
|
|
525
544
|
// It stays an error, it stays in the reading audit with the reason that made it one, and it still says
|
|
526
545
|
// what IS on file: the deferral this tool reports must stay visible for the real failures, or a dead
|
|
527
546
|
// document reads back as a record that never had one.
|
|
528
|
-
logRead("band_record", { record_id: uri }, { ok: false, reason: hit.miss });
|
|
529
547
|
// The uri grammar is the same one screen-gate reads, and it does not cover every segment shape (a
|
|
530
548
|
// 7+ character office segment parses nowhere). So this message never tells the model its uri is
|
|
531
549
|
// wrong: it says what the resolver could not do, and what to do about it either way.
|
|
532
|
-
|
|
550
|
+
//
|
|
551
|
+
// WHAT IS ON FILE INSTEAD. Where the register's search answer listed goods for the record, they are served here
|
|
552
|
+
// with the miss, each statement with its source and completeness, in parts as an official record's text is: a
|
|
553
|
+
// whole statement on the search answer can decide a line's group, and no other tool serves its text. A fetch
|
|
554
|
+
// that the register's call ledger records as failed is said to have failed; nothing else is.
|
|
555
|
+
// A cite without its leading slash resolves on a hit; the miss reads it the same way, or it would lose the failed
|
|
556
|
+
// fetch and the search answer's goods that the cite's record has.
|
|
557
|
+
const parsed = hit.miss !== "unparsable-uri";
|
|
558
|
+
const key = normalizeRecordUri(uri) || normalizeRecordUri(`/${String(uri ?? "").trim().replace(/^\/+/, "")}`);
|
|
559
|
+
const bandRow = parsed && key ? bandRowFor(key) : null;
|
|
560
|
+
const statements = bandRow ? rowGoods(bandRow) : null;
|
|
561
|
+
const fetched = parsed ? runEvidence().fetches : null;
|
|
562
|
+
const fetch = fetched && key ? (fetched.get(key) ?? fetched.get(key.split("/").pop())) : null;
|
|
563
|
+
const failed = Boolean(fetch?.failed && !fetch.ok);
|
|
564
|
+
const why = !parsed
|
|
533
565
|
? `"${uri}" did not parse as a record reference (expected /mark/<cc>/<id> — a full provider URL carrying one is fine) and no archived record matches it exactly. If the uri IS right, its shape is outside this tool's grammar: state the gap, never read it as a clean.`
|
|
534
|
-
:
|
|
535
|
-
|
|
566
|
+
: statements
|
|
567
|
+
? `${failed ? `No official record is on file for "${uri}": a fetch of it failed this run.` : `No official record is on file for "${uri}".`} The register's search answer listed these goods. Their source and completeness are shown with each statement.`
|
|
568
|
+
: failed ? `No official record is on file for "${uri}": a fetch of it failed this run.` : `No official record fetched for "${uri}" this run.`;
|
|
569
|
+
const rest = `${available.length ? `Records on file (${available.length}): ${available.slice(0, 40).join(", ")}${available.length > 40 ? ", …" : ""}.` : "No records fetched yet."} This tool is read-only — it never fetches; a record you need but do not have is an honest gap to state, not a reason to guess.`;
|
|
570
|
+
if (!statements) {
|
|
571
|
+
logRead("band_record", { record_id: uri }, { ok: false, reason: hit.miss, ...(failed ? { fetch_failed: true } : {}) });
|
|
572
|
+
return { isError: true, text: `${why} ${rest}` };
|
|
573
|
+
}
|
|
574
|
+
const goodsLines = statements.map((g) => JSON.stringify({ class: g.classes.join(",") || null, text: g.text,
|
|
575
|
+
goods_from: g.cut ? "search answer, cut by the register" : g.unknown ? "search answer, completeness not established" : "search answer" }));
|
|
576
|
+
const text = [why, ...goodsLines, rest].join("\n");
|
|
577
|
+
const parts = splitAtLines(text, RESPONSE_CHARS);
|
|
578
|
+
const wanted = Number(params.part ?? 1);
|
|
579
|
+
const part = Number.isInteger(wanted) && wanted >= 1 && wanted <= parts.length ? wanted : 1;
|
|
580
|
+
logRead("band_record", { record_id: uri }, { ok: false, reason: hit.miss, served: "search-answer-goods", bytes: text.length,
|
|
581
|
+
...(parts.length > 1 ? { part, parts: parts.length } : {}), ...(failed ? { fetch_failed: true } : {}) });
|
|
582
|
+
if (parts.length === 1) return { isError: true, text };
|
|
583
|
+
return { isError: true, text: `[record ${uri} — part ${part}/${parts.length}]\n` + parts[part - 1]
|
|
584
|
+
+ (part < parts.length ? `\n[end of part ${part}/${parts.length} — call band_record again with {"record_id": "${uri}", "part": ${part + 1}}]` : "") };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// The band row a record id names, from the band file, read once per server. null where the band holds none.
|
|
588
|
+
let bandRowsCache = null;
|
|
589
|
+
function bandRowFor(uri) {
|
|
590
|
+
if (!bandRowsCache) {
|
|
591
|
+
bandRowsCache = new Map();
|
|
592
|
+
try {
|
|
593
|
+
for (const r of loadJson(bandPath())?.enumerated ?? []) {
|
|
594
|
+
const k = normalizeRecordUri(r?.record_id);
|
|
595
|
+
if (k && !bandRowsCache.has(k)) bandRowsCache.set(k, r);
|
|
596
|
+
}
|
|
597
|
+
} catch { /* no band on file: nothing to serve beside the miss */ }
|
|
598
|
+
}
|
|
599
|
+
return bandRowsCache.get(normalizeRecordUri(uri)) ?? null;
|
|
536
600
|
}
|
|
537
601
|
|
|
538
602
|
// THE SHAPE'S GROUP PAGES ARE PLACEMENT'S. Placement owes every line a group lists a fate, so it is offered the
|
|
@@ -543,10 +607,10 @@ function bandRecord(params = {}) {
|
|
|
543
607
|
// arrives anyway is served as before: the offer changes, not what the tool answers.
|
|
544
608
|
const GROUP_PAGES = process.env.CLEAROTRON_BAND_GROUPS === "1";
|
|
545
609
|
// The same holds for the shape's own orders under the groups: placement reads them, and every other stage is
|
|
546
|
-
// served the shape without them (
|
|
610
|
+
// served the shape without them (the owner's ruling, 2026-10-14). Each is removed with the space before it, inside
|
|
547
611
|
// its line, so no line moves and the line each listed mark is printed on, which `linesByPart` reads, still holds.
|
|
548
|
-
|
|
549
|
-
const LOOKUP_DESCRIPTION = "Look up enumerated records in this run's frozen register band (read-only; logged to the reading audit). Filters AND together: record_id (exact uri), owner (substring), nice_class, tier (identical|near-identical|same-family|other|unclassifiable — classified against the shape's frozen targets), qid (exact plan-entry id — the plan-execution ledger join; a record surfaced by several slices matches under EVERY one of them), query (the _query slice that surfaced the record), text (substring of mark_text), status (substring), live_only, goods (words: records whose goods contain any of them, as whole words, singular or plural; a record with no goods on file matches too), group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), limit (default 20, max 100), offset (default 0: where in the matching records or lines to start — a capped answer names the next offset, so a large band or group is read page by page). On each record served, `goods_words` gives the client's goods words its goods hold, as the shape tests them (k of N: the words), or null where its goods cannot be read; the goods text is in band_record. Matching un-enumerated crowd descriptors ride along so a lookup can never mistake a counted-only zone for a clean.";
|
|
612
|
+
// (band-shape.mjs `withoutPlacementOrders` removes them, with the shape's other placement-only orders and its group-page commands.)
|
|
613
|
+
const LOOKUP_DESCRIPTION = "Look up enumerated records in this run's frozen register band (read-only; logged to the reading audit). Filters AND together: record_id (exact uri), owner (substring), nice_class, tier (identical|near-identical|same-family|other|unclassifiable — classified against the shape's frozen targets), qid (exact plan-entry id — the plan-execution ledger join; a record surfaced by several slices matches under EVERY one of them), query (the _query slice that surfaced the record), text (substring of mark_text), status (substring), live_only, goods (words: records whose goods contain any of them, as whole words, singular or plural; a record with no goods on file matches too), group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), limit (default 20, max 100), offset (default 0: where in the matching records or lines to start — a capped answer names the next offset, so a large band or group is read page by page). On each record served, `goods_words` gives the client's goods words its goods hold, as the shape tests them (k of N: the words), or null where its goods cannot be read; `goods_from` says what those goods rest on: official record; official record, cut by the register; official record, completeness not established; official record, no goods listed; search answer; search answer, cut by the register; search answer, completeness not established; fetch failed; or nothing held. The goods text is in band_record. Matching un-enumerated crowd descriptors ride along so a lookup can never mistake a counted-only zone for a clean.";
|
|
550
614
|
const LOOKUP_GROUP_CLAUSE = "group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), ";
|
|
551
615
|
const LOOKUP_OFFSET_GROUPS = "offset (default 0: where in the matching records or lines to start — a capped answer names the next offset, so a large band or group is read page by page)";
|
|
552
616
|
const LOOKUP_OFFSET_BAND = "offset (default 0: where in the matching records to start — a capped answer names the next offset, so a large band is read page by page)";
|
|
@@ -579,7 +643,7 @@ serve({
|
|
|
579
643
|
},
|
|
580
644
|
{
|
|
581
645
|
name: "band_record",
|
|
582
|
-
description: "The OFFICIAL registry record the driver fetched into this run's _records/ for a given record_id (/mark/... uri) — the fetched document itself, for detail the band row does not carry. Cite the uri in whatever form the band, the findings or the provider gave you: case, a wrapping provider URL and the registration-instance suffix are all resolved to the same document. Read-only: it never fetches
|
|
646
|
+
description: "The OFFICIAL registry record the driver fetched into this run's _records/ for a given record_id (/mark/... uri) — the fetched document itself, for detail the band row does not carry. Cite the uri in whatever form the band, the findings or the provider gave you: case, a wrapping provider URL and the registration-instance suffix are all resolved to the same document. Read-only: it never fetches. An unfetched record returns an honest miss listing what is on file, with the goods the register's search answer listed for it where it listed any; a record that IS on file but cannot be opened says so — both are gaps to state, never records without documents. Every call is logged to the reading audit.",
|
|
583
647
|
inputSchema: { type: "object", required: ["record_id"], properties: { record_id: { type: "string", description: "the /mark/<cc>/<id> uri" }, part: { type: "number", description: "1-based part number when an oversize record is served in parts (default 1)" } } },
|
|
584
648
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
585
649
|
handler: bandRecord,
|
|
@@ -128,7 +128,7 @@ serve({
|
|
|
128
128
|
name: "dispositions", version: "0.1.0",
|
|
129
129
|
tools: [{
|
|
130
130
|
name: "record_dispositions",
|
|
131
|
-
description: "Record your meaning rulings. Send VALUES, not a file: one entry per row, and the driver writes the document. You type NO identifier of any kind: give `row_index`, the NUMBER printed beside that obligation in the obligations block, and `receipt_index`, the POSITION of the candidate in that row's own list — the driver resolves both. Rows are validated against THAT row as they arrive, so you learn what to fix inside this turn; rows that validate are kept even when others in the same call are refused. The answer names every obligation still outstanding and which of them still owe proof of reading — a `segment_index
|
|
131
|
+
description: "Record your meaning rulings. Send VALUES, not a file: one entry per row, and the driver writes the document. You type NO identifier of any kind: give `row_index`, the NUMBER printed beside that obligation in the obligations block, and `receipt_index`, the POSITION of the candidate in that row's own list — the driver resolves both. Rows are validated against THAT row as they arrive, so you learn what to fix inside this turn; rows that validate are kept even when others in the same call are refused. The answer names every obligation still outstanding and which of them still owe proof of reading — a `segment_index`.",
|
|
132
132
|
inputSchema: { type: "object", required: ["grid_spec_path", "rows"], properties: {
|
|
133
133
|
grid_spec_path: { type: "string", description: "Absolute path to the driver-written grid spec — the same one the grid tool was given." },
|
|
134
134
|
rows: {
|
|
@@ -156,7 +156,7 @@ serve({
|
|
|
156
156
|
// — THE HONEST EXIT. Declared here because a seat can only take an exit it can see: the
|
|
157
157
|
// one row that killed a production run was refused 217 times by a seat that had no way to say
|
|
158
158
|
// it could not rule, and the schema is the half its tool-calling binds to.
|
|
159
|
-
obstacle: { type: "string", description: "ONLY when this receipt
|
|
159
|
+
obstacle: { type: "string", description: "ONLY when this receipt is genuinely impossible to evidence — every passage an elision marker, nothing quotable in any of them: one line saying what stops you. Send this ALONGSIDE your `ruling` and `note`, never instead of them. The row is then recorded as undecided, with your sentence shown to the reviewing lawyer in place of proof, and the stage completes rather than stalling on it. This is not a way past a hard row: `obstacle` is for the receipt that offers nothing, not the awkward one." },
|
|
160
160
|
} },
|
|
161
161
|
},
|
|
162
162
|
} },
|
|
@@ -1082,7 +1082,7 @@ serve({
|
|
|
1082
1082
|
findings_patch: {
|
|
1083
1083
|
type: "array",
|
|
1084
1084
|
description:
|
|
1085
|
-
"A TARGETED CORRECTION, and the shape the repair prompts ask for. Send the complete corrected finding object(s), each carrying the `ordinal` of the finding it replaces, and send NOTHING else — no `findings
|
|
1085
|
+
"A TARGETED CORRECTION, and the shape the repair prompts ask for. Send the complete corrected finding object(s), each carrying the `ordinal` of the finding it replaces, and send NOTHING else — no `findings[]` list, no `narrative` unless you also corrected a section, and a `findings` object only to carry the registers you changed or the \"corrections\" marker. The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. It cannot remove a finding: an ordinal names one to replace, and there is no way to express a deletion. Sending a `findings` document that carries its `findings[]` list instead REPLACES the record, which is how fifteen findings were lost once — if you mean to correct four findings, send four rows here, not a document containing four.",
|
|
1086
1086
|
items: {
|
|
1087
1087
|
type: "object",
|
|
1088
1088
|
required: ["ordinal"],
|
|
@@ -1109,7 +1109,11 @@ serve({
|
|
|
1109
1109
|
narrative: {
|
|
1110
1110
|
type: "object",
|
|
1111
1111
|
description: "The cross-finding read, as sections. This is the material no other artifact on the run holds — NOT a second write-up of each finding, which lives on the report card. On a first call, send it complete. On a correction, send only the sections you changed — the rest are held.",
|
|
1112
|
-
required
|
|
1112
|
+
// NO `required` HERE EITHER. It refused a correction that sent only the sections it changed — the save
|
|
1113
|
+
// this description and every repair prompt ask for — before the save code saw the call, and such a
|
|
1114
|
+
// refusal left no call file, only a line in the server's log. The save code still refuses a FIRST
|
|
1115
|
+
// call without the spine or the verdict, by name (`synthesis_spine_missing`, `synthesis_verdict_missing`),
|
|
1116
|
+
// and a patch keeps the held ones.
|
|
1113
1117
|
properties: {
|
|
1114
1118
|
spine: { type: "string", description: "The dominant-element spine: what carries these marks and what a register would weigh. The core of the product." },
|
|
1115
1119
|
verdict: { type: "string", description: "The verdict prose — what the findings together mean for this client." },
|
|
@@ -1133,7 +1137,7 @@ serve({
|
|
|
1133
1137
|
findings: {
|
|
1134
1138
|
type: "object",
|
|
1135
1139
|
description:
|
|
1136
|
-
"The findings document — schema_version, rated_under_framework, findings[], coverage[], ask_answers[] and the other top-level registers. Already typed values; it arrives here instead of being written as a file so the joins below can run, and it is validated through the shipped parser, which names its own defect tokens. " +
|
|
1140
|
+
"The findings document — schema_version, rated_under_framework, findings[], coverage[], ask_answers[] and the other top-level registers. On a first call, send it whole. To correct part of the record, send no `findings[]` list (a finding is corrected through `findings_patch`) and only the registers you changed, each complete, with the \"corrections\" marker when the repair asks for it: every register you do not send is held. A `findings` object that carries a `findings[]` list replaces the whole record; so does one that changes `schema_version` or `rated_under_framework`, which must then carry its list. Already typed values; it arrives here instead of being written as a file so the joins below can run, and it is validated through the shipped parser, which names its own defect tokens. " +
|
|
1137
1141
|
"TWO OF ITS FIELDS ALSO RENDER INTO THE NARRATIVE, which is why they are here and not on `narrative`: `coverage[]` becomes the readable coverage list (rows are {area, state, note}; `confirmed-clean` ASSERTS a search ran and is joined to this run's plan-execution receipt, so a clean claim over a slice that did not run is refused — name the area as the receipt names the slice, and the honest alternatives are coverage-limited and not-searched), and `ask_answers[]` becomes the labelled answer lines ({ask, answer}: the ask VERBATIM because the driver joins on it, and the ANSWER ALONE starting at its first word — the driver writes the \"- You asked: <ask> → \" label, so an answer that repeats the ask ships the question to the client twice).",
|
|
1138
1142
|
},
|
|
1139
1143
|
},
|
|
@@ -1165,7 +1169,7 @@ serve({
|
|
|
1165
1169
|
chunkSummary: {
|
|
1166
1170
|
type: "string",
|
|
1167
1171
|
description:
|
|
1168
|
-
"
|
|
1172
|
+
"The cross-mark read covering THIS chunk's marks: a short opening line for what the batch as a whole shows, then one \"## <MARK NAME>\" sub-header per mark with its band and the one or two things that drive it, bullets where they help. EVERY chunk emits one: " +
|
|
1169
1173
|
"the whole-batch executive summary is composed in code by concatenating them, so a chunk " +
|
|
1170
1174
|
"without one vanishes from the summary a client reads.",
|
|
1171
1175
|
},
|
|
@@ -1207,7 +1211,7 @@ serve({
|
|
|
1207
1211
|
registerEstimate: { type: "string" },
|
|
1208
1212
|
parodyNote: { type: ["string", "null"] },
|
|
1209
1213
|
crowdedField: { type: "boolean" },
|
|
1210
|
-
purpleNotes: { type: "array", items: { type: "string" }, description: "
|
|
1214
|
+
purpleNotes: { type: "array", items: { type: "string" }, description: "Reviewer notes appear on the report page and are removed on export." },
|
|
1211
1215
|
// — DECLARED HERE OR IT IS FOLKLORE.
|
|
1212
1216
|
//
|
|
1213
1217
|
// Both fields were built end to end for — instructed in the dispatch,
|
|
@@ -1409,7 +1413,7 @@ serve({
|
|
|
1409
1413
|
inUseAs: { type: "string", description: "The kinds of use off the register that could conflict in this client's field, as one phrase completing \"Is this name already in use as …?\". The places the batch is searched on include the sites where these uses are listed." },
|
|
1410
1414
|
places: {
|
|
1411
1415
|
type: "array", items: { type: "string" },
|
|
1412
|
-
description: "
|
|
1416
|
+
description: "THE PLACES ARE PER NAME (task 2d) and go on each mark, not here.",
|
|
1413
1417
|
},
|
|
1414
1418
|
umbrellaBrandNote: { type: "string", description: "A note where an umbrella brand is in play, or omit it." },
|
|
1415
1419
|
executionOrder: {
|
|
@@ -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
|
|