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,117 @@
1
+ /**
2
+ * S3 — the `input_corruption/v1` negative-control transform for the B4
3
+ * `synthesize-cert/v1` bench (design 20260706-b4-r8-harness-design v3 §6/§15.3).
4
+ *
5
+ * The negative arm is defined by INPUT mutation only (every arm runs the same
6
+ * prompt), realized as this ONE deterministic named transform: the record cites
7
+ * `mutation_kind` + `mutation_params`, this implementation owns boundedness.
8
+ * Both judged metrics are targeted (§6.2-3) through two content-changing levers:
9
+ *
10
+ * - grounding lever = `relabel/v1`: every format_cluster string and every
11
+ * child_summary is deterministically rewritten (seed-based), so the sorted
12
+ * cluster list / child prose CONTENT changes — the sha moves and the arm
13
+ * authors from wrong facts, which the judge (against the ORIGINAL packet)
14
+ * can flag. Child summaries rotate between siblings when that changes bytes
15
+ * (plausible-but-wrong prose); otherwise a seed suffix guarantees the change.
16
+ * - boundary lever = `seam_offset/v1`: every value_shape seam row moves by a
17
+ * seed-picked offset ≥2 where the node range permits (|Δ|=1 only as the
18
+ * tight-range fallback) — ≥2 escapes the production ±1 anchor tolerance, so
19
+ * a mutated-seam-following output cannot silently re-anchor to the original.
20
+ *
21
+ * Per-metric provenance (§6 G/I): the result reports which levers ACTUALLY
22
+ * changed the packet — on a no-seam input the boundary lever is inert and the
23
+ * row targets grounding only ("no spurious boundary" is what boundary then
24
+ * judges). An input with NO applicable lever (no clusters, no children, no
25
+ * seams) is REJECTED fail-closed: it cannot host a negative row, and the
26
+ * sampler/loop must exclude it (§6 레버 보장).
27
+ *
28
+ * Whether the corruption REALLY degrades the metrics is a semantic question —
29
+ * R7 human curation (§13.3), never re-enforced deterministically here.
30
+ */
31
+ import { createHash } from "node:crypto";
32
+ import { SYNTHESIZE_CERT_METRICS, } from "./synthesize-cert-record.js";
33
+ import { synthesizeCertInputSha256 } from "./synthesize-cert-sampler.js";
34
+ export const SYNTHESIZE_CERT_MUTATION_KIND = "input_corruption/v1";
35
+ export const INPUT_CORRUPTION_GROUNDING_LEVER = "relabel/v1";
36
+ export const INPUT_CORRUPTION_BOUNDARY_LEVER = "seam_offset/v1";
37
+ const sha8 = (text) => createHash("sha256").update(text).digest("hex").slice(0, 8);
38
+ /**
39
+ * Applies `input_corruption/v1` to a frozen packet. Throws on an input with no
40
+ * applicable lever (the §6 negative-sample exclusion), so "a negative row that
41
+ * silently ran the original packet" is structurally impossible.
42
+ */
43
+ export function applyInputCorruptionV1(packet, params) {
44
+ if (typeof params.seed !== "string" || params.seed.length === 0) {
45
+ throw new Error("input_corruption/v1: seed must be a non-empty string");
46
+ }
47
+ const originalSha = synthesizeCertInputSha256(packet); // envelope-asserts the input too
48
+ const hasClusters = packet.format_clusters.length > 0;
49
+ const hasChildren = packet.child_summaries.length > 0;
50
+ const hasSeams = packet.value_shape_seams.length > 0;
51
+ if (!hasClusters && !hasChildren && !hasSeams) {
52
+ throw new Error("input_corruption/v1: input has no applicable lever (no format_clusters, no child_summaries, no seams) — it cannot host a negative row and must be excluded from the negative sample (§6)");
53
+ }
54
+ // ── grounding lever: relabel/v1 ─────────────────────────────────────────────
55
+ const mutatedClusters = packet.format_clusters.map((s) => `${s}~${sha8(`${params.seed}|cluster|${s}`)}`);
56
+ let mutatedChildren = packet.child_summaries.map((c) => ({ key: c.key, summary: c.summary }));
57
+ if (hasChildren) {
58
+ const n = mutatedChildren.length;
59
+ const rotated = mutatedChildren.map((c, i) => ({
60
+ key: c.key,
61
+ summary: mutatedChildren[(i + 1) % n].summary,
62
+ }));
63
+ const rotationChanged = rotated.some((c, i) => c.summary !== mutatedChildren[i].summary);
64
+ mutatedChildren = rotationChanged
65
+ ? rotated // plausible-but-wrong: each subtree now carries a SIBLING's prose
66
+ : mutatedChildren.map((c) => ({
67
+ key: c.key,
68
+ summary: `${c.summary} ~${sha8(`${params.seed}|child|${c.key}`)}`,
69
+ }));
70
+ }
71
+ const groundingApplied = hasClusters || hasChildren;
72
+ // ── boundary lever: seam_offset/v1 ─────────────────────────────────────────
73
+ const r = packet.node_ref;
74
+ const preferred = 2 + (parseInt(sha8(`${params.seed}|seam`), 16) % 3); // 2..4
75
+ const offsetRow = (row) => {
76
+ for (const delta of [preferred, -preferred, 1, -1]) {
77
+ const moved = row + delta;
78
+ if (moved !== row && moved > r.row_start && moved <= r.row_end)
79
+ return moved;
80
+ }
81
+ return row - 1; // degenerate 1-row range: still a guaranteed change
82
+ };
83
+ const mutatedSeams = packet.value_shape_seams.map((s) => ({
84
+ row: offsetRow(s.row),
85
+ prev_shape: s.prev_shape,
86
+ new_shape: s.new_shape,
87
+ }));
88
+ const mutated = {
89
+ node_ref: { sheet: r.sheet, column_index: r.column_index, row_start: r.row_start, row_end: r.row_end },
90
+ format_clusters: mutatedClusters,
91
+ value_shape_seams: mutatedSeams,
92
+ child_summaries: mutatedChildren,
93
+ };
94
+ const mutatedSha = synthesizeCertInputSha256(mutated);
95
+ if (mutatedSha === originalSha) {
96
+ throw new Error("input_corruption/v1: mutation left the packet sha unchanged (impossible state — transform guarantee broken)");
97
+ }
98
+ return {
99
+ mutated,
100
+ mutated_input_sha256: mutatedSha,
101
+ levers_applied: { grounding: groundingApplied, boundary: hasSeams },
102
+ };
103
+ }
104
+ /** The record's `negative_arm` block for this transform — single source so the
105
+ * harness cannot cite the kind while running something else (§6.3). */
106
+ export function buildInputCorruptionV1NegativeArm(seed) {
107
+ return {
108
+ arm: "negative_control",
109
+ mutation_kind: SYNTHESIZE_CERT_MUTATION_KIND,
110
+ mutation_params: {
111
+ grounding_lever: INPUT_CORRUPTION_GROUNDING_LEVER,
112
+ boundary_lever: INPUT_CORRUPTION_BOUNDARY_LEVER,
113
+ seed,
114
+ },
115
+ targeted_metrics: [...SYNTHESIZE_CERT_METRICS],
116
+ };
117
+ }
@@ -0,0 +1,182 @@
1
+ import { assertSynthesisInputBounded, assertSynthesisOutputBounded, buildSynthesisInputForNode, } from "../reconstruct/comprehension-semantic-map.js";
2
+ import { synthesizeCertDeterministicFactsSha256, synthesizeCertInputSha256, } from "./synthesize-cert-sampler.js";
3
+ /**
4
+ * Freezes one packet per sampled manifest entry. For each entry the pipeline's
5
+ * deterministic facts are re-derived and bound to the manifest's
6
+ * `deterministic_facts_sha256` (fail-closed: a wrong workbook, a changed
7
+ * reduce config, or a stale manifest cannot silently freeze). Merge entries
8
+ * get reference-authored child summaries (memoized per pipeline); leaf entries
9
+ * freeze with `child_summaries: []` and spend nothing.
10
+ */
11
+ export async function freezeSynthesizeCertPackets(args) {
12
+ const summaryCaches = new Map();
13
+ let referenceCalls = 0;
14
+ /** Post-order reference authoring of `key`'s summary (and its non-subsumed
15
+ * descendants'), memoized — mirrors the production bridge walk. */
16
+ const authorSummary = async (pipeline, cache, key) => {
17
+ if (cache.has(key))
18
+ return;
19
+ const tnode = pipeline.trace.nodes.get(key);
20
+ if (!tnode)
21
+ throw new Error(`synthesize-cert-packet: trace node missing for ${key} (reference walk).`);
22
+ const consumed = tnode.child_keys.filter((k) => pipeline.modes.get(k) !== "subsumed");
23
+ for (const child of consumed)
24
+ await authorSummary(pipeline, cache, child);
25
+ const input = buildSynthesisInputForNode(pipeline.trace, pipeline.nodesByKey, pipeline.modes, key, cache);
26
+ assertSynthesisInputBounded(input);
27
+ referenceCalls += 1; // attempt-counted at dispatch (W2-X7-001 discipline).
28
+ const out = await args.referenceSynthesize(input);
29
+ assertSynthesisOutputBounded(out);
30
+ cache.set(key, out.semantic_summary);
31
+ };
32
+ const packets = [];
33
+ for (const entry of args.entries) {
34
+ const pipeline = args.resolvePipeline(entry);
35
+ if (!pipeline.trace.nodes.has(entry.node_key)) {
36
+ throw new Error(`synthesize-cert-packet: manifest entry ${entry.input_id} names node ${entry.node_key}, absent from its resolved pipeline`);
37
+ }
38
+ let cache = summaryCaches.get(pipeline);
39
+ if (!cache) {
40
+ cache = new Map();
41
+ summaryCaches.set(pipeline, cache);
42
+ }
43
+ // The manifest's merge flag must be TRUE about the resolved pipeline — a
44
+ // flipped flag would either skip authoring (caught below) or silently
45
+ // misfile the row's stratum in the record.
46
+ const mode = pipeline.modes.get(entry.node_key);
47
+ if ((mode === "accumulating") !== entry.stratum.merge) {
48
+ throw new Error(`synthesize-cert-packet: ${entry.input_id} declares merge=${entry.stratum.merge} but the pipeline classifies ${entry.node_key} as '${mode}' (fail-closed)`);
49
+ }
50
+ // Manifest ↔ pipeline binding (§4): the deterministic facts the pipeline
51
+ // yields NOW must hash to what the manifest froze at sampling time.
52
+ const tnode = pipeline.trace.nodes.get(entry.node_key);
53
+ const consumed = tnode.child_keys.filter((k) => pipeline.modes.get(k) !== "subsumed");
54
+ const factsProbe = buildSynthesisInputForNode(pipeline.trace, pipeline.nodesByKey, pipeline.modes, entry.node_key, new Map(consumed.map((k) => [k, ""])));
55
+ const facts = {
56
+ node_ref: factsProbe.node_ref,
57
+ format_clusters: factsProbe.format_clusters,
58
+ value_shape_seams: factsProbe.value_shape_seams,
59
+ };
60
+ const factsSha = synthesizeCertDeterministicFactsSha256(facts);
61
+ if (factsSha !== entry.deterministic_facts_sha256) {
62
+ throw new Error(`synthesize-cert-packet: ${entry.input_id} deterministic facts recompute to ${factsSha} but the manifest froze ${entry.deterministic_facts_sha256} — wrong workbook, changed reduce config, or stale manifest (fail-closed)`);
63
+ }
64
+ // Reference child authoring (merge only; a frontier node consumes no
65
+ // children and the builder returns child_summaries: []).
66
+ for (const child of consumed) {
67
+ if (entry.stratum.merge)
68
+ await authorSummary(pipeline, cache, child);
69
+ }
70
+ const packet = buildSynthesisInputForNode(pipeline.trace, pipeline.nodesByKey, pipeline.modes, entry.node_key, cache);
71
+ assertSynthesisInputBounded(packet);
72
+ packets.push({
73
+ fixture_id: entry.fixture_id,
74
+ input_id: entry.input_id,
75
+ node_key: entry.node_key,
76
+ stratum: entry.stratum,
77
+ deterministic_facts_sha256: entry.deterministic_facts_sha256,
78
+ input_sha256: synthesizeCertInputSha256(packet),
79
+ packet,
80
+ });
81
+ }
82
+ return { packets, reference_synthesize_calls: referenceCalls };
83
+ }
84
+ // ── --resume checkpoint (live-wiring cut, owner decision D1) ─────────────────
85
+ //
86
+ // Freeze is the ONE paid phase a resumed run must never repeat: this contract
87
+ // durably serializes exactly what `freezeSynthesizeCertPackets` produced, so a
88
+ // crashed/interrupted --go run can reload the identical frozen packets instead
89
+ // of re-spending the reference-authoring calls. The orchestrator script owns
90
+ // the file I/O (write right after freeze completes, read at --resume start);
91
+ // this module owns the contract shape and its fail-closed verification.
92
+ export const SYNTHESIZE_CERT_FREEZE_CHECKPOINT_CONTRACT = "synthesize-cert-freeze-checkpoint/v1";
93
+ /** Pure projection of a freeze result into the durable checkpoint shape — no
94
+ * I/O; the caller JSON.stringifies and writes it. */
95
+ export function serializeSynthesizeCertFreezeCheckpoint(frozen, manifestIdentitySha256) {
96
+ return {
97
+ checkpoint_contract: SYNTHESIZE_CERT_FREEZE_CHECKPOINT_CONTRACT,
98
+ manifest_identity_sha256: manifestIdentitySha256,
99
+ reference_synthesize_calls: frozen.reference_synthesize_calls,
100
+ packets: frozen.packets,
101
+ };
102
+ }
103
+ /**
104
+ * Fail-closed parse + re-verification of a persisted checkpoint. Every check
105
+ * throws (never silently coerces or drops): contract tag mismatch, manifest
106
+ * identity mismatch, a malformed packet shape, a duplicate input_id, the
107
+ * checkpoint's input_id SET disagreeing with the expected manifest (extra OR
108
+ * missing — no silent scope-shrink/grow), and — the tamper/corruption guard —
109
+ * each packet's `input_sha256` failing to recompute via the SAME canonical
110
+ * hash (`synthesizeCertInputSha256`) `freezeSynthesizeCertPackets` used to
111
+ * produce it. Reuses `assertSynthesisInputBounded` (the shipped source-safe
112
+ * envelope guard) rather than re-implementing packet shape checks.
113
+ */
114
+ export function parseSynthesizeCertFreezeCheckpoint(raw, args) {
115
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
116
+ throw new Error("synthesize-cert-packet: freeze checkpoint must be a JSON object (fail-closed)");
117
+ }
118
+ const obj = raw;
119
+ if (obj.checkpoint_contract !== SYNTHESIZE_CERT_FREEZE_CHECKPOINT_CONTRACT) {
120
+ throw new Error(`synthesize-cert-packet: freeze checkpoint carries contract ${JSON.stringify(obj.checkpoint_contract)}, expected ${SYNTHESIZE_CERT_FREEZE_CHECKPOINT_CONTRACT} (fail-closed)`);
121
+ }
122
+ if (obj.manifest_identity_sha256 !== args.expectedManifestIdentitySha256) {
123
+ throw new Error(`synthesize-cert-packet: freeze checkpoint manifest identity ${JSON.stringify(obj.manifest_identity_sha256)} disagrees with the expected manifest ${args.expectedManifestIdentitySha256} (fail-closed — same --fixture args in same order?)`);
124
+ }
125
+ if (typeof obj.reference_synthesize_calls !== "number" ||
126
+ !Number.isSafeInteger(obj.reference_synthesize_calls) ||
127
+ obj.reference_synthesize_calls < 0) {
128
+ throw new Error("synthesize-cert-packet: freeze checkpoint reference_synthesize_calls must be a nonnegative safe integer (fail-closed)");
129
+ }
130
+ if (!Array.isArray(obj.packets)) {
131
+ throw new Error("synthesize-cert-packet: freeze checkpoint packets must be an array (fail-closed)");
132
+ }
133
+ const seenInputIds = new Set();
134
+ const packets = obj.packets.map((rawPacket, index) => {
135
+ if (typeof rawPacket !== "object" || rawPacket === null || Array.isArray(rawPacket)) {
136
+ throw new Error(`synthesize-cert-packet: freeze checkpoint packets[${index}] must be an object (fail-closed)`);
137
+ }
138
+ const p = rawPacket;
139
+ const { fixture_id, input_id, node_key, stratum, deterministic_facts_sha256, input_sha256, packet } = p;
140
+ if (typeof fixture_id !== "string" ||
141
+ typeof input_id !== "string" ||
142
+ typeof node_key !== "string" ||
143
+ typeof deterministic_facts_sha256 !== "string" ||
144
+ typeof input_sha256 !== "string" ||
145
+ typeof stratum !== "object" || stratum === null || Array.isArray(stratum) ||
146
+ typeof stratum.seam !== "boolean" ||
147
+ typeof stratum.merge !== "boolean" ||
148
+ typeof packet !== "object" || packet === null || Array.isArray(packet)) {
149
+ throw new Error(`synthesize-cert-packet: freeze checkpoint packets[${index}] has a malformed shape (fail-closed)`);
150
+ }
151
+ if (seenInputIds.has(input_id)) {
152
+ throw new Error(`synthesize-cert-packet: freeze checkpoint lists input_id ${input_id} more than once (fail-closed)`);
153
+ }
154
+ seenInputIds.add(input_id);
155
+ // Reuse the shipped source-safe envelope guard (§13.6) instead of a second
156
+ // hand-rolled shape check — a malformed/enriched packet fails here.
157
+ assertSynthesisInputBounded(packet);
158
+ const recomputedSha = synthesizeCertInputSha256(packet);
159
+ if (recomputedSha !== input_sha256) {
160
+ throw new Error(`synthesize-cert-packet: freeze checkpoint packet ${input_id} recomputes input_sha256 ${recomputedSha}, checkpoint declares ${input_sha256} (fail-closed — tampered or corrupted checkpoint)`);
161
+ }
162
+ return {
163
+ fixture_id,
164
+ input_id,
165
+ node_key: node_key,
166
+ stratum: {
167
+ seam: stratum.seam,
168
+ merge: stratum.merge,
169
+ },
170
+ deterministic_facts_sha256,
171
+ input_sha256,
172
+ packet: packet,
173
+ };
174
+ });
175
+ const expectedIds = new Set(args.expectedInputIds);
176
+ const extra = [...seenInputIds].filter((id) => !expectedIds.has(id));
177
+ const missing = [...expectedIds].filter((id) => !seenInputIds.has(id));
178
+ if (extra.length > 0 || missing.length > 0) {
179
+ throw new Error(`synthesize-cert-packet: freeze checkpoint input_id set disagrees with the expected manifest (extra=[${extra.join(", ")}] missing=[${missing.join(", ")}]) — fail-closed, scope-shrink/grow forbidden`);
180
+ }
181
+ return { packets, reference_synthesize_calls: obj.reference_synthesize_calls };
182
+ }