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.
- package/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +137 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +575 -24
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +75 -3
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +282 -29
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +135 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +188 -47
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
- package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
- package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
- package/dist/core-runtime/reconstruct/record.js +42 -0
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
- package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
- package/dist/core-runtime/reconstruct/run.js +5198 -536
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
- package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +217 -5
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +12 -5
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +18 -3
- package/settings.example.json +77 -16
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* provider calls) and by the G7 committed-config guard (which covers every
|
|
8
8
|
* committed seat). Review-side runtime enforcement is a noted follow-up, so the
|
|
9
9
|
* runtime gate today is wired on the reconstruct live path only. An unlisted
|
|
10
|
-
* (provider, model) is rejected fail-loud at those gate points.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* (provider, model) is rejected fail-loud at those gate points. The only
|
|
11
|
+
* runtime-owned exception is the B7 bench-candidate option passed explicitly by
|
|
12
|
+
* a benchmark harness: it can rescue an UNREGISTERED pair only at an exact
|
|
13
|
+
* allowlisted route path, and it never rescues registered role mismatches.
|
|
14
|
+
* Settings resolution itself stays a PURE projection and does NOT apply this
|
|
15
|
+
* gate (see settings-chain). The
|
|
13
16
|
* authority ships with the onto install (it is located from the install root,
|
|
14
17
|
* like core-lens-registry.yaml), so it is always present. The registry is data
|
|
15
18
|
* (human-curated, evidence-cited); this module loads it and provides the
|
|
@@ -29,22 +32,154 @@ import { fileURLToPath } from "node:url";
|
|
|
29
32
|
import { parse as parseYaml } from "yaml";
|
|
30
33
|
import { z } from "zod";
|
|
31
34
|
import { resolveInstallationPath } from "./installation-paths.js";
|
|
35
|
+
import { REVIEW_EXECUTION_UNIT_IDS } from "./review-execution-units.js";
|
|
32
36
|
export const SUPPORTED_MODELS_AUTHORITY_PATH = ".onto/authority/supported-models.yaml";
|
|
37
|
+
/**
|
|
38
|
+
* Role vocabulary — the sealed 6-role set, each grounded in an existing code
|
|
39
|
+
* seat (INV-MODEL-1 role-aware design §2.1; `review` added by the 2026-07-11
|
|
40
|
+
* review-role registration design): `author` (semantic_author actor, certified
|
|
41
|
+
* by golden full-pipeline completion), `semantic_map_synthesize` /
|
|
42
|
+
* `semantic_map_verify` (the semantic-map capability pair), `answer_support_judge`
|
|
43
|
+
* (the judge adoption dispatch), `confirmation_provider` (confirmation actor),
|
|
44
|
+
* `review` (the review execution seats — actors teamlead/lens/synthesize and
|
|
45
|
+
* their unit overrides). The vocabulary is sealed here; which roles are
|
|
46
|
+
* LISTABLE in a registry entry is governed separately by {@link CONTRACTED_ROLES}.
|
|
47
|
+
*/
|
|
48
|
+
export const SUPPORTED_MODEL_ROLES = [
|
|
49
|
+
"author",
|
|
50
|
+
"semantic_map_synthesize",
|
|
51
|
+
"semantic_map_verify",
|
|
52
|
+
"answer_support_judge",
|
|
53
|
+
"confirmation_provider",
|
|
54
|
+
"review",
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Roles with a DEFINED evidence contract — the only roles an entry may list in
|
|
58
|
+
* `roles`. Listing a vocabulary role without a contract fails registry load
|
|
59
|
+
* (fail-closed): certification must never outrun its evidence definition.
|
|
60
|
+
* Growing this set means defining that role's evidence contract and carries an
|
|
61
|
+
* INVARIANT-CHANGE: INV-MODEL-1 marker (role authority change).
|
|
62
|
+
*/
|
|
63
|
+
export const CONTRACTED_ROLES = [
|
|
64
|
+
"author",
|
|
65
|
+
"semantic_map_synthesize",
|
|
66
|
+
// Evidence contract: review-cert/v2 (2026-07-11 review-role registration
|
|
67
|
+
// design §4) — per-check pass-rate vs a contemporaneous baseline arm,
|
|
68
|
+
// absolute core-check floors, pinned check universe, R7 human curation.
|
|
69
|
+
"review",
|
|
70
|
+
];
|
|
33
71
|
const SupportedModelEntrySchema = z
|
|
34
72
|
.object({
|
|
35
73
|
provider: z.string().min(1),
|
|
36
74
|
model: z.string().min(1),
|
|
37
75
|
verified_at: z.string().min(1),
|
|
38
76
|
benchmark_evidence_refs: z.array(z.string().min(1)).min(1),
|
|
77
|
+
// Role-restricted certification (INV-MODEL-1 role-aware design §2.2).
|
|
78
|
+
// ABSENT = grandfathered full-route allowance: the entry predates the role
|
|
79
|
+
// dimension and keeps its flat-registry permissions (an owner
|
|
80
|
+
// backward-compatibility decision, NOT a stage-traversal evidence claim).
|
|
81
|
+
// PRESENT = evidence-contracted certification: the model is valid ONLY at
|
|
82
|
+
// dispatches whose required role is listed. Values are further restricted
|
|
83
|
+
// to CONTRACTED_ROLES at load (see assertContractedRoles).
|
|
84
|
+
roles: z.array(z.enum(SUPPORTED_MODEL_ROLES)).min(1).optional(),
|
|
39
85
|
notes: z.string().optional(),
|
|
86
|
+
// Active (provider, model) context window in tokens — the SSOT the
|
|
87
|
+
// reconstruct projection-budget helper (deriveDocumentExcerptProjectionBudget)
|
|
88
|
+
// reads to scale a single text document's seed-stage excerpt to the model.
|
|
89
|
+
// Optional: an entry without it falls back to the static projection FLOOR
|
|
90
|
+
// (model-agnostic, no regression). INV-MODEL-1; the window field is
|
|
91
|
+
// G4-protected (a change requires an INVARIANT-CHANGE: INV-MODEL-1 marker).
|
|
92
|
+
context_window_tokens: z.number().int().positive().optional(),
|
|
93
|
+
// Citation for context_window_tokens, kept SEPARATE from
|
|
94
|
+
// benchmark_evidence_refs: a context window is a published model spec, not a
|
|
95
|
+
// benchmark result. Required whenever context_window_tokens is present so a
|
|
96
|
+
// window value is never unsourced (C7).
|
|
97
|
+
context_window_provenance: z.string().min(1).optional(),
|
|
98
|
+
// Provider-published maximum output ceiling. Reconstruct direct-API
|
|
99
|
+
// headroom preflight consumes this value before any provider call.
|
|
100
|
+
// INV-MODEL-1 / G4 protected, independently provenance-backed.
|
|
101
|
+
max_output_tokens: z.number().int().positive().safe().optional(),
|
|
102
|
+
max_output_tokens_provenance: z.string().min(1).optional(),
|
|
103
|
+
})
|
|
104
|
+
.strict()
|
|
105
|
+
.refine((entry) => entry.context_window_tokens === undefined ||
|
|
106
|
+
entry.context_window_provenance !== undefined, {
|
|
107
|
+
message: "context_window_tokens requires context_window_provenance (a window value must cite its source)",
|
|
108
|
+
path: ["context_window_provenance"],
|
|
40
109
|
})
|
|
41
|
-
.
|
|
110
|
+
.refine((entry) => entry.max_output_tokens === undefined ||
|
|
111
|
+
entry.max_output_tokens_provenance !== undefined, {
|
|
112
|
+
message: "max_output_tokens requires max_output_tokens_provenance (an output limit must cite its source)",
|
|
113
|
+
path: ["max_output_tokens_provenance"],
|
|
114
|
+
});
|
|
42
115
|
const SupportedModelRegistrySchema = z
|
|
43
116
|
.object({
|
|
44
117
|
schema_version: z.string().min(1),
|
|
45
118
|
supported_models: z.array(SupportedModelEntrySchema),
|
|
46
119
|
})
|
|
47
|
-
.strict()
|
|
120
|
+
.strict()
|
|
121
|
+
.superRefine((registry, ctx) => {
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
registry.supported_models.forEach((entry, index) => {
|
|
124
|
+
const key = `${entry.provider}\u0000${entry.model}`;
|
|
125
|
+
if (seen.has(key)) {
|
|
126
|
+
ctx.addIssue({
|
|
127
|
+
code: "custom",
|
|
128
|
+
path: ["supported_models", index],
|
|
129
|
+
message: `duplicate supported model pair: ${entry.provider}/${entry.model}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
seen.add(key);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
export const RECONSTRUCT_SEMANTIC_MAP_SYNTHESIZE_LLM_ROUTE_PATH = "reconstruct.execution.actors.semantic_map_synthesize.llm";
|
|
136
|
+
export const RECONSTRUCT_DISPATCH_FALLBACK_LLM_ROUTE_PATH = "reconstruct.execution.dispatch_fallback.llm";
|
|
137
|
+
/**
|
|
138
|
+
* Single owner of dispatch → required-role derivation (design §2.3).
|
|
139
|
+
* Settings seats without a finer mapping require `author` — the strongest
|
|
140
|
+
* certification (golden full-pipeline completion) — so a role-restricted entry
|
|
141
|
+
* can never occupy an unmapped seat (fail-closed default).
|
|
142
|
+
*/
|
|
143
|
+
/** Bounded matcher for the review execution seats (review-role design §3):
|
|
144
|
+
* the three actor seats (nested and legacy settings forms — the same fixed
|
|
145
|
+
* actor names the settings schema accepts) and unit overrides restricted to
|
|
146
|
+
* the KNOWN unit-id vocabulary. The bound is load-bearing: G7 walks raw
|
|
147
|
+
* parsed YAML without the strict zod layer, so an unknown actor or unit key
|
|
148
|
+
* DOES reach the resolver — it must fall through to the fail-closed `author`
|
|
149
|
+
* default, never be adopted as a review seat. Salvage transcription
|
|
150
|
+
* (`...retry.salvage.transcription_llm`) deliberately does not match. */
|
|
151
|
+
const REVIEW_ACTOR_SEAT_PATH = /^review\.execution\.(?:actors\.)?(?:teamlead|lens|synthesize)\.llm$/;
|
|
152
|
+
const REVIEW_UNIT_SEAT_PATH = /^review\.execution\.units\.([^.[]+)\.llm$/;
|
|
153
|
+
function isReviewSeatPath(path) {
|
|
154
|
+
if (REVIEW_ACTOR_SEAT_PATH.test(path))
|
|
155
|
+
return true;
|
|
156
|
+
const unit = REVIEW_UNIT_SEAT_PATH.exec(path);
|
|
157
|
+
return unit !== null &&
|
|
158
|
+
REVIEW_EXECUTION_UNIT_IDS.includes(unit[1]);
|
|
159
|
+
}
|
|
160
|
+
export function requiredSupportedModelRoleForDispatch(dispatch) {
|
|
161
|
+
if (dispatch.kind === "request_judge")
|
|
162
|
+
return "answer_support_judge";
|
|
163
|
+
if (dispatch.kind === "semantic_map_synthesize")
|
|
164
|
+
return "semantic_map_synthesize";
|
|
165
|
+
if (dispatch.kind === "semantic_map_verify")
|
|
166
|
+
return "semantic_map_verify";
|
|
167
|
+
switch (dispatch.path) {
|
|
168
|
+
case "reconstruct.execution.actors.semantic_author.llm":
|
|
169
|
+
return "author";
|
|
170
|
+
case "reconstruct.execution.actors.confirmation_provider.llm":
|
|
171
|
+
return "confirmation_provider";
|
|
172
|
+
case RECONSTRUCT_SEMANTIC_MAP_SYNTHESIZE_LLM_ROUTE_PATH:
|
|
173
|
+
return "semantic_map_synthesize";
|
|
174
|
+
default:
|
|
175
|
+
if (isReviewSeatPath(dispatch.path))
|
|
176
|
+
return "review";
|
|
177
|
+
// Salvage transcription, top-level llm, unknown review actor/unit keys,
|
|
178
|
+
// and any future unmapped path: require the strongest certification
|
|
179
|
+
// (fail-closed).
|
|
180
|
+
return "author";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
48
183
|
/** Locates the authority registry shipped with the install by walking up from
|
|
49
184
|
* this module to the install root's `.onto/authority/` — the same resolver
|
|
50
185
|
* core-lens-registry.yaml uses. The authority is an install resource, not a
|
|
@@ -67,12 +202,12 @@ function findSupportedModelsAuthorityPath() {
|
|
|
67
202
|
"This authority file is the source of truth for selectable models and " +
|
|
68
203
|
"must ship with the onto installation.");
|
|
69
204
|
}
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const parsed = SupportedModelRegistrySchema.safeParse(
|
|
205
|
+
/** Shape-validates raw (already-parsed) registry data. The one validation path
|
|
206
|
+
* both {@link loadSupportedModelRegistry} (disk) and tests use, so the schema
|
|
207
|
+
* contract — strict shape, positive-int `context_window_tokens`, a window value
|
|
208
|
+
* only with its provenance — is exercised without touching the install file. */
|
|
209
|
+
export function parseSupportedModelRegistry(raw) {
|
|
210
|
+
const parsed = SupportedModelRegistrySchema.safeParse(raw);
|
|
76
211
|
if (!parsed.success) {
|
|
77
212
|
throw new Error(`Malformed supported-model registry at ${SUPPORTED_MODELS_AUTHORITY_PATH}: ` +
|
|
78
213
|
parsed.error.issues
|
|
@@ -80,8 +215,35 @@ export function loadSupportedModelRegistry() {
|
|
|
80
215
|
.join("; "));
|
|
81
216
|
}
|
|
82
217
|
assertRepoRelativeEvidenceRefs(parsed.data);
|
|
218
|
+
assertContractedRoles(parsed.data);
|
|
83
219
|
return parsed.data;
|
|
84
220
|
}
|
|
221
|
+
/** Fail-closed listable-role check: every role an entry LISTS must have a
|
|
222
|
+
* defined evidence contract ({@link CONTRACTED_ROLES}). The zod enum admits the
|
|
223
|
+
* full sealed vocabulary so role names never drift, but a certification without
|
|
224
|
+
* an evidence contract must not exist — reject at load, not at gate time. */
|
|
225
|
+
export function assertContractedRoles(registry) {
|
|
226
|
+
const bad = [];
|
|
227
|
+
for (const entry of registry.supported_models) {
|
|
228
|
+
for (const role of entry.roles ?? []) {
|
|
229
|
+
if (!CONTRACTED_ROLES.includes(role)) {
|
|
230
|
+
bad.push(`${entry.provider}/${entry.model}: ${role}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (bad.length > 0) {
|
|
235
|
+
throw new Error(`Malformed supported-model registry at ${SUPPORTED_MODELS_AUTHORITY_PATH}: ` +
|
|
236
|
+
"roles lists role(s) without a defined evidence contract (listable roles: " +
|
|
237
|
+
`${CONTRACTED_ROLES.join(", ")}):\n${bad.map((b) => ` - ${b}`).join("\n")}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Loads and shape-validates the supported-model registry from the install root.
|
|
241
|
+
* Strict: the authority always ships, so a missing or malformed file is an
|
|
242
|
+
* installation error, not a skip. */
|
|
243
|
+
export function loadSupportedModelRegistry() {
|
|
244
|
+
const filePath = findSupportedModelsAuthorityPath();
|
|
245
|
+
return parseSupportedModelRegistry(parseYaml(fs.readFileSync(filePath, "utf8")));
|
|
246
|
+
}
|
|
85
247
|
/** Validates that every `benchmark_evidence_refs` entry is a repo-relative path
|
|
86
248
|
* (not absolute, no `..` escape), so the cited evidence is an in-repo auditable
|
|
87
249
|
* artifact rather than an out-of-tree file. Pure string check — safe at runtime
|
|
@@ -147,13 +309,31 @@ export function collectModelSelections(settings) {
|
|
|
147
309
|
if (value === null || typeof value !== "object")
|
|
148
310
|
return;
|
|
149
311
|
const record = value;
|
|
312
|
+
if (trail === RECONSTRUCT_DISPATCH_FALLBACK_LLM_ROUTE_PATH) {
|
|
313
|
+
const provider = typeof record.provider === "string" ? record.provider : undefined;
|
|
314
|
+
const model = typeof record.model === "string" ? record.model : undefined;
|
|
315
|
+
for (const kind of ["semantic_map_synthesize", "semantic_map_verify"]) {
|
|
316
|
+
out.push({
|
|
317
|
+
provider,
|
|
318
|
+
model,
|
|
319
|
+
path: `${trail}#${kind}`,
|
|
320
|
+
requiredRole: requiredSupportedModelRoleForDispatch({ kind }),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
150
325
|
if (typeof record.model === "string" || typeof record.provider === "string") {
|
|
326
|
+
const path = trail || "(root)";
|
|
151
327
|
out.push({
|
|
152
328
|
provider: typeof record.provider === "string"
|
|
153
329
|
? record.provider
|
|
154
330
|
: undefined,
|
|
155
331
|
model: typeof record.model === "string" ? record.model : undefined,
|
|
156
|
-
path
|
|
332
|
+
path,
|
|
333
|
+
requiredRole: requiredSupportedModelRoleForDispatch({
|
|
334
|
+
kind: "settings_path",
|
|
335
|
+
path,
|
|
336
|
+
}),
|
|
157
337
|
});
|
|
158
338
|
}
|
|
159
339
|
for (const [key, child] of Object.entries(record)) {
|
|
@@ -163,37 +343,110 @@ export function collectModelSelections(settings) {
|
|
|
163
343
|
visit(settings, "");
|
|
164
344
|
return out;
|
|
165
345
|
}
|
|
346
|
+
/** Canonical settings-to-dispatch collector consumed by runtime and G7. */
|
|
347
|
+
export const collectSupportedModelDispatches = collectModelSelections;
|
|
348
|
+
/** Does `entry` cover `role`? Absent `roles` = grandfathered full-route
|
|
349
|
+
* allowance (covers every role — flat-registry backward compatibility);
|
|
350
|
+
* present = covers exactly the listed roles. */
|
|
351
|
+
function entryCoversRole(entry, role) {
|
|
352
|
+
return entry.roles === undefined || entry.roles.includes(role);
|
|
353
|
+
}
|
|
354
|
+
function supportedModelEntryFor(registry, provider, model) {
|
|
355
|
+
return registry.supported_models.find((entry) => entry.provider === provider && entry.model === model);
|
|
356
|
+
}
|
|
357
|
+
export function supportedModelMaxOutputTokens(registry, provider, model) {
|
|
358
|
+
return supportedModelEntryFor(registry, provider, model)?.max_output_tokens;
|
|
359
|
+
}
|
|
360
|
+
function isAllowedUnregisteredBenchCandidate(route, registry, options) {
|
|
361
|
+
if (route.provider === undefined || route.model === undefined)
|
|
362
|
+
return false;
|
|
363
|
+
if (supportedModelEntryFor(registry, route.provider, route.model) !== undefined) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
return (options?.benchCandidates ?? []).some((candidate) => candidate.provider === route.provider &&
|
|
367
|
+
candidate.model === route.model &&
|
|
368
|
+
candidate.allowedRoutePaths.includes(route.path));
|
|
369
|
+
}
|
|
166
370
|
/** Non-throwing membership check: is (provider, model) a benchmark-verified
|
|
167
|
-
* supported route? Reuses the same
|
|
371
|
+
* supported route FOR THIS DISPATCH? Reuses the same registry as
|
|
168
372
|
* {@link assertSupportedModelRoutes}, but returns a boolean so opt-in callers
|
|
169
373
|
* (e.g. the answer-support judge per-stage model override) can DEGRADE to the
|
|
170
374
|
* inherited config when an override is unsupported, instead of failing the run.
|
|
171
|
-
*
|
|
172
|
-
|
|
375
|
+
* The dispatch parameter is REQUIRED (no default) so every caller names its
|
|
376
|
+
* dispatch and a role-restricted entry can never be adopted at a dispatch it
|
|
377
|
+
* is not certified for (F6-b leak closure). An unresolved provider or model is
|
|
378
|
+
* not verified. */
|
|
379
|
+
export function isSupportedModelRoute(provider, model, registry, dispatch) {
|
|
173
380
|
if (provider === undefined || model === undefined)
|
|
174
381
|
return false;
|
|
175
|
-
|
|
382
|
+
const role = requiredSupportedModelRoleForDispatch(dispatch);
|
|
383
|
+
const entry = supportedModelEntryFor(registry, provider, model);
|
|
384
|
+
return entry !== undefined && entryCoversRole(entry, role);
|
|
176
385
|
}
|
|
177
386
|
/** Throws if any effective route is not a benchmark-verified (provider, model)
|
|
178
|
-
* pair
|
|
179
|
-
* rejected (fail-loud) rather than
|
|
180
|
-
*
|
|
181
|
-
* cannot verify.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
387
|
+
* pair CERTIFIED for the route's required role. A route whose effective
|
|
388
|
+
* provider OR model could not be resolved is rejected (fail-loud) rather than
|
|
389
|
+
* leniently accepted — the route must resolve to a verified pair, otherwise
|
|
390
|
+
* the runtime would dispatch a route the gate cannot verify. A pair that is
|
|
391
|
+
* registered but role-restricted (entry.roles present) is rejected at any
|
|
392
|
+
* route whose requiredRole it does not list. */
|
|
393
|
+
export function assertSupportedModelRoutes(routes, registry, options) {
|
|
394
|
+
const violations = routes.filter((route) => {
|
|
395
|
+
if (route.provider === undefined || route.model === undefined)
|
|
396
|
+
return true;
|
|
397
|
+
const entry = supportedModelEntryFor(registry, route.provider, route.model);
|
|
398
|
+
if (entry !== undefined)
|
|
399
|
+
return !entryCoversRole(entry, route.requiredRole);
|
|
400
|
+
return !isAllowedUnregisteredBenchCandidate(route, registry, options);
|
|
401
|
+
});
|
|
187
402
|
if (violations.length === 0)
|
|
188
403
|
return;
|
|
189
404
|
const detail = violations
|
|
190
|
-
.map((route) =>
|
|
405
|
+
.map((route) => {
|
|
406
|
+
const pair = `${route.provider ? `${route.provider}/` : "(unresolved provider)/"}${route.model ?? "(unresolved model)"}`;
|
|
407
|
+
const entry = route.provider !== undefined && route.model !== undefined
|
|
408
|
+
? supportedModelEntryFor(registry, route.provider, route.model)
|
|
409
|
+
: undefined;
|
|
410
|
+
const reason = entry?.roles
|
|
411
|
+
? ` — certified for [${entry.roles.join(", ")}], seat requires ${route.requiredRole}`
|
|
412
|
+
: "";
|
|
413
|
+
return `- ${route.path}: ${pair}${reason}`;
|
|
414
|
+
})
|
|
191
415
|
.join("\n");
|
|
192
416
|
const allowed = registry.supported_models
|
|
193
|
-
.map((entry) => `${entry.provider}/${entry.model}`)
|
|
417
|
+
.map((entry) => `${entry.provider}/${entry.model}${entry.roles ? ` (roles: ${entry.roles.join(", ")})` : ""}`)
|
|
194
418
|
.join(", ");
|
|
195
419
|
throw new Error("settings.json selects model route(s) not verified as supported by benchmark " +
|
|
196
420
|
`(see ${SUPPORTED_MODELS_AUTHORITY_PATH}):\n${detail}\n` +
|
|
197
|
-
`Benchmark-verified selectable models: ${allowed}. Add a model
|
|
198
|
-
"
|
|
421
|
+
`Benchmark-verified selectable models: ${allowed}. Add a model (or role) ` +
|
|
422
|
+
"only with the evidence its contract requires (see the registry header).");
|
|
423
|
+
}
|
|
424
|
+
export function assertB4BenchCandidateDispatchAllowed(args) {
|
|
425
|
+
const registry = args.registry ?? loadSupportedModelRegistry();
|
|
426
|
+
const route = {
|
|
427
|
+
provider: args.provider,
|
|
428
|
+
model: args.model,
|
|
429
|
+
path: RECONSTRUCT_SEMANTIC_MAP_SYNTHESIZE_LLM_ROUTE_PATH,
|
|
430
|
+
requiredRole: requiredSupportedModelRoleForDispatch({
|
|
431
|
+
kind: "settings_path",
|
|
432
|
+
path: RECONSTRUCT_SEMANTIC_MAP_SYNTHESIZE_LLM_ROUTE_PATH,
|
|
433
|
+
}),
|
|
434
|
+
};
|
|
435
|
+
try {
|
|
436
|
+
assertSupportedModelRoutes([route], registry);
|
|
437
|
+
return { allowance: "registered_supported", route };
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (supportedModelEntryFor(registry, args.provider, args.model) !== undefined) {
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
assertSupportedModelRoutes([route], registry, {
|
|
445
|
+
benchCandidates: [{
|
|
446
|
+
provider: args.provider,
|
|
447
|
+
model: args.model,
|
|
448
|
+
allowedRoutePaths: [RECONSTRUCT_SEMANTIC_MAP_SYNTHESIZE_LLM_ROUTE_PATH],
|
|
449
|
+
}],
|
|
450
|
+
});
|
|
451
|
+
return { allowance: "bench_candidate", route };
|
|
199
452
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S7 — `synthesize-cert/v1` record assembly for the B4 bench (design
|
|
3
|
+
* 20260706-b4-r8-harness-design v3 §9/§15.7).
|
|
4
|
+
*
|
|
5
|
+
* Pure projection from the run's real lineage (sampled entries → frozen
|
|
6
|
+
* packets → judgement rows) into the frozen record contract. The declared
|
|
7
|
+
* aggregates are filled by CALLING the validator module's own
|
|
8
|
+
* `computeSynthesizeCertAggregates` (§6.3 parser-ownership: the
|
|
9
|
+
* declared-vs-recomputed comparison then only fails on post-hoc tampering),
|
|
10
|
+
* and the negative_arm block comes from the mutation module's single source —
|
|
11
|
+
* the record can never cite a transform the harness did not run.
|
|
12
|
+
*
|
|
13
|
+
* The assembler shape-validates its own output (schema parse, fail-loud) but
|
|
14
|
+
* does NOT run the semantic recompute: publishing is the orchestrator's gate —
|
|
15
|
+
* `validateSynthesizeCertRecord(record) === []` AND the capsule binding gate
|
|
16
|
+
* (synthesize-cert-capsule.ts) must both pass before anything is persisted as
|
|
17
|
+
* evidence (§9-4). `created_at` is caller-supplied (the harness core stays
|
|
18
|
+
* deterministic and replayable).
|
|
19
|
+
*/
|
|
20
|
+
import { buildInputCorruptionV1NegativeArm } from "./synthesize-cert-mutation.js";
|
|
21
|
+
import { computeSynthesizeCertAggregates, parseSynthesizeCertRecord, SYNTHESIZE_CERT_ARMS, SYNTHESIZE_CERT_CONTRACT, SynthesizeCertDispatchConfigSchema, } from "./synthesize-cert-record.js";
|
|
22
|
+
/**
|
|
23
|
+
* record.input_manifest from the sampled entries + their frozen packets.
|
|
24
|
+
* The packet is the sha AUTHORITY (§4 input_sha256 exists only post-freeze);
|
|
25
|
+
* every lineage disagreement (missing packet, fixture or facts drift) throws.
|
|
26
|
+
*/
|
|
27
|
+
export function buildSynthesizeCertInputManifest(entries, packets) {
|
|
28
|
+
const packetByInputId = new Map(packets.map((p) => [p.input_id, p]));
|
|
29
|
+
return entries.map((entry) => {
|
|
30
|
+
const packet = packetByInputId.get(entry.input_id);
|
|
31
|
+
if (!packet) {
|
|
32
|
+
throw new Error(`synthesize-cert-assemble: sampled input ${entry.input_id} has no frozen packet`);
|
|
33
|
+
}
|
|
34
|
+
if (packet.fixture_id !== entry.fixture_id ||
|
|
35
|
+
packet.deterministic_facts_sha256 !== entry.deterministic_facts_sha256) {
|
|
36
|
+
throw new Error(`synthesize-cert-assemble: packet lineage for ${entry.input_id} disagrees with its sampled entry (fixture or deterministic facts drift)`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
fixture_id: entry.fixture_id,
|
|
40
|
+
input_id: entry.input_id,
|
|
41
|
+
input_sha256: packet.input_sha256,
|
|
42
|
+
stratum: { seam: entry.stratum.seam, merge: entry.stratum.merge },
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pure capture→arm_dispatch projection (effort-witness design §4.5.1-8).
|
|
48
|
+
* Groups the run's capture lines (parsed live-calls.jsonl objects) by the
|
|
49
|
+
* three record arm roles — reference/judge lines are not record arms and are
|
|
50
|
+
* ignored. Line semantics (design §4.5.1-10): a line WITHOUT a `dispatch` key
|
|
51
|
+
* is a pre-field legacy line = NO EVIDENCE, never normalized to "dispatched
|
|
52
|
+
* with no knobs" (the sonnet-5 20260708 legacy lines were in fact effort-low
|
|
53
|
+
* dispatches — that normalization would fabricate a false witness). A line
|
|
54
|
+
* with `dispatch: {}` IS the witness "no knob on the dispatched config".
|
|
55
|
+
* All-legacy → { legacy: true }; mixed legacy/new, an empty arm, a malformed
|
|
56
|
+
* dispatch shape, or within-arm inconsistency → violations (fail-loud).
|
|
57
|
+
* LIMIT: B4 arms are single-call `synthesizeSemanticMapNode` seats today; if
|
|
58
|
+
* a future arm adds base-effort verify calls, per-arm consistency must gain a
|
|
59
|
+
* call-kind axis or it will false-throw on a legitimate mix (design §4.5.1-11).
|
|
60
|
+
*/
|
|
61
|
+
export function projectSynthesizeCertArmDispatch(captureLines) {
|
|
62
|
+
const byArm = new Map(SYNTHESIZE_CERT_ARMS.map((arm) => [arm, []]));
|
|
63
|
+
for (const raw of captureLines) {
|
|
64
|
+
if (typeof raw !== "object" || raw === null)
|
|
65
|
+
continue;
|
|
66
|
+
const line = raw;
|
|
67
|
+
const role = line.role;
|
|
68
|
+
if (typeof role !== "string" || !byArm.has(role))
|
|
69
|
+
continue;
|
|
70
|
+
byArm.get(role).push({
|
|
71
|
+
seq: line.seq,
|
|
72
|
+
dispatch: line.dispatch,
|
|
73
|
+
hasKey: Object.hasOwn(line, "dispatch"),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const armLineTotal = [...byArm.values()].reduce((n, lines) => n + lines.length, 0);
|
|
77
|
+
const legacyTotal = [...byArm.values()].reduce((n, lines) => n + lines.filter((l) => !l.hasKey).length, 0);
|
|
78
|
+
if (armLineTotal > 0 && legacyTotal === armLineTotal) {
|
|
79
|
+
return { armDispatch: null, legacy: true, violations: [] };
|
|
80
|
+
}
|
|
81
|
+
const violations = [];
|
|
82
|
+
const armDispatch = {};
|
|
83
|
+
for (const arm of SYNTHESIZE_CERT_ARMS) {
|
|
84
|
+
const lines = byArm.get(arm);
|
|
85
|
+
if (lines.length === 0) {
|
|
86
|
+
violations.push(`arm ${arm}: no captured calls — a declared dispatch without witness is not certifiable`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let witnessed = null;
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
if (!line.hasKey) {
|
|
92
|
+
violations.push(`arm ${arm}: capture line seq=${String(line.seq)} predates the dispatch witness (mixed legacy/new capture — no evidence line in a witnessing run)`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const parsed = SynthesizeCertDispatchConfigSchema.safeParse(line.dispatch);
|
|
96
|
+
if (!parsed.success) {
|
|
97
|
+
violations.push(`arm ${arm}: capture line seq=${String(line.seq)} carries a malformed dispatch witness: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (witnessed === null) {
|
|
101
|
+
witnessed = parsed.data;
|
|
102
|
+
}
|
|
103
|
+
else if (witnessed.reasoning_effort !== parsed.data.reasoning_effort ||
|
|
104
|
+
witnessed.thinking_mode !== parsed.data.thinking_mode) {
|
|
105
|
+
violations.push(`arm ${arm}: inconsistent dispatch within one arm (${JSON.stringify(witnessed)} vs ${JSON.stringify(parsed.data)} at seq=${String(line.seq)})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (witnessed !== null)
|
|
109
|
+
armDispatch[arm] = witnessed;
|
|
110
|
+
}
|
|
111
|
+
if (violations.length > 0)
|
|
112
|
+
return { armDispatch: null, legacy: false, violations };
|
|
113
|
+
return {
|
|
114
|
+
armDispatch: armDispatch,
|
|
115
|
+
legacy: false,
|
|
116
|
+
violations: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Pure declared-vs-witnessed dispatch guard (effort-witness design §3 verify +
|
|
121
|
+
* §4.5.1-9). Compares the preflight DECLARATION against the capture WITNESS
|
|
122
|
+
* knob-by-knob per arm, plus two deterministic realization-boundary rules
|
|
123
|
+
* keyed on each arm's DECLARED provider brand:
|
|
124
|
+
* (a) an openai(codex-route) arm must witness a present `reasoning_effort` —
|
|
125
|
+
* a knobless codex dispatch inherits an unobservable host-TOML effort and
|
|
126
|
+
* is not certifiable as any effort;
|
|
127
|
+
* (b) no arm may witness `reasoning_effort` and `thinking_mode` together —
|
|
128
|
+
* the anthropic route ignores effort once thinking is disabled, so that
|
|
129
|
+
* certification claim would be unrealized.
|
|
130
|
+
* Returns violation strings; empty = the certified dispatch is the dispatched
|
|
131
|
+
* dispatch.
|
|
132
|
+
*/
|
|
133
|
+
export function synthesizeCertDispatchGuardViolations(args) {
|
|
134
|
+
const violations = [];
|
|
135
|
+
for (const arm of SYNTHESIZE_CERT_ARMS) {
|
|
136
|
+
const declared = args.declared[arm];
|
|
137
|
+
const witnessed = args.witnessed[arm];
|
|
138
|
+
if (declared.reasoning_effort !== witnessed.reasoning_effort) {
|
|
139
|
+
violations.push(`arm ${arm}: declared reasoning_effort=${declared.reasoning_effort ?? "(absent)"} but witnessed ${witnessed.reasoning_effort ?? "(absent)"}`);
|
|
140
|
+
}
|
|
141
|
+
if (declared.thinking_mode !== witnessed.thinking_mode) {
|
|
142
|
+
violations.push(`arm ${arm}: declared thinking_mode=${declared.thinking_mode ?? "(absent)"} but witnessed ${witnessed.thinking_mode ?? "(absent)"}`);
|
|
143
|
+
}
|
|
144
|
+
if (args.armProviders[arm] === "openai" && witnessed.reasoning_effort === undefined) {
|
|
145
|
+
violations.push(`arm ${arm}: openai(codex-route) arm witnessed NO reasoning_effort — the worker would inherit an unobservable host-TOML effort; declare and dispatch an explicit effort`);
|
|
146
|
+
}
|
|
147
|
+
if (witnessed.reasoning_effort !== undefined && witnessed.thinking_mode !== undefined) {
|
|
148
|
+
violations.push(`arm ${arm}: witnessed BOTH reasoning_effort and thinking_mode — the anthropic route ignores effort once thinking is disabled, so this certification claim would be unrealized`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return violations;
|
|
152
|
+
}
|
|
153
|
+
export function assembleSynthesizeCertRecord(args) {
|
|
154
|
+
const inputManifest = buildSynthesizeCertInputManifest(args.entries, args.packets);
|
|
155
|
+
const judgementRows = [...args.judgementRows];
|
|
156
|
+
const negative = buildInputCorruptionV1NegativeArm(args.mutationSeed);
|
|
157
|
+
const record = {
|
|
158
|
+
record_contract: SYNTHESIZE_CERT_CONTRACT,
|
|
159
|
+
created_at: args.createdAt,
|
|
160
|
+
provider: args.candidateModel.provider,
|
|
161
|
+
model: args.candidateModel.model,
|
|
162
|
+
declared_reps: args.declaredReps,
|
|
163
|
+
arm_prompt_sha256: {
|
|
164
|
+
baseline: args.promptSha256,
|
|
165
|
+
candidate: args.promptSha256,
|
|
166
|
+
negative_control: args.promptSha256,
|
|
167
|
+
},
|
|
168
|
+
arm_model: {
|
|
169
|
+
baseline: { provider: args.baselineModel.provider, model: args.baselineModel.model },
|
|
170
|
+
candidate: { provider: args.candidateModel.provider, model: args.candidateModel.model },
|
|
171
|
+
negative_control: {
|
|
172
|
+
provider: args.candidateModel.provider,
|
|
173
|
+
model: args.candidateModel.model,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
...(args.armDispatch !== undefined ? { arm_dispatch: args.armDispatch } : {}),
|
|
177
|
+
negative_arm: negative,
|
|
178
|
+
input_manifest: inputManifest,
|
|
179
|
+
judgement_rows: judgementRows,
|
|
180
|
+
declared_aggregates: computeSynthesizeCertAggregates({
|
|
181
|
+
inputManifest,
|
|
182
|
+
judgementRows,
|
|
183
|
+
}),
|
|
184
|
+
reproduction: {
|
|
185
|
+
command: args.reproduction.command,
|
|
186
|
+
source_paths: [...args.reproduction.source_paths],
|
|
187
|
+
limitations: args.reproduction.limitations,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
const parsed = parseSynthesizeCertRecord(record);
|
|
191
|
+
if (!parsed.record) {
|
|
192
|
+
throw new Error(`synthesize-cert-assemble: assembled record failed the contract schema — ${parsed.violations
|
|
193
|
+
.map((v) => v.message)
|
|
194
|
+
.join("; ")}`);
|
|
195
|
+
}
|
|
196
|
+
return parsed.record;
|
|
197
|
+
}
|