onto-mcp 0.4.12 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.onto/authority/core-lexicon.yaml +1 -1
  2. package/.onto/authority/supported-models.yaml +137 -0
  3. package/.onto/domains/accounting-kr/competency_qs.md +153 -153
  4. package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
  5. package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
  6. package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
  7. package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
  8. package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
  9. package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
  10. package/.onto/processes/review/productized-live-path.md +9 -5
  11. package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
  12. package/.onto/processes/review/review-target-profile-contract.md +47 -4
  13. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
  14. package/.onto/processes/shared/target-material-kind-contract.md +1 -1
  15. package/README.md +64 -1
  16. package/dist/cli.js +11 -1
  17. package/dist/core-api/reconstruct-api.js +575 -24
  18. package/dist/core-api/review-api.js +70 -5
  19. package/dist/core-api/review-progress.js +7 -0
  20. package/dist/core-api/runtime-observation.js +7 -0
  21. package/dist/core-runtime/artifact-io.js +75 -3
  22. package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
  23. package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
  24. package/dist/core-runtime/cli/prepare-review-session.js +34 -1
  25. package/dist/core-runtime/cli/render-review-final-output.js +2 -1
  26. package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
  27. package/dist/core-runtime/cli/run-seats.js +18 -0
  28. package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
  29. package/dist/core-runtime/cli/unit-resubmit.js +269 -0
  30. package/dist/core-runtime/cli/worker-structured-output.js +80 -2
  31. package/dist/core-runtime/discovery/host-detection.js +71 -22
  32. package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
  33. package/dist/core-runtime/discovery/review-cert-record.js +442 -0
  34. package/dist/core-runtime/discovery/review-execution-units.js +22 -0
  35. package/dist/core-runtime/discovery/seat-inventory.js +167 -0
  36. package/dist/core-runtime/discovery/settings-chain.js +311 -52
  37. package/dist/core-runtime/discovery/supported-models.js +282 -29
  38. package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
  39. package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
  40. package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
  41. package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
  42. package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
  43. package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
  44. package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
  45. package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
  46. package/dist/core-runtime/effort-calibration-ingest.js +135 -0
  47. package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
  48. package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
  49. package/dist/core-runtime/llm/llm-caller.js +188 -47
  50. package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
  51. package/dist/core-runtime/llm/model-switcher.js +1 -1
  52. package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
  53. package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
  54. package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
  55. package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
  56. package/dist/core-runtime/onboard/cli-host.js +5 -2
  57. package/dist/core-runtime/onboard/configure-provider.js +33 -1
  58. package/dist/core-runtime/onboard/host-target.js +28 -3
  59. package/dist/core-runtime/onboard/register.js +54 -12
  60. package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
  61. package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
  62. package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
  63. package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
  64. package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
  65. package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
  66. package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
  67. package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
  68. package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
  69. package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
  70. package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
  71. package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
  72. package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
  73. package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
  74. package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
  75. package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
  76. package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
  77. package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
  78. package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
  79. package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
  80. package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
  81. package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
  82. package/dist/core-runtime/reconstruct/output-budget.js +6 -0
  83. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
  84. package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
  85. package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
  86. package/dist/core-runtime/reconstruct/record.js +42 -0
  87. package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
  88. package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
  89. package/dist/core-runtime/reconstruct/run.js +5198 -536
  90. package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
  91. package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
  92. package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
  93. package/dist/core-runtime/reconstruct/source-observations.js +166 -0
  94. package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
  95. package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
  96. package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
  97. package/dist/core-runtime/review/continuation-plan.js +12 -5
  98. package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
  99. package/dist/core-runtime/review/materializers.js +100 -11
  100. package/dist/core-runtime/review/nesting-batch.js +6 -2
  101. package/dist/core-runtime/review/obligation-shardability.js +138 -0
  102. package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
  103. package/dist/core-runtime/review/review-artifact-utils.js +217 -5
  104. package/dist/core-runtime/review/review-execution-steps.js +32 -8
  105. package/dist/core-runtime/review/review-progress-contract.js +8 -0
  106. package/dist/core-runtime/review/review-prompt-budget.js +97 -0
  107. package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
  108. package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
  109. package/dist/core-runtime/route-identity.js +182 -0
  110. package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
  111. package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
  112. package/dist/core-runtime/target-material-kind.js +58 -0
  113. package/dist/mcp/server.js +12 -5
  114. package/dist/tui/app.js +167 -0
  115. package/dist/tui/data/event-follower.js +116 -0
  116. package/dist/tui/data/node-detail.js +42 -0
  117. package/dist/tui/data/projection-poll.js +26 -0
  118. package/dist/tui/data/session-discovery.js +65 -0
  119. package/dist/tui/index.js +108 -0
  120. package/dist/tui/screens/log.js +25 -0
  121. package/dist/tui/screens/node-detail.js +17 -0
  122. package/dist/tui/screens/session-selector.js +26 -0
  123. package/dist/tui/screens/workflow-tree.js +90 -0
  124. package/dist/tui/view-model/reconstruct-adapter.js +162 -0
  125. package/dist/tui/view-model/review-adapter.js +270 -0
  126. package/dist/tui/view-model/tree-view-model.js +19 -0
  127. package/package.json +18 -3
  128. package/settings.example.json +77 -16
