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
|
@@ -42,17 +42,18 @@
|
|
|
42
42
|
// prose, so the same check had nothing to run on. They are typed now, so it runs: a `confirmed-clean`
|
|
43
43
|
// row whose area does not join the plan-execution receipt is refused AT THE CALL, in the turn where
|
|
44
44
|
// restating it is free, rather than at a gate whose only repair is a forced re-ask of the whole stage.
|
|
45
|
-
import { writeFileSync, appendFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
|
|
45
|
+
import { writeFileSync, appendFileSync, mkdirSync, readFileSync, existsSync, renameSync, rmSync } from "node:fs";
|
|
46
46
|
import { captureCall, stampVerdict } from "./call-capture.mjs";
|
|
47
47
|
import { join, dirname } from "node:path";
|
|
48
48
|
import { driverDir } from "../shared/driver-dir.mjs";
|
|
49
|
-
import { parseFindingsJson, COVERAGE_AREA_STATES } from "./findings-model.mjs";
|
|
49
|
+
import { parseFindingsJson, COVERAGE_AREA_STATES, topKeysForVersion } from "./findings-model.mjs";
|
|
50
50
|
import { readFrozenMethod, FROZEN_METHOD_FILE } from "./framework-method.mjs";
|
|
51
51
|
import { findCoverageRecommendations } from "./verify.mjs";
|
|
52
52
|
import { declinationCallPaths, readDeclinations } from "./declination-tool.mjs"; // — the seat's own declines, read by the driver never asserted by the seat
|
|
53
53
|
import { reconcileDeclinationDuty, declinationDutyRefusal } from "./declination-duty.mjs";
|
|
54
54
|
import { findingUris } from "./record-carry.mjs"; // one derivation of "which records did the findings name", called not copied
|
|
55
55
|
import { foldAfterSave } from "./record-fold.mjs"; // once the delivery seam is passed, the saved record is folded again
|
|
56
|
+
import { runLog } from "./log.mjs"; // what the transport read a call as goes to the run's record, never to the model's answer
|
|
56
57
|
import { recordOriginsOr, blankOnRepeat, recordBlanking, FOREIGN_HOST_TOKEN } from "./foreign-record-links.mjs"; // a foreign record link: refused once, then blanked
|
|
57
58
|
export const NARRATIVE_FILE = "narrative.md";
|
|
58
59
|
export const FINDINGS_FILE = "findings.json";
|
|
@@ -440,6 +441,267 @@ export function lastAcceptedCall(runDir) {
|
|
|
440
441
|
} catch { return null; }
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
// ── THE BASE A LATER PATCH BUILDS ON IS HELD, PUT BACK AND REPAIRED WITH THE RECORD ──────────────────────
|
|
445
|
+
//
|
|
446
|
+
// A corrective pass that saves and is then rolled back used to put back findings.json alone. narrative.md
|
|
447
|
+
// kept the failed pass's text, and the merge base kept the failed pass's record, so the next patch merged onto
|
|
448
|
+
// a record the rollback had discarded and quietly applied it again. The three functions below hold the
|
|
449
|
+
// narrative and the base beside the findings snapshot, put them back, and let the driver write a repair into
|
|
450
|
+
// the base, so a restore the next patch cannot see is not dropped by it.
|
|
451
|
+
|
|
452
|
+
const writeAtomic = (file, text) => {
|
|
453
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
454
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
455
|
+
writeFileSync(tmp, text);
|
|
456
|
+
renameSync(tmp, file);
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
/** The merge base and the narrative as they stand: each file's bytes, or null where it is absent or unreadable. Never throws. */
|
|
460
|
+
export function snapshotSynthesisBase(runDir) {
|
|
461
|
+
const dir0 = String(runDir ?? "");
|
|
462
|
+
const bytes = (at) => { try { return readFileSync(at, "utf8"); } catch { return null; } };
|
|
463
|
+
return { accepted: bytes(synthesisCallPaths(dir0).accepted), narrative: bytes(join(dir0, NARRATIVE_FILE)) };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Put back what `snapshotSynthesisBase` held. Never throws. Returns what happened to each file — "restored",
|
|
468
|
+
* "removed", "unchanged" or "failed" — or null when there is no snapshot, which leaves both files as they are.
|
|
469
|
+
*
|
|
470
|
+
* A snapshot that held NO base removes the one written since, so a later patch is refused for having no base
|
|
471
|
+
* rather than built on the pass that was discarded. A snapshot that held no narrative leaves the file as it
|
|
472
|
+
* stands: a run with no narrative.md reads as one whose synthesis never finished, and the rollback exists to
|
|
473
|
+
* deliver.
|
|
474
|
+
*/
|
|
475
|
+
export function restoreSynthesisBase(runDir, snap) {
|
|
476
|
+
if (!snap || typeof snap !== "object") return null;
|
|
477
|
+
const dir0 = String(runDir ?? "");
|
|
478
|
+
const putBack = (at, held, { removeWhenAbsent }) => {
|
|
479
|
+
try {
|
|
480
|
+
let now = null;
|
|
481
|
+
try { now = readFileSync(at, "utf8"); } catch { now = null; }
|
|
482
|
+
if (held == null) {
|
|
483
|
+
if (!removeWhenAbsent || now == null) return "unchanged";
|
|
484
|
+
rmSync(at, { force: true });
|
|
485
|
+
return "removed";
|
|
486
|
+
}
|
|
487
|
+
if (now === held) return "unchanged";
|
|
488
|
+
writeAtomic(at, held);
|
|
489
|
+
return "restored";
|
|
490
|
+
} catch { return "failed"; }
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
accepted: putBack(synthesisCallPaths(dir0).accepted, snap.accepted, { removeWhenAbsent: true }),
|
|
494
|
+
narrative: putBack(join(dir0, NARRATIVE_FILE), snap.narrative, { removeWhenAbsent: false }),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* A DRIVER EDIT TO THE MERGE BASE, judged before it is written. Never throws.
|
|
500
|
+
*
|
|
501
|
+
* `edit` is PURE: it takes the base's findings record, the model's own in its numbering, and returns the record
|
|
502
|
+
* to hold instead. The edited call is judged by `acceptSynthesis` with the run's own reads, as a call is, so the
|
|
503
|
+
* base stays what it has always been: the last record that passed. A refusal, a throw or a failed write leaves
|
|
504
|
+
* the base as it was.
|
|
505
|
+
*
|
|
506
|
+
* Returns `{ok: true, changed}` or `{ok: false, reason}`.
|
|
507
|
+
*/
|
|
508
|
+
export function rebaseSynthesis(runDir, edit, opts = {}) {
|
|
509
|
+
const dir0 = String(runDir ?? "");
|
|
510
|
+
const base = lastAcceptedCall(dir0);
|
|
511
|
+
if (!base || typeof base !== "object") return { ok: false, reason: "no_base: this run holds no accepted synthesis call to edit" };
|
|
512
|
+
let findings;
|
|
513
|
+
try { findings = edit(structuredClone(base.findings)); }
|
|
514
|
+
catch (e) { return { ok: false, reason: `edit_failed: ${String(e?.message ?? e).slice(0, 160)}` }; }
|
|
515
|
+
if (JSON.stringify(findings) === JSON.stringify(base.findings)) return { ok: true, changed: false };
|
|
516
|
+
const auto = driverReadsFor(dir0);
|
|
517
|
+
const { asks = auto.asks, ledger = auto.ledger, manifest = auto.manifest,
|
|
518
|
+
method = auto.method, methodInvalid = auto.methodInvalid ?? null,
|
|
519
|
+
owed = auto.owed, declined = auto.declined, recordOrigins = undefined,
|
|
520
|
+
now = () => new Date().toISOString() } = opts;
|
|
521
|
+
const call = { ...base, findings };
|
|
522
|
+
const v = acceptSynthesis(call, { asks, ledger, manifest, method, methodInvalid, owed, declined, recordOrigins: recordOriginsOr(recordOrigins) });
|
|
523
|
+
if (!v.ok) return { ok: false, reason: v.reason };
|
|
524
|
+
try {
|
|
525
|
+
writeAtomic(synthesisCallPaths(dir0).accepted, JSON.stringify({
|
|
526
|
+
_provenance: "the last ACCEPTED call, with a driver edit that passed the same acceptance — the base a later repair patches onto",
|
|
527
|
+
acceptedAt: now(), editedBy: "driver", params: call,
|
|
528
|
+
}, null, 2) + "\n");
|
|
529
|
+
} catch (e) { return { ok: false, reason: `write_failed: ${String(e?.message ?? e).slice(0, 160)}` }; }
|
|
530
|
+
return { ok: true, changed: true };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ── A FINDINGS OBJECT WITH NO LIST IS A PATCH ON THE REGISTERS IT CARRIES ───────────────────────────────
|
|
534
|
+
//
|
|
535
|
+
// A correction has been seen to send the registers it changed inside a `findings` object that carries no
|
|
536
|
+
// findings list, beside its rows. Read as a document, that object has no findings at all, and the call was
|
|
537
|
+
// refused as an unparseable record, or as a record accounting for none of the run's owed records, though the
|
|
538
|
+
// stored record held every one. A findings list makes the document whole; without one, the call patches what
|
|
539
|
+
// it carries, and the merged record is judged whole as any call is.
|
|
540
|
+
//
|
|
541
|
+
// MAIN'S READING STANDS FOR EVERYTHING ELSE. A list-less object reads as registers only when there is a stored
|
|
542
|
+
// record to patch, a narrative sent beside it is an object of the sections the tool describes, every key it carries
|
|
543
|
+
// is a register of that record's own ladder (the one its schema_version gives) or its rows, every key the call
|
|
544
|
+
// carries beside its narrative and that object is one too, its envelope is absent or equal to the stored one wherever
|
|
545
|
+
// it is sent, a corrections marker sent inside it or beside it is one the decode reads, a register sent inside it and
|
|
546
|
+
// again beside it carries the same value in both places, the call's own rows each name a finding the stored record
|
|
547
|
+
// holds, and no ordinal carries two different rows, among the call's own rows or its nested ones. Any other shape is
|
|
548
|
+
// read exactly as it always was, so the model meets the refusal it always met, byte for byte.
|
|
549
|
+
|
|
550
|
+
const isPlainObject = (v) => v != null && typeof v === "object" && !Array.isArray(v);
|
|
551
|
+
// Key-sorted JSON: "the same content" must not turn on the order the model wrote its keys in.
|
|
552
|
+
const canon = (v) => (v === null || typeof v !== "object") ? JSON.stringify(v)
|
|
553
|
+
: Array.isArray(v) ? `[${v.map(canon).join(",")}]`
|
|
554
|
+
: `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`).join(",")}}`;
|
|
555
|
+
const sameValue = (a, b) => canon(a) === canon(b);
|
|
556
|
+
|
|
557
|
+
// A finding row names the finding it replaces by its ordinal, a positive whole number.
|
|
558
|
+
const isRow = (r) => isPlainObject(r) && Number.isInteger(r.ordinal) && r.ordinal > 0;
|
|
559
|
+
// A corrections marker sent as a JSON string, decoded: the object it encodes, or undefined for anything else.
|
|
560
|
+
const decodedMarker = (v) => { try { const x = JSON.parse(v); return isPlainObject(x) ? x : undefined; } catch { return undefined; } };
|
|
561
|
+
// A marker the decode reads: none (null, which the parser reads as no marker), an object, or a string encoding one.
|
|
562
|
+
const isMarker = (v) => v == null || isPlainObject(v) || (typeof v === "string" && decodedMarker(v) !== undefined);
|
|
563
|
+
// A register's value as the transport reads it: a marker string as the object it encodes, anything else as sent.
|
|
564
|
+
const asRead = (k, v) => (k === "corrections" && typeof v === "string" ? (decodedMarker(v) ?? v) : v);
|
|
565
|
+
// A narrative section as the tool describes it: the coverage prose inside `coverage`, the calibration challenges as
|
|
566
|
+
// {challenge, answer} pairs, and every other section as text. `ask_answers` belongs to the record, not here.
|
|
567
|
+
const isSection = ([k, v]) => k === "coverage" ? isPlainObject(v) && Object.keys(v).every((x) => x === "read") && isStr(v.read)
|
|
568
|
+
: k === "calibration" ? Array.isArray(v) && v.every((c) => isPlainObject(c) && isStr(c.challenge) && isStr(c.answer))
|
|
569
|
+
: k !== "ask_answers" && isStr(v);
|
|
570
|
+
// Two different rows for one ordinal among `rows`. A row sent again with the same content is one row.
|
|
571
|
+
const twoRowsForOneOrdinal = (rows) => {
|
|
572
|
+
const one = new Map();
|
|
573
|
+
return rows.some((r) => (one.has(r.ordinal) ? !sameValue(one.get(r.ordinal), r) : (one.set(r.ordinal, r), false)));
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// The record's envelope. A list-less object that would change either, inside the object or at the call's top
|
|
577
|
+
// level, is not a register edit: re-versioning is a whole re-send (the schema-downlevel rung), and read as
|
|
578
|
+
// registers it could move the stored list to another version's rules without the list ever being sent.
|
|
579
|
+
const ENVELOPE_KEYS = ["schema_version", "rated_under_framework"];
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Rows sent inside a list-less `findings`, beside the call's own. PURE. Null when that is ambiguous: rows that are
|
|
583
|
+
* not an array, a nested row that is not an object carrying a positive whole-number ordinal (the way a finding row
|
|
584
|
+
* names the finding it replaces), or one ordinal carrying two different rows, whether both are nested or one is at the
|
|
585
|
+
* top level. The call's own rows reach here already checked to carry one row per ordinal (`registerPatchOf`), so a
|
|
586
|
+
* nested row is compared with the one row its ordinal has there. A nested row that repeats one already sent is
|
|
587
|
+
* applied once, and `lifted` counts only the rows actually added.
|
|
588
|
+
*/
|
|
589
|
+
function liftNestedRows(top, nested) {
|
|
590
|
+
if (nested === undefined) return { rows: top, lifted: 0 };
|
|
591
|
+
if (!Array.isArray(nested) || (top != null && !Array.isArray(top))) return null;
|
|
592
|
+
if (!nested.every(isRow)) return null;
|
|
593
|
+
const sent = new Map((top ?? []).map((r) => [r?.ordinal, r]));
|
|
594
|
+
const added = [];
|
|
595
|
+
for (const r of nested) {
|
|
596
|
+
if (sent.has(r?.ordinal)) {
|
|
597
|
+
if (!sameValue(sent.get(r?.ordinal), r)) return null;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
sent.set(r?.ordinal, r);
|
|
601
|
+
added.push(r);
|
|
602
|
+
}
|
|
603
|
+
return { rows: added.length ? [...(top ?? []), ...added] : top, lifted: added.length };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* A call whose `findings` carries no list, read as a patch on the registers it carries onto `stored`. PURE.
|
|
608
|
+
*
|
|
609
|
+
* Returns `{registers, rows, lifted}`, or null when the call does not read that way (see above), and null leaves
|
|
610
|
+
* the call to main's reading of it. `rows` are the call's own `findings_patch` with the nested rows after them.
|
|
611
|
+
*/
|
|
612
|
+
export function registerPatchOf(stored, call) {
|
|
613
|
+
const doc = call?.findings;
|
|
614
|
+
if (!isPlainObject(stored?.findings) || !isPlainObject(doc) || Object.hasOwn(doc, "findings")) return null;
|
|
615
|
+
// A narrative sent beside it is an object of sections. Text, a list or null is no section patch: merged, its
|
|
616
|
+
// characters would become numbered keys in the base while its prose reached no file. And each section it sends is
|
|
617
|
+
// one the tool describes: a coverage that is not its prose, or calibration that is not its pairs, would replace
|
|
618
|
+
// the held section with something no file renders, and a spine or verdict that is not text is no section at all.
|
|
619
|
+
if (call.narrative !== undefined && !(isPlainObject(call.narrative) && Object.entries(call.narrative).every(isSection))) return null;
|
|
620
|
+
// What it may carry: a register of the stored record's own ladder, or rows.
|
|
621
|
+
const allowed = new Set([...topKeysForVersion(stored.findings.schema_version).filter((k) => k !== "findings"), "findings_patch"]);
|
|
622
|
+
if (Object.keys(doc).some((k) => !allowed.has(k))) return null;
|
|
623
|
+
// And what the call may carry beside it. The merge copies a patch's own top-level keys into the record, so a key
|
|
624
|
+
// there that the ladder does not allow would be judged against the stored record, where the object read as a
|
|
625
|
+
// document is judged as sent.
|
|
626
|
+
if (Object.keys(call).some((k) => k !== "narrative" && k !== "findings" && !allowed.has(k))) return null;
|
|
627
|
+
// Its envelope, inside the object or beside it, is absent or the stored one. The merge copies the call's own
|
|
628
|
+
// top-level keys last, so a different envelope there would re-version the record as surely as one inside.
|
|
629
|
+
if (ENVELOPE_KEYS.some((k) => [doc, call].some((o) => Object.hasOwn(o, k) && !sameValue(o[k], stored.findings[k])))) return null;
|
|
630
|
+
// A corrections marker, inside the object or beside it, is one the decode reads. Any other marker, a string that
|
|
631
|
+
// does not decode to an object among them, is refused as it always was: on this shape, as main reads the object.
|
|
632
|
+
if (![doc, call].every((o) => isMarker(o.corrections))) return null;
|
|
633
|
+
// A register sent inside the object and again beside it carries one value in both places. Read as registers, the
|
|
634
|
+
// copy beside the object would be kept and the other dropped without a word, so the model would never learn that
|
|
635
|
+
// part of its correction was ignored; read as main reads the call, it meets main's refusal. Compared as read, so the
|
|
636
|
+
// answer is the same on the call as it arrives and on the call once its markers are decoded.
|
|
637
|
+
if (Object.keys(doc).some((k) => k !== "findings_patch" && Object.hasOwn(call, k) && !sameValue(asRead(k, doc[k]), asRead(k, call[k])))) return null;
|
|
638
|
+
// The call's own rows each replace a finding the stored record holds, named by a positive whole-number ordinal.
|
|
639
|
+
// A row list that is not one, or a row aimed elsewhere, is read as main reads the call.
|
|
640
|
+
const held = new Set((Array.isArray(stored.findings.findings) ? stored.findings.findings : []).map((f) => f?.ordinal));
|
|
641
|
+
if (call.findings_patch !== undefined && !(Array.isArray(call.findings_patch) && call.findings_patch.every((r) => isRow(r) && held.has(r.ordinal)))) return null;
|
|
642
|
+
// And no ordinal carries two different rows among them. Read as registers, the last would be applied and the other
|
|
643
|
+
// dropped without a word, the choice turning on the order they were sent in; read as main reads the call, it meets
|
|
644
|
+
// main's refusal. A row sent again with the same content is one row.
|
|
645
|
+
if (call.findings_patch !== undefined && twoRowsForOneOrdinal(call.findings_patch)) return null;
|
|
646
|
+
const { findings_patch: nested, ...registers } = doc;
|
|
647
|
+
const lift = liftNestedRows(call.findings_patch, nested);
|
|
648
|
+
return lift ? { registers, rows: lift.rows, lifted: lift.lifted } : null;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** Would `call` read as a register patch on `stored` but for a corrections marker the decode cannot read? PURE. */
|
|
652
|
+
function markerAloneBlocks(stored, call) {
|
|
653
|
+
const doc = call?.findings;
|
|
654
|
+
if (!isPlainObject(doc) || Object.hasOwn(doc, "findings")) return false;
|
|
655
|
+
if ([doc, call].every((o) => isMarker(o.corrections))) return false;
|
|
656
|
+
const { corrections: _inner, ...docRest } = doc;
|
|
657
|
+
const { corrections: _outer, ...callRest } = call;
|
|
658
|
+
return registerPatchOf(stored, { ...callRest, findings: docRest }) != null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Read a call in the shapes the model has been seen to send it. PURE. Returns the call to decide on — the
|
|
663
|
+
* received object itself unless something below applies — and what was read, for the run's own record.
|
|
664
|
+
*
|
|
665
|
+
* A `corrections` marker sent as a JSON STRING is decoded where the call carries no findings list: at the top
|
|
666
|
+
* level of a patch, and inside a findings object read as registers (`registerPatchOf`). Only a string that
|
|
667
|
+
* decodes to an object is replaced, and the object is then validated as any marker is; anything else stays as it
|
|
668
|
+
* came, and the parser refuses it as it always did. A call carrying a list — a whole call, or a record re-sent
|
|
669
|
+
* with no narrative — is read exactly as main reads it.
|
|
670
|
+
*
|
|
671
|
+
* WHAT IS COUNTED, for the run log only. On a WHOLE call every top-level key beside its narrative and its findings
|
|
672
|
+
* is ignored, as it always was, and counted: `keysIgnored` for keys, which are never named, since a key off the
|
|
673
|
+
* record's ladder is the model's own word; `rowsIgnored` for a top-level `findings_patch`, and `rowsDiffering` for
|
|
674
|
+
* those of its rows the record's own list does not hold as sent. Nothing a register patch carries is dropped: a
|
|
675
|
+
* register sent in both places with two values makes the call one main reads as it always did (`registerPatchOf`).
|
|
676
|
+
*/
|
|
677
|
+
export function readSynthesisCall(received, stored) {
|
|
678
|
+
const notes = { decoded: [], keysIgnored: 0, rowsIgnored: 0, rowsDiffering: 0 };
|
|
679
|
+
if (!isPlainObject(received)) return { call: received, notes };
|
|
680
|
+
const asRegisters = registerPatchOf(stored, received) !== null;
|
|
681
|
+
if (received.findings !== undefined && !asRegisters) {
|
|
682
|
+
if (received.narrative !== undefined) { // a whole call
|
|
683
|
+
const list = new Map((Array.isArray(received.findings?.findings) ? received.findings.findings : []).map((f) => [f?.ordinal, f]));
|
|
684
|
+
for (const [k, v] of Object.entries(received)) {
|
|
685
|
+
if (k === "narrative" || k === "findings") continue;
|
|
686
|
+
if (k !== "findings_patch" || !Array.isArray(v)) { notes.keysIgnored++; continue; }
|
|
687
|
+
notes.rowsIgnored += v.length;
|
|
688
|
+
notes.rowsDiffering += v.filter((r) => !list.has(r?.ordinal) || !sameValue(list.get(r?.ordinal), r)).length;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { call: received, notes };
|
|
692
|
+
}
|
|
693
|
+
let call = received;
|
|
694
|
+
if (typeof call.corrections === "string") {
|
|
695
|
+
const x = decodedMarker(call.corrections);
|
|
696
|
+
if (x !== undefined) { call = { ...call, corrections: x }; notes.decoded.push("corrections"); }
|
|
697
|
+
}
|
|
698
|
+
if (asRegisters && typeof call.findings.corrections === "string") {
|
|
699
|
+
const x = decodedMarker(call.findings.corrections);
|
|
700
|
+
if (x !== undefined) { call = { ...call, findings: { ...call.findings, corrections: x } }; notes.decoded.push("findings.corrections"); }
|
|
701
|
+
}
|
|
702
|
+
return { call, notes };
|
|
703
|
+
}
|
|
704
|
+
|
|
443
705
|
/**
|
|
444
706
|
* Merge a patch call onto the stored one. PURE.
|
|
445
707
|
*
|
|
@@ -451,6 +713,9 @@ export function lastAcceptedCall(runDir) {
|
|
|
451
713
|
* A patch naming an ordinal the stored call does not hold is a DEFECT, not an insert: the repair
|
|
452
714
|
* ladder only ever patches findings the parse already quarantined, so an unknown ordinal means the seat
|
|
453
715
|
* is repairing something this run does not have.
|
|
716
|
+
*
|
|
717
|
+
* Returns `{ok: true, merged, lifted}`, where `lifted` counts the rows taken from inside a list-less
|
|
718
|
+
* `findings`, or `{ok: false, reason}`.
|
|
454
719
|
*/
|
|
455
720
|
export function mergeSynthesisPatch(stored, patch) {
|
|
456
721
|
if (!stored) return { ok: false, reason: "synthesis_patch_without_base: this run has no accepted synthesis call to patch — send the complete `narrative` and `findings` instead. A patch is a repair of values the driver is holding, and it is holding none" };
|
|
@@ -460,9 +725,22 @@ export function mergeSynthesisPatch(stored, patch) {
|
|
|
460
725
|
// still arrives here rather than as a first call, because it carries no narrative — and the narrative
|
|
461
726
|
// it is not being asked to redo is the stored one. Without this the rung could not succeed: the call
|
|
462
727
|
// was refused for `synthesis_narrative_missing`, naming sections nobody had asked the seat to resend.
|
|
463
|
-
|
|
728
|
+
//
|
|
729
|
+
// A `findings` OBJECT WITH NO LIST is the other shape (`registerPatchOf`): each register it carries replaces
|
|
730
|
+
// the stored one and the rest are held, and rows sent inside it are applied as rows. Precedence: the stored
|
|
731
|
+
// record, then those registers, then the call's top-level registers (copied last, below); a register sent in both
|
|
732
|
+
// places carries one value there, so the order chooses nothing. Rows replace whole findings by ordinal; narrative
|
|
733
|
+
// sections replace sections.
|
|
734
|
+
const asRegisters = registerPatchOf(stored, patch);
|
|
735
|
+
// A list-less `findings` object that reads as registers in every way but its corrections marker: the marker is what
|
|
736
|
+
// is wrong. Read as main reads the call it would replace the record, and it would then be refused for a defect it
|
|
737
|
+
// does not have (rows naming findings the run holds, or a record with no list); it gets the refusal the same marker
|
|
738
|
+
// meets at the top level instead.
|
|
739
|
+
if (!asRegisters && markerAloneBlocks(stored, patch)) return { ok: false, reason: "synthesis_findings_invalid: findings_corrections_invalid (corrections must be an object { applied, note })" };
|
|
740
|
+
if (asRegisters) Object.assign(out.findings, asRegisters.registers);
|
|
741
|
+
else if (patch?.findings !== undefined) out.findings = patch.findings;
|
|
464
742
|
for (const [k, v] of Object.entries(patch?.narrative ?? {})) out.narrative[k] = v;
|
|
465
|
-
const rows = patch?.findings_patch;
|
|
743
|
+
const rows = asRegisters ? asRegisters.rows : patch?.findings_patch;
|
|
466
744
|
if (rows != null) {
|
|
467
745
|
if (!Array.isArray(rows)) return { ok: false, reason: "synthesis_patch_shape: `findings_patch` is an array of complete finding objects, each carrying the `ordinal` of the finding it replaces" };
|
|
468
746
|
const byOrdinal = new Map((out.findings.findings ?? []).map((f, i) => [f?.ordinal, i]));
|
|
@@ -478,7 +756,7 @@ export function mergeSynthesisPatch(stored, patch) {
|
|
|
478
756
|
for (const [k, v] of Object.entries(patch ?? {})) {
|
|
479
757
|
if (k !== "narrative" && k !== "findings_patch" && k !== "findings") out.findings[k] = v;
|
|
480
758
|
}
|
|
481
|
-
return { ok: true, merged: out };
|
|
759
|
+
return { ok: true, merged: out, lifted: asRegisters?.lifted ?? 0 };
|
|
482
760
|
}
|
|
483
761
|
|
|
484
762
|
/**
|
|
@@ -626,8 +904,28 @@ export function recordSynthesis(runDir, received, opts = {}) {
|
|
|
626
904
|
// a second way to say the same thing and the two can disagree. Either half may be the missing one —
|
|
627
905
|
// a corrective sends findings changes with no narrative rewrite, a schema migration re-emits the
|
|
628
906
|
// whole record and touches no section — and in both cases the half not sent is the stored one.
|
|
629
|
-
|
|
630
|
-
|
|
907
|
+
// A call whose `findings` carries NO LIST is a patch too, on the registers it carries, where there is a stored
|
|
908
|
+
// record to patch and the call reads that way (`registerPatchOf`); any other shape is read as it always was.
|
|
909
|
+
// The call is decided on AS READ (`readSynthesisCall`): a marker sent as a JSON string is decoded first. The
|
|
910
|
+
// capture above keeps what arrived; the base written below keeps the call as read.
|
|
911
|
+
const stored = lastAcceptedCall(dir0);
|
|
912
|
+
const read = readSynthesisCall(received, stored);
|
|
913
|
+
let call = read.call;
|
|
914
|
+
const asRegisters = registerPatchOf(stored, call);
|
|
915
|
+
const isPatch = Boolean(call) && (call.findings === undefined || call.narrative === undefined || asRegisters !== null);
|
|
916
|
+
// WHAT THE TRANSPORT READ THE CALL AS: one run-log row at whichever exit the call takes, beside the decision,
|
|
917
|
+
// when the reading decoded, lifted or ignored anything. Never in the answer below, which the tool
|
|
918
|
+
// returns whole into the model's turn. Best-effort, as the foreign-link record is. A lift is counted as READ,
|
|
919
|
+
// so one the merge then refuses is on the record as one the acceptance refuses is.
|
|
920
|
+
const reading = { ...read.notes, lifted: asRegisters?.lifted ?? 0 };
|
|
921
|
+
const recordReading = (accepted) => {
|
|
922
|
+
if (!reading.decoded.length && !reading.lifted && !reading.keysIgnored && !reading.rowsIgnored) return;
|
|
923
|
+
try {
|
|
924
|
+
runLog(dir0, { event: "synthesis-call-normalised", stage: "synthesis", seq: cap.seq ?? null, accepted,
|
|
925
|
+
decoded: reading.decoded, lifted: reading.lifted,
|
|
926
|
+
keysIgnored: reading.keysIgnored, rowsIgnored: reading.rowsIgnored, rowsDiffering: reading.rowsDiffering });
|
|
927
|
+
} catch { /* a log that cannot be written must not stop the record it describes */ }
|
|
928
|
+
};
|
|
631
929
|
// Appended, never overwritten: a turn can be refused more than once and each one is a fact about the
|
|
632
930
|
// run. Best-effort — bookkeeping that can kill a run is worse than bookkeeping that is absent.
|
|
633
931
|
const noteRefusal = (reason) => {
|
|
@@ -635,9 +933,10 @@ export function recordSynthesis(runDir, received, opts = {}) {
|
|
|
635
933
|
};
|
|
636
934
|
|
|
637
935
|
if (isPatch) {
|
|
638
|
-
const merged = mergeSynthesisPatch(
|
|
936
|
+
const merged = mergeSynthesisPatch(stored, call); // the call as READ: a decoded marker merges as an object
|
|
639
937
|
if (!merged.ok) {
|
|
640
938
|
noteRefusal(merged.reason);
|
|
939
|
+
recordReading(false);
|
|
641
940
|
return { written: null, refused: merged.reason,
|
|
642
941
|
captured: closeCapture({ ok: false, refused: merged.reason }), capture_failed: captureFailed };
|
|
643
942
|
}
|
|
@@ -656,6 +955,7 @@ export function recordSynthesis(runDir, received, opts = {}) {
|
|
|
656
955
|
const v = acceptSynthesis(call, { asks, ledger, manifest, method, methodInvalid, owed, declined, recordOrigins: origins });
|
|
657
956
|
if (!v.ok) {
|
|
658
957
|
noteRefusal(v.reason);
|
|
958
|
+
recordReading(false);
|
|
659
959
|
return { written: null, refused: v.reason,
|
|
660
960
|
captured: closeCapture({ ok: false, refused: v.reason }), capture_failed: captureFailed };
|
|
661
961
|
}
|
|
@@ -674,6 +974,7 @@ export function recordSynthesis(runDir, received, opts = {}) {
|
|
|
674
974
|
} catch (e) {
|
|
675
975
|
// The call was VALID and we could not store it. That is infrastructure, and it must not read as a
|
|
676
976
|
// rejected call — the two have opposite repairs.
|
|
977
|
+
recordReading(false);
|
|
677
978
|
return {
|
|
678
979
|
written: null, refused: null,
|
|
679
980
|
write_failed: String(e?.message ?? e).slice(0, 200),
|
|
@@ -684,6 +985,7 @@ export function recordSynthesis(runDir, received, opts = {}) {
|
|
|
684
985
|
// call records above stay the model's own, the base for its next patch; findings.json becomes the folded
|
|
685
986
|
// record the reviewer, the cards and the delivery read. Before the seam this leaves the record as written.
|
|
686
987
|
foldAfterSave(dir0, findingsAt);
|
|
988
|
+
recordReading(true);
|
|
687
989
|
|
|
688
990
|
return {
|
|
689
991
|
written: [findingsAt, narrativeAt], refused: null,
|
|
@@ -27,6 +27,46 @@
|
|
|
27
27
|
/** The verdicts, ordered by caution. A clamp may move RIGHT along this list and never left. */
|
|
28
28
|
const CAUTION = Object.freeze(["CLEAR", "CONDITIONAL", "BLOCKING"]);
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* A GUARD'S GAP POSITIVELY CLOSED when it is judged again: its account was read, could be computed, and finds no gap.
|
|
32
|
+
* An account that is absent, unreadable or not computable is no proof that the gap closed, and the clause stands.
|
|
33
|
+
* `token` names the guard. The floor's account is judged in its own module (`floorDutyClosed`) and arrives as
|
|
34
|
+
* `floorClosed`; the writer's duty arrives as its reconciliation, and the recall gate as its reconciliation with the
|
|
35
|
+
* records a refused merge left out. PURE.
|
|
36
|
+
*/
|
|
37
|
+
export function gapPositivelyClosed(token, { floorClosed = false, duty = null, recall = null, leftOut = [] } = {}) {
|
|
38
|
+
if (token === "floor_duty_undischarged") return floorClosed === true;
|
|
39
|
+
if (token === "synthesis_unaccounted_delivered") return duty?.computable === true && Array.isArray(duty.unaccounted) && duty.unaccounted.length === 0;
|
|
40
|
+
if (token === "recall_reconciliation_unended") return recall?.computable === true && !((recall.totals?.unended ?? 0) > 0) && !leftOut.length;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* THE RESULT ONCE A CLOSED GAP'S CLAUSE IS WITHDRAWN, recomputed from the accepted assessment and the conditions that
|
|
46
|
+
* still stand. A CONDITIONAL goes back to CLEAR only when the assessment was CLEAR and no condition is left; any other
|
|
47
|
+
* result stands, so a condition that still applies, a review that said CONDITIONAL or BLOCKING, or a result the guard
|
|
48
|
+
* never moved is never lifted. PURE.
|
|
49
|
+
*/
|
|
50
|
+
export function verdictWithoutTheClosedGap({ verdict, assessed, reasons = [] } = {}) {
|
|
51
|
+
return verdict === "CONDITIONAL" && assessed === "CLEAR" && !reasons.length ? "CLEAR" : verdict;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* ONE SENTENCE WHEN THE WRITER'S DUTY AND THE RECALL GATE BOTH FIND RECORDS UNADDRESSED. Both state the approved
|
|
56
|
+
* sentence for records neither addressed nor set aside, each out of its own population: the records the writer was
|
|
57
|
+
* owed, and the records the recall gate weighed. Stated twice, the same words would give two totals for "the register
|
|
58
|
+
* records this search surfaced". Stated once, N is the records either names and M the records either required to be
|
|
59
|
+
* assessed, each counted once: never a sum of overlapping records, and never a record neither required. Null when
|
|
60
|
+
* either side names no record or either population is unknown; each then states its own count. PURE.
|
|
61
|
+
*/
|
|
62
|
+
export function jointUnaccounted({ dutyIds = [], owedIds = null, recallIds = [], weighedIds = null } = {}) {
|
|
63
|
+
if (!Array.isArray(owedIds) || !Array.isArray(weighedIds) || !dutyIds.length || !recallIds.length) return null;
|
|
64
|
+
const ids = (...lists) => new Set(lists.flat().map((x) => String(x ?? "").trim().toLowerCase()).filter(Boolean));
|
|
65
|
+
const n = ids(dutyIds, recallIds).size;
|
|
66
|
+
const m = ids(owedIds, weighedIds, dutyIds, recallIds).size;
|
|
67
|
+
return { n, m };
|
|
68
|
+
}
|
|
69
|
+
|
|
30
70
|
/**
|
|
31
71
|
* The shape of an engine identifier: `word_word:N` tokens (`floor_duty_undischarged:22`) and the
|
|
32
72
|
* `snake_case` defect vocabulary generally. The CLIENT'S clause may never carry one —:
|
|
@@ -173,9 +213,20 @@ export function orderClausesForLede(clauses, reasons, guardSet) {
|
|
|
173
213
|
* after 5.55 hours and the client received nothing instead of a report naming one gap. The caller
|
|
174
214
|
* decides what the absence means.
|
|
175
215
|
*/
|
|
216
|
+
// The approved sentence for surfaced records the report neither addresses nor sets aside. The recall gate finds the
|
|
217
|
+
// same state, a retrieved record ending nowhere a reader can see, and since the owner's ruling of 2026-10-19 (never hold
|
|
218
|
+
// a run) it ships that state instead of stopping: it is said in this same sentence, not a new one.
|
|
219
|
+
const unaccountedRecords = (n, m) =>
|
|
220
|
+
`${n} of the ${m} register records this search surfaced are neither addressed as findings nor expressly set aside in this report — they remain open points a reader must weigh`;
|
|
176
221
|
const CLAUSE_AUTHORITY = Object.freeze({
|
|
177
|
-
synthesis_unaccounted_delivered:
|
|
178
|
-
|
|
222
|
+
synthesis_unaccounted_delivered: unaccountedRecords,
|
|
223
|
+
recall_reconciliation_unended: unaccountedRecords,
|
|
224
|
+
// The owner's words of 2026-10-19, for records a repeated placement pass re-ranked after the writer last wrote, when
|
|
225
|
+
// the writer's re-run for the change did not complete: his singular for one record, his plural for more. The second
|
|
226
|
+
// count is the population, kept for the run record.
|
|
227
|
+
ranking_changed_unread: (n) => (n === 1
|
|
228
|
+
? "1 register record was re-ranked after this report was written; the audit trail gives its final ranking"
|
|
229
|
+
: `${n} register records were re-ranked after this report was written; the audit trail gives their final ranking`),
|
|
179
230
|
floor_duty_undischarged: (n, m) =>
|
|
180
231
|
`${n} of the ${m} live registrations identical or near-identical to the mark are not individually addressed in this report — each remains an open point a reader must weigh`,
|
|
181
232
|
});
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// verify-knockout.mjs — validators + gates for the KNOCKOUT (Depth 1) lane.
|
|
4
4
|
// Same contract as verify.mjs validators: (file, text) -> {ok, reason?} for per-stage outputs (they ride
|
|
5
5
|
// runStage's corrective ladder), plus code-side gates for the merged findings and the pre-publish lint.
|
|
6
|
-
// The receipts discipline: every cited URL must appear in THAT mark's raw research payload
|
|
7
|
-
// analogue of the band-truth gate (a finding the driver cannot trace to held evidence is treated as
|
|
6
|
+
// The receipts discipline: every cited URL must appear in THAT mark's raw research payload or in the register
|
|
7
|
+
// records supplied for that mark — the knockout analogue of the band-truth gate (a finding the driver cannot trace to held evidence is treated as
|
|
8
8
|
// fabricated). It runs at three doors and has NO off switch: CLEAROTRON_KNOCKOUT_URL_GATE is deleted.
|
|
9
9
|
// An env var that disarms a gate makes the gate's absence look exactly like its pass, which is the one
|
|
10
10
|
// failure mode this lane's gates exist to refuse.
|
|
@@ -94,12 +94,27 @@ export function knockoutCitedUrls(f) {
|
|
|
94
94
|
*
|
|
95
95
|
* @returns {{ok: boolean, failures: string[], checked: {marks: number, citing: number, findings: number, urls: number}}}
|
|
96
96
|
*/
|
|
97
|
+
/**
|
|
98
|
+
* THE REGISTER FILINGS THE RATING STEP WAS HANDED, per mark, as text (register-records.json, which the rating
|
|
99
|
+
* message names when it is on disk). The message lets a citation come from the mark's own payload or from the
|
|
100
|
+
* filings supplied for that mark, so the receipts check reads both; another mark's filings license nothing.
|
|
101
|
+
*/
|
|
102
|
+
function suppliedRecordsByMark(runDir) {
|
|
103
|
+
try {
|
|
104
|
+
const doc = JSON.parse(readFileSync(driverDir(runDir, "register-records.json"), "utf8"));
|
|
105
|
+
return new Map((Array.isArray(doc?.marks) ? doc.marks : []).filter((x) => x?.name)
|
|
106
|
+
.map((x) => [String(x.name).trim().toLowerCase(), JSON.stringify(Array.isArray(x.records) ? x.records : [])]));
|
|
107
|
+
} catch { return new Map(); }
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
export function knockoutReceipts(runDir, marks) {
|
|
98
111
|
const failures = [];
|
|
99
112
|
const checked = { marks: (marks ?? []).length, citing: 0, findings: 0, urls: 0 };
|
|
113
|
+
const supplied = suppliedRecordsByMark(runDir);
|
|
100
114
|
for (const m of marks ?? []) {
|
|
101
115
|
const list = Array.isArray(m?.findings) ? m.findings : [];
|
|
102
116
|
checked.findings += list.length;
|
|
117
|
+
const records = supplied.get(String(m?.name ?? "").trim().toLowerCase()) ?? null;
|
|
103
118
|
let payload = null, opened = false;
|
|
104
119
|
for (const f of list) {
|
|
105
120
|
const cited = knockoutCitedUrls(f);
|
|
@@ -111,8 +126,11 @@ export function knockoutReceipts(runDir, marks) {
|
|
|
111
126
|
}
|
|
112
127
|
for (const url of cited) {
|
|
113
128
|
checked.urls += 1;
|
|
114
|
-
if (
|
|
115
|
-
|
|
129
|
+
if (records != null && urlInPayload(url, records)) continue; // a filing supplied for this mark
|
|
130
|
+
// One refusal for both branches (design's wording, 2026-10-19): the register records supplied for the mark are
|
|
131
|
+
// held evidence too, so a refusal never tells the model to drop one of them.
|
|
132
|
+
if (payload == null || !urlInPayload(url, payload))
|
|
133
|
+
failures.push(`knockout_url_unreceipted:${m.name}: "${url}" does not appear in the mark's own research payload or in the register records supplied for that mark — cite only held evidence`);
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
}
|
package/driver/verify.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { dirname, join, basename } from "node:path";
|
|
|
12
12
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
13
13
|
import { findReceiptViolations, findGridLedgerViolations, findUnranDictatedCells, findPlatformIdentityViolations, parsePrRiskQueries, MEANING_SEAT, erroredConnotationQueriesAmong } from "./common-law-receipts.mjs";
|
|
14
14
|
// Conversion 2 — the discriminator the two rulings above key on. PURE-ish: one existsSync-shaped read.
|
|
15
|
-
import { matterFrameWasRecorded, frameRatifiedForms } from "./matter-frame-record.mjs";
|
|
15
|
+
import { matterFrameWasRecorded, frameRatifiedForms } from "./matter-frame-record.mjs"; import { lastAcceptedOverview, acceptReportOverview, readReportIdentity } from "./report-overview-record.mjs";
|
|
16
16
|
import { findConnotationViolations, parsePrRiskResults, prRiskPopulation,
|
|
17
17
|
CONNOTATION_UNMATCHED_MARK, CONNOTATION_NO_RESEMBLANCE_MARK, MEANING_ANGLES_RE,
|
|
18
18
|
parseDispositionForm, CONNOTATION_UNRULED_REASONS, queryKey } from "./connotation-search.mjs";
|
|
@@ -237,6 +237,7 @@ function all(...checks) {
|
|
|
237
237
|
return ok();
|
|
238
238
|
}
|
|
239
239
|
const ok = (reason = "") => ({ ok: true, reason });
|
|
240
|
+
|
|
240
241
|
const fail = (reason) => ({ ok: false, reason });
|
|
241
242
|
|
|
242
243
|
// ── A1 SPLIT: the structural core shared by the commonLaw and commonLawHalf validators ─────────────
|
|
@@ -2505,8 +2506,8 @@ export const validators = {
|
|
|
2505
2506
|
// delivery contract files. report.md is now ASSEMBLED by the driver (assembleReportMd) from the two LLM
|
|
2506
2507
|
// halves below; its validator stays (front-matter + Marks) as the post-assembly structural gate.
|
|
2507
2508
|
report: (_p, c) => all(nonEmpty(c, 300), needs(c, [/^---/m, /^#\s*Marks/im], "front-matter+marks", ["front-matter", "marks-section"])),
|
|
2508
|
-
// B1 — report-overview: the cross-finding shell (front-matter + Actions/Coverage/Methodology, NO cards).
|
|
2509
|
-
reportOverview: (
|
|
2509
|
+
// B1 — report-overview: the cross-finding shell (front-matter + Actions/Coverage/Methodology, NO cards). The driver's own render of an accepted call passes on the acceptance's terms (see isAcceptedOverviewRender, at the end of this file).
|
|
2510
|
+
reportOverview: (p, c) => (isAcceptedOverviewRender(dirname(String(p ?? "")), c) ? ok("rendered from an accepted call") : all(nonEmpty(c, 120), needs(c, [/^---/m, /^#\s*(Actions|Coverage|Methodology)/im], "front-matter+shell", ["front-matter", "shell-section"]))),
|
|
2510
2511
|
// B1 — report-card: ONE isolated finding card (heading + its depth section). Lenient-but-structural
|
|
2511
2512
|
// like the rest.: the required section is "### Full detail", NOT "### The read" — the read was a
|
|
2512
2513
|
// third condensation of a finding already summarised by the typed `net`, and it is retired. This gate
|
|
@@ -2777,3 +2778,22 @@ export function lateReviewAgainst(carried, reviewMd) {
|
|
|
2777
2778
|
const softened = !hardened && Number.isInteger(a) && Number.isInteger(b) && b < a ? now : null;
|
|
2778
2779
|
return { hardened, softened };
|
|
2779
2780
|
}
|
|
2781
|
+
|
|
2782
|
+
/**
|
|
2783
|
+
* IS THIS THE DRIVER'S OWN RENDER OF THE RUN'S LAST ACCEPTED OVERVIEW CALL, byte for byte? Never throws.
|
|
2784
|
+
*
|
|
2785
|
+
* The record_report_overview transport (report-overview-record.mjs) measures the call and renders the shell itself. A
|
|
2786
|
+
* caption with no actions and no methodology is a call it accepts, and it renders with no section heading; the
|
|
2787
|
+
* delivered report places its code-built sections whether or not `# Actions` is there. Holding that render to a
|
|
2788
|
+
* section heading asked the model to repair structure the code writes, and a run went into recovery on it. So
|
|
2789
|
+
* `validators.reportOverview` passes a file that is exactly this render, on the acceptance's terms. Any other file (an
|
|
2790
|
+
* archived or replayed shell written by hand, or a render changed after the save) is held to the structural check.
|
|
2791
|
+
*/
|
|
2792
|
+
function isAcceptedOverviewRender(runDir, content) {
|
|
2793
|
+
try {
|
|
2794
|
+
const call = lastAcceptedOverview(runDir);
|
|
2795
|
+
if (!call) return false;
|
|
2796
|
+
const v = acceptReportOverview(call, { identity: readReportIdentity(runDir) });
|
|
2797
|
+
return v.ok === true && v.content === content;
|
|
2798
|
+
} catch { return false; }
|
|
2799
|
+
}
|
package/mcp-server/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,11 @@ export {
|
|
|
20
20
|
// exists: a second copy of a path table drifts, and the drift shows up as an artifact reported missing.
|
|
21
21
|
export { koPaths } from "../../driver/stages-knockout.mjs";
|
|
22
22
|
|
|
23
|
+
// PLACEMENT'S STANDING DECISIONS, as the driver's audit builder delivers them in the Audit Trail. The rebuild of a
|
|
24
|
+
// missing audit (lib/findings.mjs) carries the same rows, so the decisions recall reconciliation accepted are not lost
|
|
25
|
+
// on the recovery path. A dependency-free driver module, like the rest of this file.
|
|
26
|
+
export { placementAuditRows } from "../../driver/placement-decisions.mjs";
|
|
27
|
+
|
|
23
28
|
// HOW PUBLISH ADDRESSES A KNOCKOUT'S LISTED FILINGS. On a register with no record pages of its own, each
|
|
24
29
|
// filing with an office number is given the office's own page, or the office and the number and why that is
|
|
25
30
|
// not a link, and the report, the workbook and report-data.json state it. The evidence view states it from
|