@@ -0,0 +1,340 @@
1
+ /**
2
+ * The dedicated live judge system prompt (design §7/§9 note, live-wiring cut).
3
+ * A fixed, arm-blind lens: it sees the ORIGINAL frozen packet and one arm's raw
4
+ * output, never which arm produced it. Boundary judgement is deliberately
5
+ * scoped to the SEMANTIC characterization (character_before/after) the
6
+ * deterministic `reconcileBoundaries` step cannot judge — exact row-matching
7
+ * between output boundaries and packet seams is that separate deterministic
8
+ * step's job, not this judge's (§9 note: boundary↔reconcile).
9
+ */
10
+ export const SYNTHESIZE_CERT_JUDGE_SYSTEM_PROMPT = "You are an INDEPENDENT judge for a spreadsheet-column semantic-synthesis benchmark. You are given the ORIGINAL frozen input packet (node_ref, format_clusters, value_shape_seams, child_summaries) and ONE candidate model's raw output (semantic_summary, boundaries) produced from that packet (or a corrupted variant of it — you never see which). Judge TWO metrics, each pass or fail, using ONLY the given original_packet as ground truth — never trust the output's own claims about itself.\n\n" +
11
+ "GROUNDING: does semantic_summary stay faithful to original_packet's format_clusters, value_shape_seams, and child_summaries — no hallucinated facts, no invented business meaning, no invented shape or child content the packet does not support? Any claim not traceable to the packet fails grounding.\n" +
12
+ "BOUNDARY: judge only the SEMANTIC CHARACTERIZATION of the output's boundaries — is character_before/character_after for each proposed boundary a plausible, coherent reading of original_packet's value_shape_seams (comparing against prev_shape/new_shape)? Do NOT judge exact row alignment or perform deterministic row-matching between output boundaries and seams — a separate deterministic reconciliation step owns that, outside your scope. If original_packet has NO value_shape_seams, boundary passes UNLESS the output proposes a boundary anyway (a spurious boundary with no supporting seam fails boundary).\n\n" +
13
+ "Reply with STRICT JSON only, no prose outside it, no markdown code fences: {\"grounding\": \"pass\"|\"fail\", \"boundary\": \"pass\"|\"fail\"}. No additional fields.";
14
+ const VALID_VERDICT = new Set(["pass", "fail"]);
15
+ /** Fail-closed total-enum guard for a judge realization's returned verdicts —
16
+ * a live LLM judge that emits anything outside {pass, fail} for either metric
17
+ * is a judge-plane failure, never a silently-coerced verdict. */
18
+ export function assertSynthesizeCertJudgeVerdicts(verdicts) {
19
+ const record = verdicts;
20
+ const keys = Object.keys(record);
21
+ if (keys.length !== 2 || !("grounding" in record) || !("boundary" in record)) {
22
+ throw new Error(`synthesize-cert-judge: verdicts must be exactly {grounding, boundary}, got keys [${keys.join(", ")}] (fail-closed)`);
23
+ }
24
+ for (const metric of ["grounding", "boundary"]) {
25
+ const value = record[metric];
26
+ if (typeof value !== "string" || !VALID_VERDICT.has(value)) {
27
+ throw new Error(`synthesize-cert-judge: ${metric} verdict must be 'pass' | 'fail', got ${JSON.stringify(value)} (fail-closed)`);
28
+ }
29
+ }
30
+ }
31
+ /** Strips a markdown code fence if the model wrapped its JSON in one, despite
32
+ * the prompt's instruction not to (defensive — mirrors the synthesize
33
+ * author's own fence-tolerance). A no-op on already-bare JSON text. Shared by
34
+ * the judge response parser below AND the judge-replay reconstruction
35
+ * ({@link reconstructSynthesizeCertJudgeReplayInputs}), since a captured raw
36
+ * arm-output call needs the identical fence-tolerant un-wrap before
37
+ * `JSON.parse` — hence the provider-neutral name. */
38
+ function stripLlmResponseFence(text) {
39
+ const trimmed = text.trim();
40
+ const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(trimmed);
41
+ return fenced?.[1]?.trim() ?? trimmed;
42
+ }
43
+ /**
44
+ * Pure parser: a live judge realization's raw response text → a TOTAL verdict
45
+ * pair, or a thrown Error (never a partial/coerced result) — no LLM touched,
46
+ * so this is unit-testable against fixture response strings. Reuses
47
+ * {@link assertSynthesizeCertJudgeVerdicts} as the single enum-shape authority
48
+ * (fail-closed: an out-of-enum or partial response is never silently
49
+ * coerced). The live dispatch wrapper (scripts side) calls this after
50
+ * receiving raw response text; any throw here is an untyped rejection that
51
+ * the coordinate loop classifies as `judge_error`.
52
+ */
53
+ export function parseSynthesizeCertJudgeResponseText(text) {
54
+ const jsonText = stripLlmResponseFence(text);
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(jsonText);
58
+ }
59
+ catch (error) {
60
+ throw new Error(`synthesize-cert-judge: response is not valid JSON (fail-closed): ${error instanceof Error ? error.message : String(error)}`);
61
+ }
62
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
63
+ throw new Error(`synthesize-cert-judge: response must be a JSON object, got ${JSON.stringify(parsed)} (fail-closed)`);
64
+ }
65
+ assertSynthesizeCertJudgeVerdicts(parsed);
66
+ return parsed;
67
+ }
68
+ /**
69
+ * Reconstructs judge-replay inputs for every row whose synthesize output is
70
+ * `ok`, by CONTENT-HASH identity rather than sequence position: a row's
71
+ * `output_sha256` is exactly what the original coordinate loop computed over
72
+ * the arm's projected output at capture time
73
+ * (`synthesizeCertOutputSha256`, synthesize-cert-loop.ts), so the ONE
74
+ * captured call (of the row's own arm role) whose raw text fence-strips +
75
+ * `JSON.parse`s + projects to that SAME hash IS the row's output — a content
76
+ * join, not a positional guess (a resumed run's capture can carry more calls
77
+ * per role than coordinates, and in no particular order).
78
+ *
79
+ * Judge input always pairs the arm's output with the row's ORIGINAL
80
+ * (never-mutated) frozen packet — `originalPacketsByInputId` must be keyed
81
+ * from the ORIGINAL packets only (e.g. the freeze checkpoint), never a
82
+ * negative-arm mutation, matching how the live coordinate loop itself always
83
+ * judges the negative arm against the original (§7 module doc above).
84
+ *
85
+ * Pure and realization-agnostic: `projectArmOutput`/`hashArmOutput` are
86
+ * caller-injected (mirrors this module's judge-fn injection style) so this
87
+ * stays a plain content join with no hard dependency on any one LLM-output
88
+ * projection — production callers pass the SAME single-source functions the
89
+ * original loop used (`projectSemanticMapSynthesisOutput`,
90
+ * `synthesizeCertOutputSha256`), so a mismatch can only mean a genuinely
91
+ * missing capture, never a divergent re-implementation of the hash.
92
+ *
93
+ * On a sha collision within one arm role (>1 captured call projects to the
94
+ * identical output — e.g. a resumed run's re-dispatch producing
95
+ * byte-identical text), the LOWEST-seq call is used deterministically; since
96
+ * colliding calls are content-IDENTICAL by construction, this only picks a
97
+ * canonical source and never changes the reconstructed output.
98
+ */
99
+ export function reconstructSynthesizeCertJudgeReplayInputs(args) {
100
+ // role -> output_sha256 -> ascending-seq candidates.
101
+ const byRole = new Map();
102
+ for (const call of args.capturedCalls) {
103
+ if (typeof call.text !== "string")
104
+ continue;
105
+ let output;
106
+ try {
107
+ const parsed = JSON.parse(stripLlmResponseFence(call.text));
108
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
109
+ continue;
110
+ output = args.projectArmOutput(parsed);
111
+ }
112
+ catch {
113
+ continue; // unusable capture — never a join error, simply no candidate
114
+ }
115
+ const sha = args.hashArmOutput(output);
116
+ let shaIndex = byRole.get(call.role);
117
+ if (!shaIndex) {
118
+ shaIndex = new Map();
119
+ byRole.set(call.role, shaIndex);
120
+ }
121
+ const bucket = shaIndex.get(sha) ?? [];
122
+ bucket.push({ seq: call.seq, output });
123
+ shaIndex.set(sha, bucket);
124
+ }
125
+ const matched = [];
126
+ const unmatched = [];
127
+ for (const row of args.rows) {
128
+ if (row.candidate_output_status !== "ok")
129
+ continue; // no output to replay
130
+ const originalPacket = args.originalPacketsByInputId.get(row.input_id);
131
+ const bucket = row.output_sha256 ? byRole.get(row.arm)?.get(row.output_sha256) : undefined;
132
+ if (!originalPacket || !bucket || bucket.length === 0) {
133
+ unmatched.push(row);
134
+ continue;
135
+ }
136
+ const chosen = [...bucket].sort((a, b) => a.seq - b.seq)[0];
137
+ matched.push({ row, judgeInput: { original_packet: originalPacket, arm_output: chosen.output } });
138
+ }
139
+ return { matched, unmatched };
140
+ }
141
+ /** Parses the trailing `:<start>-<end>` row range off a child_summaries key
142
+ * (the `<sheet>#<col>:<start>-<end>` node-key convention) — end-anchored so
143
+ * a sheet name containing `:` is still handled safely. A key that doesn't
144
+ * match contributes no boundary (never throws): this verifier's job is
145
+ * grounding, not upstream packet-shape validation, which is owned
146
+ * elsewhere. */
147
+ function parseChildKeyRowRange(key) {
148
+ const match = /:(\d+)-(\d+)$/.exec(key);
149
+ if (!match)
150
+ return null;
151
+ return { start: Number(match[1]), end: Number(match[2]) };
152
+ }
153
+ /**
154
+ * Deterministically verifies a summary's structural claims against the
155
+ * packet's OWN structural facts (value_shape_seams row positions AND
156
+ * child_summaries' own row-range keys — both packet-supplied, equally
157
+ * grounding) — never against format-label TEXT, never against the summary's
158
+ * prose. Pure, no I/O, no LLM. `grounded` is `violations.length === 0`.
159
+ *
160
+ * Two checks, both row-position-only:
161
+ * - `fabricated_boundary_row`: a cited boundary row must be the region
162
+ * start/end, a seam's row, a seam's row − 1 (the last row of the PRIOR
163
+ * shape — summaries commonly cite the ending row, not only the row a new
164
+ * shape begins; both are grounded in the SAME seam), or a child_summaries
165
+ * entry's start/end row (a merge packet's child-partition boundary is a
166
+ * packet fact the arm legitimately saw, exactly as grounding as a seam —
167
+ * R7 Group A case 7).
168
+ * - `fabricated_transition_row`: a cited transition's `at_row` must be
169
+ * SOME seam's row exactly — a "transition" claim is specifically about a
170
+ * format CHANGE point, which only a seam represents (a child-partition
171
+ * row is a subtree split, not necessarily a shape change, so it does NOT
172
+ * ground a transition claim the way it grounds a boundary claim).
173
+ *
174
+ * `cited_format_labels` and each transition's `from`/`to` text are NEVER
175
+ * compared against `packet.format_clusters` or seam shape names — label
176
+ * TEXT matching is out of this verifier's scope (see the module doc above:
177
+ * domain-agnostic-no-static-enums; format naming is an LLM-semantic
178
+ * residual, not a deterministic-code judgment).
179
+ */
180
+ export function verifyStructuralGrounding(args) {
181
+ const violations = [];
182
+ const validBoundaryRows = new Set([args.regionStart, args.regionEnd]);
183
+ for (const seam of args.packet.value_shape_seams) {
184
+ validBoundaryRows.add(seam.row);
185
+ validBoundaryRows.add(seam.row - 1);
186
+ }
187
+ for (const child of args.packet.child_summaries) {
188
+ const range = parseChildKeyRowRange(child.key);
189
+ if (!range)
190
+ continue;
191
+ validBoundaryRows.add(range.start);
192
+ validBoundaryRows.add(range.end);
193
+ }
194
+ const seamRows = new Set(args.packet.value_shape_seams.map((seam) => seam.row));
195
+ for (const row of args.claims.cited_boundary_rows) {
196
+ if (!validBoundaryRows.has(row)) {
197
+ violations.push({
198
+ code: "fabricated_boundary_row",
199
+ message: `cited boundary row ${row} matches no packet seam (row or row-1), no child_summaries start/end, nor the region start/end (fail-closed)`,
200
+ });
201
+ }
202
+ }
203
+ for (const transition of args.claims.cited_transitions) {
204
+ if (!seamRows.has(transition.at_row)) {
205
+ violations.push({
206
+ code: "fabricated_transition_row",
207
+ message: `cited transition at row ${transition.at_row} matches no packet seam row (fail-closed; label text is not checked)`,
208
+ });
209
+ }
210
+ }
211
+ return violations;
212
+ }
213
+ // ── SG2 — extracted-claim schema + extractor honesty guard ──────────────────
214
+ //
215
+ // The extractor (SG3, an independent LLM lens — opus in production) runs at
216
+ // LOW privilege: it may only pull out what the summary text LITERALLY says,
217
+ // never infer or evaluate. `assertClaimsGroundedInText` is the boundary that
218
+ // enforces that privilege split deterministically — an extraction that cites
219
+ // a row/label absent from the summary's own text is an EXTRACTION failure
220
+ // (the extractor invented something), a category error distinct from the
221
+ // packet-grounding violations {@link verifyStructuralGrounding} reports.
222
+ /** The extractor's raw response failed to parse as JSON, or parsed to the
223
+ * wrong shape — an EXTRACTION-plane failure (§ orchestrator classifies
224
+ * `parse_error`), distinct from a grounding violation. */
225
+ export class SynthesizeCertClaimParseFail extends Error {
226
+ }
227
+ /** The extractor cited a row/label absent from the summary text it was
228
+ * extracting from — an EXTRACTION-plane honesty failure (orchestrator
229
+ * classifies `honesty_violation`), distinct from a grounding violation
230
+ * (which compares claims against the PACKET, not the summary text). */
231
+ export class SynthesizeCertClaimHonestyViolation extends Error {
232
+ }
233
+ /** Fail-closed: every number the claims cite as a boundary/transition row,
234
+ * and every format label they cite, must appear verbatim in `summaryText`
235
+ * (rows as a digit substring, labels case-insensitively) — else the
236
+ * extractor fabricated something not present in the text it was extracting
237
+ * from. Throws {@link SynthesizeCertClaimHonestyViolation} (never silently
238
+ * drops) on the first violation found. */
239
+ export function assertClaimsGroundedInText(claims, summaryText) {
240
+ const citedRows = new Set([
241
+ ...claims.cited_boundary_rows,
242
+ ...claims.cited_transitions.map((t) => t.at_row),
243
+ ]);
244
+ for (const row of citedRows) {
245
+ if (!summaryText.includes(String(row))) {
246
+ throw new SynthesizeCertClaimHonestyViolation(`synthesize-cert-judge: extractor cited row ${row} which does not appear in the summary text (fail-closed — extraction, not grounding, failure)`);
247
+ }
248
+ }
249
+ const lowerText = summaryText.toLowerCase();
250
+ const citedLabels = new Set([
251
+ ...claims.cited_format_labels,
252
+ ...claims.cited_transitions.flatMap((t) => [t.from, t.to]),
253
+ ]);
254
+ for (const label of citedLabels) {
255
+ if (!lowerText.includes(label.toLowerCase())) {
256
+ throw new SynthesizeCertClaimHonestyViolation(`synthesize-cert-judge: extractor cited format label '${label}' which does not appear in the summary text (fail-closed — extraction, not grounding, failure)`);
257
+ }
258
+ }
259
+ }
260
+ // ── SG3 — structural-claim extraction prompt + response parser ──────────────
261
+ //
262
+ // The extractor is deliberately NOT a judge: it is told so explicitly, and
263
+ // its prompt asks for verbatim extraction only (no inference, no
264
+ // evaluation). Live dispatch (scripts side) mirrors the judge's dispatch
265
+ // shape exactly — a raw callLlm with this dedicated prompt, parsed by the
266
+ // pure parser below.
267
+ export const SYNTHESIZE_CERT_STRUCTURAL_CLAIM_EXTRACTION_SYSTEM_PROMPT = "You are a STRUCTURAL CLAIM EXTRACTOR, not a judge. You are given ONE semantic-summary text describing a spreadsheet column region. Extract ONLY what the text LITERALLY states — never infer, never evaluate, never add a row number or label the text does not contain, and never judge whether the summary is correct.\n\n" +
268
+ "Extract exactly:\n" +
269
+ "(a) cited_boundary_rows: every row number the text presents as a region boundary or transition point.\n" +
270
+ "(b) cited_format_labels: every format/type label the text names (e.g. INT, DEC, ISO_DATE).\n" +
271
+ "(c) cited_transitions: every {at_row, from, to} transition the text describes, where at_row is the row number and from/to are the two format labels named as changing at that row.\n\n" +
272
+ "Reply with STRICT JSON only, no prose outside it, no markdown code fences: " +
273
+ '{"cited_boundary_rows": [integer, ...], "cited_format_labels": [string, ...], "cited_transitions": [{"at_row": integer, "from": string, "to": string}, ...]}. ' +
274
+ "Empty arrays are honest and acceptable when the text cites nothing of that kind. No additional fields.";
275
+ /** Fail-closed total-shape guard for an extractor realization's returned
276
+ * claims — anything outside {cited_boundary_rows: number[],
277
+ * cited_format_labels: string[], cited_transitions: {at_row:number,
278
+ * from:string, to:string}[]} is an extraction-plane failure, never a
279
+ * silently-coerced result. Throws {@link SynthesizeCertClaimParseFail}. */
280
+ export function assertSynthesizeCertStructuralClaims(claims) {
281
+ const record = claims;
282
+ const keys = Object.keys(record);
283
+ if (keys.length !== 3 ||
284
+ !("cited_boundary_rows" in record) ||
285
+ !("cited_format_labels" in record) ||
286
+ !("cited_transitions" in record)) {
287
+ throw new SynthesizeCertClaimParseFail(`synthesize-cert-judge: claims must be exactly {cited_boundary_rows, cited_format_labels, cited_transitions}, got keys [${keys.join(", ")}] (fail-closed)`);
288
+ }
289
+ const rows = record.cited_boundary_rows;
290
+ if (!Array.isArray(rows) || !rows.every((r) => typeof r === "number" && Number.isFinite(r))) {
291
+ throw new SynthesizeCertClaimParseFail("synthesize-cert-judge: cited_boundary_rows must be an array of numbers (fail-closed)");
292
+ }
293
+ const labels = record.cited_format_labels;
294
+ if (!Array.isArray(labels) || !labels.every((l) => typeof l === "string" && l.length > 0)) {
295
+ throw new SynthesizeCertClaimParseFail("synthesize-cert-judge: cited_format_labels must be an array of non-empty strings (fail-closed)");
296
+ }
297
+ const transitions = record.cited_transitions;
298
+ const validTransition = (t) => {
299
+ if (typeof t !== "object" || t === null || Array.isArray(t))
300
+ return false;
301
+ const obj = t;
302
+ if (Object.keys(obj).length !== 3)
303
+ return false;
304
+ const atRow = obj.at_row;
305
+ const from = obj.from;
306
+ const to = obj.to;
307
+ return (typeof atRow === "number" &&
308
+ Number.isFinite(atRow) &&
309
+ typeof from === "string" &&
310
+ from.length > 0 &&
311
+ typeof to === "string" &&
312
+ to.length > 0);
313
+ };
314
+ if (!Array.isArray(transitions) || !transitions.every(validTransition)) {
315
+ throw new SynthesizeCertClaimParseFail("synthesize-cert-judge: cited_transitions must be an array of {at_row:number, from:string, to:string} (fail-closed)");
316
+ }
317
+ }
318
+ /**
319
+ * Pure parser: an extractor realization's raw response text → TOTAL claims,
320
+ * or a thrown {@link SynthesizeCertClaimParseFail} (never a partial/coerced
321
+ * result) — no LLM touched, so this is unit-testable against fixture
322
+ * response strings. Reuses {@link stripLlmResponseFence} (shared with the
323
+ * judge parser) and {@link assertSynthesizeCertStructuralClaims} as the
324
+ * single shape authority.
325
+ */
326
+ export function parseSynthesizeCertStructuralClaimsResponseText(text) {
327
+ const jsonText = stripLlmResponseFence(text);
328
+ let parsed;
329
+ try {
330
+ parsed = JSON.parse(jsonText);
331
+ }
332
+ catch (error) {
333
+ throw new SynthesizeCertClaimParseFail(`synthesize-cert-judge: structural-claim extraction response is not valid JSON (fail-closed): ${error instanceof Error ? error.message : String(error)}`);
334
+ }
335
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
336
+ throw new SynthesizeCertClaimParseFail(`synthesize-cert-judge: structural-claim extraction response must be a JSON object, got ${JSON.stringify(parsed)} (fail-closed)`);
337
+ }
338
+ assertSynthesizeCertStructuralClaims(parsed);
339
+ return parsed;
340
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * S5 — failure-preserving coordinate loop for the B4 `synthesize-cert/v1`
3
+ * bench (design 20260706-b4-r8-harness-design v3 §8/§15.5, R8).
4
+ *
5
+ * The expected universe is frozen packets × declared_reps × the three arms.
6
+ * EVERY coordinate yields exactly one judgement row — a failed synthesize, a
7
+ * failed judge, and a soft-abort remainder are all rows (never silent drops),
8
+ * with the synthesize OUTPUT plane and the judge EXECUTION plane recorded
9
+ * independently and metrics `not_judged` on any failure. Each arm's execution
10
+ * is a SINGLE synthesize call on the frozen packet (negative: the
11
+ * `input_corruption/v1` mutation of it, computed once per input BEFORE any
12
+ * dispatch so a leverless input fails pre-spend); there is no per-arm subtree
13
+ * walk (owner decision 2a).
14
+ *
15
+ * Resume/re-run semantics (§8): prior rows are keyed by coordinate; a decisive
16
+ * prior row is kept without re-dispatch, a non-decisive one is re-executed and
17
+ * OVERWRITTEN with `attempts` incremented — the published judge_failure_rate's
18
+ * residual-only nature stays visible. The caller re-binds the run to the
19
+ * frozen manifest (S2 facts binding + S7 record assembly); the loop never
20
+ * shrinks or extends the universe it is handed.
21
+ *
22
+ * Realization error classification: an arm realization SHOULD throw the typed
23
+ * errors below so §6.2-1's parse/structural zero-tolerance is attributable;
24
+ * any untyped rejection is an honest transport loss (`not_run`). A judge
25
+ * rejection classifies as `timeout` only via its typed error, else
26
+ * `judge_error`. An output that fails the source-safe envelope or verdicts
27
+ * outside the total enum are structural failures of the respective plane.
28
+ */
29
+ import { createHash } from "node:crypto";
30
+ import { assertSynthesisOutputBounded, } from "../reconstruct/comprehension-semantic-map.js";
31
+ import { assertSynthesizeCertJudgeVerdicts, } from "./synthesize-cert-judge.js";
32
+ import { applyInputCorruptionV1, } from "./synthesize-cert-mutation.js";
33
+ import { isDecisiveRow, SYNTHESIZE_CERT_ARMS, } from "./synthesize-cert-record.js";
34
+ /** The realization parsed no output from the provider response. */
35
+ export class SynthesizeCertParseFail extends Error {
36
+ }
37
+ /** The realization parsed output of the wrong shape. */
38
+ export class SynthesizeCertStructuralFail extends Error {
39
+ }
40
+ /** The judge realization timed out. */
41
+ export class SynthesizeCertJudgeTimeout extends Error {
42
+ }
43
+ /** Canonical identity of one arm output — capsule per_row binding material
44
+ * (§18) and the optional honesty field on ok rows. */
45
+ export function synthesizeCertOutputSha256(out) {
46
+ return createHash("sha256")
47
+ .update(JSON.stringify({
48
+ semantic_summary: out.semantic_summary,
49
+ boundaries: out.boundaries.map((b) => ({
50
+ row: b.row,
51
+ character_before: b.character_before,
52
+ character_after: b.character_after,
53
+ })),
54
+ }))
55
+ .digest("hex");
56
+ }
57
+ const DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
58
+ /** Canonical coordinate identity (packet input_id × rep × arm) — the SINGLE
59
+ * source both this loop's own priorRows dedup-guard and the orchestrator's
60
+ * --resume progress fold ({@link foldSynthesizeCertProgressRows}) key against,
61
+ * so the two can never drift into disagreeing key formats. */
62
+ export const coordinateKey = (inputId, rep, arm) => `${inputId} ${rep} ${arm}`;
63
+ /**
64
+ * Folds RAW progress rows — e.g. read line-by-line from an incremental
65
+ * `onRowComplete` sidecar, which can carry more than one line for the same
66
+ * coordinate across a single run's own re-attempts — into one row per
67
+ * coordinate, LAST WRITE WINS. This is the only normalization a --resume
68
+ * caller needs before handing the result to `runSynthesizeCertLoop` as
69
+ * `priorRows`: the loop ITSELF already throws (see above) if `priorRows`
70
+ * still carries a duplicate coordinate, so this function does not duplicate
71
+ * that (or any other) validation — dedup is its only job.
72
+ *
73
+ * Generic over any row shape carrying the three coordinate fields (not just
74
+ * {@link SynthesizeCertJudgementRow}): sibling progress sidecars — e.g. the
75
+ * rejudge and structured-grounding cuts' own per-coordinate progress rows —
76
+ * reuse this SAME fold instead of re-implementing coordinate dedup.
77
+ */
78
+ export function foldSynthesizeCertProgressRows(rows) {
79
+ const byCoordinate = new Map();
80
+ for (const row of rows) {
81
+ byCoordinate.set(coordinateKey(row.input_id, row.rep, row.arm), row);
82
+ }
83
+ return [...byCoordinate.values()];
84
+ }
85
+ export async function runSynthesizeCertLoop(args) {
86
+ const maxConsecutive = args.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
87
+ if (!Number.isSafeInteger(args.declaredReps) || args.declaredReps < 1) {
88
+ throw new Error(`synthesize-cert-loop: declaredReps must be a positive safe integer, got ${args.declaredReps}`);
89
+ }
90
+ if (!Number.isSafeInteger(maxConsecutive) || maxConsecutive < 1) {
91
+ throw new Error(`synthesize-cert-loop: maxConsecutiveFailures must be a positive safe integer`);
92
+ }
93
+ const seenInputIds = new Set();
94
+ for (const p of args.packets) {
95
+ if (seenInputIds.has(p.input_id)) {
96
+ throw new Error(`synthesize-cert-loop: duplicate packet input_id ${p.input_id}`);
97
+ }
98
+ seenInputIds.add(p.input_id);
99
+ }
100
+ // Pre-spend mutation of EVERY input (§6 lever guarantee): a leverless input
101
+ // throws here, before any dispatch — never mid-run after paid calls.
102
+ const mutationsByInputId = new Map();
103
+ for (const p of args.packets) {
104
+ mutationsByInputId.set(p.input_id, applyInputCorruptionV1(p.packet, { seed: args.mutationSeed }));
105
+ }
106
+ const priorByCoordinate = new Map();
107
+ for (const prior of args.priorRows ?? []) {
108
+ const key = coordinateKey(prior.input_id, prior.rep, prior.arm);
109
+ if (priorByCoordinate.has(key)) {
110
+ throw new Error(`synthesize-cert-loop: priorRows carry coordinate ${key} more than once`);
111
+ }
112
+ priorByCoordinate.set(key, prior);
113
+ }
114
+ const rows = [];
115
+ const emit = async (row) => {
116
+ rows.push(row);
117
+ await args.onRowComplete?.(row);
118
+ };
119
+ let consecutiveFailures = 0;
120
+ let aborted = null;
121
+ let synthesizeCalls = 0;
122
+ let judgeCalls = 0;
123
+ for (const frozen of args.packets) {
124
+ const mutation = mutationsByInputId.get(frozen.input_id);
125
+ for (let rep = 1; rep <= args.declaredReps; rep += 1) {
126
+ for (const arm of SYNTHESIZE_CERT_ARMS) {
127
+ const isNegative = arm === "negative_control";
128
+ const rowBase = {
129
+ row_id: `${frozen.input_id}.r${rep}.${arm}`,
130
+ fixture_id: frozen.fixture_id,
131
+ input_id: frozen.input_id,
132
+ input_sha256: isNegative ? mutation.mutated_input_sha256 : frozen.input_sha256,
133
+ rep,
134
+ arm,
135
+ stratum: { seam: frozen.stratum.seam, merge: frozen.stratum.merge },
136
+ ...(isNegative ? { source_input_id: frozen.input_id } : {}),
137
+ };
138
+ const prior = priorByCoordinate.get(coordinateKey(frozen.input_id, rep, arm));
139
+ if (prior && isDecisiveRow(prior)) {
140
+ await emit(prior); // resume: a decisive coordinate is never re-spent.
141
+ continue;
142
+ }
143
+ const attempts = prior ? (prior.attempts ?? 1) + 1 : 1;
144
+ if (aborted) {
145
+ await emit({
146
+ ...rowBase,
147
+ candidate_output_status: "not_run",
148
+ judge_status: "not_run",
149
+ metrics: { grounding: "not_judged", boundary: "not_judged" },
150
+ attempts,
151
+ });
152
+ continue;
153
+ }
154
+ // ── synthesize plane: ONE call on the frozen (or mutated) packet.
155
+ let output = null;
156
+ let outputStatus = "ok";
157
+ synthesizeCalls += 1; // attempt-counted at dispatch
158
+ let raw = null;
159
+ try {
160
+ raw = await args.arms[arm](isNegative ? mutation.mutated : frozen.packet);
161
+ }
162
+ catch (error) {
163
+ outputStatus =
164
+ error instanceof SynthesizeCertParseFail
165
+ ? "parse_fail"
166
+ : error instanceof SynthesizeCertStructuralFail
167
+ ? "structural_fail"
168
+ : "not_run"; // untyped rejection = honest transport loss
169
+ }
170
+ if (raw !== null) {
171
+ try {
172
+ assertSynthesisOutputBounded(raw); // envelope: parsed but wrong shape
173
+ output = raw;
174
+ }
175
+ catch {
176
+ outputStatus = "structural_fail";
177
+ }
178
+ }
179
+ // ── judge plane: original packet reference, negative included (§7).
180
+ let judgeStatus = "not_run";
181
+ let metrics = {
182
+ grounding: "not_judged",
183
+ boundary: "not_judged",
184
+ };
185
+ if (output !== null) {
186
+ judgeCalls += 1; // attempt-counted at dispatch
187
+ try {
188
+ const verdicts = await args.judge({
189
+ original_packet: frozen.packet,
190
+ arm_output: output,
191
+ });
192
+ assertSynthesizeCertJudgeVerdicts(verdicts);
193
+ judgeStatus = "ok";
194
+ metrics = { grounding: verdicts.grounding, boundary: verdicts.boundary };
195
+ }
196
+ catch (error) {
197
+ judgeStatus = error instanceof SynthesizeCertJudgeTimeout ? "timeout" : "judge_error";
198
+ }
199
+ }
200
+ const row = {
201
+ ...rowBase,
202
+ candidate_output_status: outputStatus,
203
+ judge_status: judgeStatus,
204
+ metrics,
205
+ ...(output !== null ? { output_sha256: synthesizeCertOutputSha256(output) } : {}),
206
+ attempts,
207
+ };
208
+ await emit(row);
209
+ if (outputStatus === "ok" && judgeStatus === "ok") {
210
+ consecutiveFailures = 0;
211
+ }
212
+ else {
213
+ consecutiveFailures += 1;
214
+ if (consecutiveFailures >= maxConsecutive) {
215
+ aborted = {
216
+ reason: `soft-abort: ${consecutiveFailures} consecutive failed coordinates (threshold ${maxConsecutive})`,
217
+ at_coordinate: row.row_id,
218
+ };
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ return {
225
+ rows,
226
+ negative_mutations: new Map([...mutationsByInputId].map(([inputId, m]) => [
227
+ inputId,
228
+ { mutated_input_sha256: m.mutated_input_sha256, levers_applied: m.levers_applied },
229
+ ])),
230
+ aborted,
231
+ synthesize_calls: synthesizeCalls,
232
+ judge_calls: judgeCalls,
233
+ };
234
+ }