@primust/verifier 1.0.0 → 1.0.1

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 (48) hide show
  1. package/dist/chunk-LTWQK3HT.js +432 -0
  2. package/dist/chunk-ZADQUKKN.js +2963 -0
  3. package/dist/cli.d.ts +3 -2
  4. package/dist/cli.js +309 -361
  5. package/dist/index.d.ts +335 -13
  6. package/dist/index.js +1181 -13
  7. package/dist/v29-envelope-GFVVA2S6.js +42 -0
  8. package/package.json +7 -8
  9. package/dist/bounded-trace.d.ts +0 -46
  10. package/dist/bounded-trace.d.ts.map +0 -1
  11. package/dist/bounded-trace.js +0 -558
  12. package/dist/bounded-trace.js.map +0 -1
  13. package/dist/cli.d.ts.map +0 -1
  14. package/dist/cli.js.map +0 -1
  15. package/dist/index.d.ts.map +0 -1
  16. package/dist/index.js.map +0 -1
  17. package/dist/key-cache.d.ts +0 -20
  18. package/dist/key-cache.d.ts.map +0 -1
  19. package/dist/key-cache.js +0 -68
  20. package/dist/key-cache.js.map +0 -1
  21. package/dist/scoped.d.ts +0 -35
  22. package/dist/scoped.d.ts.map +0 -1
  23. package/dist/scoped.js +0 -582
  24. package/dist/scoped.js.map +0 -1
  25. package/dist/types.d.ts +0 -60
  26. package/dist/types.d.ts.map +0 -1
  27. package/dist/types.js +0 -5
  28. package/dist/types.js.map +0 -1
  29. package/dist/upstream_resolver.d.ts +0 -60
  30. package/dist/upstream_resolver.d.ts.map +0 -1
  31. package/dist/upstream_resolver.js +0 -126
  32. package/dist/upstream_resolver.js.map +0 -1
  33. package/dist/v29-envelope.d.ts +0 -55
  34. package/dist/v29-envelope.d.ts.map +0 -1
  35. package/dist/v29-envelope.js +0 -450
  36. package/dist/v29-envelope.js.map +0 -1
  37. package/dist/verifier.d.ts +0 -36
  38. package/dist/verifier.d.ts.map +0 -1
  39. package/dist/verifier.js +0 -1235
  40. package/dist/verifier.js.map +0 -1
  41. package/dist/verifier.test.d.ts +0 -2
  42. package/dist/verifier.test.d.ts.map +0 -1
  43. package/dist/verifier.test.js +0 -395
  44. package/dist/verifier.test.js.map +0 -1
  45. package/dist/verify-html-template.d.ts +0 -45
  46. package/dist/verify-html-template.d.ts.map +0 -1
  47. package/dist/verify-html-template.js +0 -182
  48. package/dist/verify-html-template.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,13 +1,1181 @@
1
- export { verify } from './verifier';
2
- export { generateVerifyHtml } from './verify-html-template';
3
- export { seedKeyCache } from './key-cache';
4
- export { REASONS, buildMerkleRoot, canonicalJson, canonicalJsonString, canonicalNumber, verifyBoundedTrace, } from './bounded-trace';
5
- export { SCOPED_REASONS, verifyScopedCertificates, } from './scoped';
6
- // Cross-org upstream-VPEC resolver (Follow-7)
7
- // UpstreamRootResolver type is canonical from ./types (Follow-6); we
8
- // only re-export the resolver factory + its options here.
9
- export { createUpstreamRootResolver, closeResolver, } from './upstream_resolver';
10
- // v29 Layer 1 — ProofCheck envelope + manifest + runtime-binding
11
- // conformance. Mirror of packages/verifier-py/.../v29_envelope.py.
12
- export { ENVELOPE_VERSION, ALLOWED_KINDS, ALLOWED_TRUST_EDGES, PROOF_TIER_HIERARCHY, canonicalJson as canonicalJsonV29, canonicalHash as canonicalHashV29, validateEnvelopeShape, reproduceAggregations, validateAggregations, reproduceTierCeiling, validateRecordsAgainstHarness, validateRuntimeBindingHash, reproduceManifestCanonicalHash, validateManifestCanonicalHash, verifyV29, } from './v29-envelope';
13
- //# sourceMappingURL=index.js.map
1
+ import {
2
+ closeResolver,
3
+ createUpstreamRootResolver,
4
+ seedKeyCache,
5
+ verify
6
+ } from "./chunk-ZADQUKKN.js";
7
+ import {
8
+ ALLOWED_KINDS,
9
+ ALLOWED_TRUST_EDGES,
10
+ ENVELOPE_VERSION,
11
+ PROOF_TIER_HIERARCHY,
12
+ canonicalHash,
13
+ canonicalJson,
14
+ reproduceAggregations,
15
+ reproduceManifestCanonicalHash,
16
+ reproduceTierCeiling,
17
+ validateAggregations,
18
+ validateEnvelopeShape,
19
+ validateManifestCanonicalHash,
20
+ validateRecordsAgainstHarness,
21
+ validateRuntimeBindingHash,
22
+ verifyV29
23
+ } from "./chunk-LTWQK3HT.js";
24
+
25
+ // src/verify-html-template.ts
26
+ function generateVerifyHtml(options) {
27
+ const { publicKeyB64, packData } = options;
28
+ return `<!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>Primust Evidence Pack \u2014 Offline Verification</title>
34
+ <style>
35
+ * { box-sizing: border-box; margin: 0; padding: 0; }
36
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #fafafa; color: #1a1a1a; padding: 2rem; max-width: 900px; margin: 0 auto; }
37
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
38
+ .subtitle { color: #666; font-size: 0.9rem; margin-bottom: 2rem; }
39
+ .status { padding: 1rem; border-radius: 8px; margin-bottom: 1.5rem; font-weight: 600; font-size: 1.1rem; }
40
+ .status.verified { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
41
+ .status.failed { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
42
+ .status.loading { background: #e2e3e5; color: #383d41; border: 1px solid #d6d8db; }
43
+ .summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
44
+ .stat { background: white; border: 1px solid #e0e0e0; border-radius: 8px; padding: 1rem; }
45
+ .stat-label { font-size: 0.75rem; color: #888; text-transform: uppercase; }
46
+ .stat-value { font-size: 1.5rem; font-weight: 700; margin-top: 0.25rem; }
47
+ .vpec-table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0; }
48
+ .vpec-table th, .vpec-table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #f0f0f0; font-size: 0.85rem; }
49
+ .vpec-table th { background: #f8f9fa; font-weight: 600; font-size: 0.75rem; text-transform: uppercase; color: #666; }
50
+ .badge { display: inline-block; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
51
+ .badge-pass { background: #d4edda; color: #155724; }
52
+ .badge-fail { background: #f8d7da; color: #721c24; }
53
+ .badge-pending { background: #fff3cd; color: #856404; }
54
+ .chain { margin-top: 2rem; }
55
+ .chain-node { border: 1px solid #e0e0e0; border-radius: 8px; padding: 1rem; margin-bottom: 0; background: white; }
56
+ .chain-node.current { border-color: #007bff; background: #f0f7ff; }
57
+ .chain-arrow { text-align: center; padding: 0.25rem 0; color: #ccc; font-size: 1.2rem; }
58
+ .footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e0e0e0; font-size: 0.8rem; color: #888; }
59
+ code { font-family: 'SF Mono', Monaco, monospace; font-size: 0.8rem; background: #f4f4f4; padding: 0.1rem 0.3rem; border-radius: 3px; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <h1>Primust Evidence Pack Verification</h1>
64
+ <div class="subtitle">Pack: ${packData.pack_id} &middot; Period: ${packData.period_start} to ${packData.period_end}</div>
65
+
66
+ <div id="status" class="status loading">Verifying...</div>
67
+
68
+ <div class="summary">
69
+ <div class="stat">
70
+ <div class="stat-label">VPECs</div>
71
+ <div class="stat-value">${packData.vpec_count}</div>
72
+ </div>
73
+ <div class="stat">
74
+ <div class="stat-label">Mathematical</div>
75
+ <div class="stat-value">${packData.provable_surface_summary.mathematical}</div>
76
+ </div>
77
+ <div class="stat">
78
+ <div class="stat-label">Execution</div>
79
+ <div class="stat-value">${packData.provable_surface_summary.execution}</div>
80
+ </div>
81
+ <div class="stat">
82
+ <div class="stat-label">Witnessed</div>
83
+ <div class="stat-value">${packData.provable_surface_summary.witnessed}</div>
84
+ </div>
85
+ </div>
86
+
87
+ <table class="vpec-table" id="vpec-table">
88
+ <thead>
89
+ <tr>
90
+ <th>Status</th>
91
+ <th>VPEC ID</th>
92
+ <th>Proof Level</th>
93
+ <th>Surface</th>
94
+ <th>Gaps</th>
95
+ <th>Issued</th>
96
+ </tr>
97
+ </thead>
98
+ <tbody id="vpec-tbody"></tbody>
99
+ </table>
100
+
101
+ <div class="chain" id="chain-container"></div>
102
+
103
+ <div class="footer">
104
+ This file verifies offline. Nothing is sent to any server.<br>
105
+ For CLI verification: <code>pip install primust-verify</code>
106
+ </div>
107
+
108
+ <script>
109
+ const PRIMUST_PUBLIC_KEY_B64 = "${publicKeyB64}";
110
+ const PACK_DATA = ${JSON.stringify(packData)};
111
+
112
+ async function importKey(b64) {
113
+ const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
114
+ return crypto.subtle.importKey('raw', raw, { name: 'Ed25519' }, false, ['verify']);
115
+ }
116
+
117
+ async function verifySignature(key, signatureHex, payloadStr) {
118
+ const sig = new Uint8Array(signatureHex.match(/.{2}/g).map(b => parseInt(b, 16)));
119
+ const data = new TextEncoder().encode(payloadStr);
120
+ return crypto.subtle.verify('Ed25519', key, sig, data);
121
+ }
122
+
123
+ async function main() {
124
+ const statusEl = document.getElementById('status');
125
+ const tbody = document.getElementById('vpec-tbody');
126
+ let allValid = true;
127
+
128
+ try {
129
+ const key = await importKey(PRIMUST_PUBLIC_KEY_B64);
130
+
131
+ for (const vpec of PACK_DATA.vpecs) {
132
+ let valid = false;
133
+ try {
134
+ valid = await verifySignature(key, vpec.signature, vpec.signed_payload);
135
+ } catch (e) {
136
+ valid = false;
137
+ }
138
+
139
+ if (!valid) allValid = false;
140
+
141
+ const row = document.createElement('tr');
142
+ row.innerHTML =
143
+ '<td><span class="badge ' + (valid ? 'badge-pass' : 'badge-fail') + '">' + (valid ? 'VERIFIED' : 'NOT VERIFIED') + '</span></td>' +
144
+ '<td><code>' + vpec.vpec_id + '</code></td>' +
145
+ '<td>' + vpec.proof_level_floor + '</td>' +
146
+ '<td>' + (vpec.provable_surface * 100).toFixed(0) + '%</td>' +
147
+ '<td>' + vpec.gap_count + '</td>' +
148
+ '<td>' + vpec.issued_at + '</td>';
149
+ tbody.appendChild(row);
150
+ }
151
+
152
+ // Render upstream chain if present
153
+ if (PACK_DATA.upstream_vpecs && PACK_DATA.upstream_vpecs.length > 0) {
154
+ const container = document.getElementById('chain-container');
155
+ container.innerHTML = '<h2 style="font-size:1.1rem;margin-bottom:1rem;">Cross-Organization Chain</h2>';
156
+
157
+ for (const uv of PACK_DATA.upstream_vpecs) {
158
+ let uvValid = false;
159
+ try {
160
+ uvValid = await verifySignature(key, uv.signature, uv.signed_payload);
161
+ } catch (e) { uvValid = false; }
162
+
163
+ const node = document.createElement('div');
164
+ node.className = 'chain-node';
165
+ node.innerHTML =
166
+ '<span class="badge ' + (uvValid ? 'badge-pass' : 'badge-fail') + '">' + (uvValid ? 'VERIFIED' : 'NOT VERIFIED') + '</span> ' +
167
+ '<strong>' + uv.org_id + '</strong> &mdash; <code>' + uv.vpec_id + '</code> (' + uv.proof_level_floor + ')';
168
+ container.appendChild(node);
169
+
170
+ const arrow = document.createElement('div');
171
+ arrow.className = 'chain-arrow';
172
+ arrow.textContent = '\\u25BC';
173
+ container.appendChild(arrow);
174
+ }
175
+
176
+ const currentNode = document.createElement('div');
177
+ currentNode.className = 'chain-node current';
178
+ currentNode.innerHTML = '<strong>This Pack</strong> &mdash; <code>' + PACK_DATA.pack_id + '</code>';
179
+ container.appendChild(currentNode);
180
+ }
181
+
182
+ statusEl.className = 'status ' + (allValid ? 'verified' : 'failed');
183
+ statusEl.textContent = allValid
184
+ ? '\\u2713 VERIFIED \u2014 All ' + PACK_DATA.vpecs.length + ' credentials verified'
185
+ : '\\u2717 NOT VERIFIED \u2014 Some credentials failed verification';
186
+ } catch (e) {
187
+ statusEl.className = 'status failed';
188
+ statusEl.textContent = 'Verification error: ' + e.message;
189
+ }
190
+ }
191
+
192
+ main();
193
+ </script>
194
+ </body>
195
+ </html>`;
196
+ }
197
+
198
+ // src/bounded-trace.ts
199
+ import { createHash } from "crypto";
200
+ function canonicalNumber(x) {
201
+ if (typeof x !== "number" || Number.isNaN(x) || !Number.isFinite(x)) {
202
+ throw new Error("canonical JSON cannot represent NaN, infinity, or non-numbers");
203
+ }
204
+ if (x === 0) return "0";
205
+ let s = x.toString();
206
+ const eIdx = s.indexOf("e");
207
+ if (eIdx >= 0) {
208
+ const mantissa = s.slice(0, eIdx);
209
+ const expPart = s.slice(eIdx + 1);
210
+ const sign = expPart.startsWith("-") ? "-" : "+";
211
+ const expDigits = expPart.replace(/^[+-]/, "").replace(/^0+(?=\d)/, "");
212
+ return `${mantissa}e${sign}${expDigits || "0"}`;
213
+ }
214
+ if (s.includes(".")) {
215
+ s = s.replace(/\.?0+$/, (m) => m.startsWith(".") ? "" : m);
216
+ }
217
+ if (s === "-0") s = "0";
218
+ return s;
219
+ }
220
+ function canonicalJson2(obj) {
221
+ const s = canonicalJsonString(obj);
222
+ return new TextEncoder().encode(s);
223
+ }
224
+ function canonicalJsonString(obj) {
225
+ if (obj === null) return "null";
226
+ if (typeof obj === "boolean") return obj ? "true" : "false";
227
+ if (typeof obj === "number") return canonicalNumber(obj);
228
+ if (typeof obj === "string") return JSON.stringify(obj);
229
+ if (Array.isArray(obj)) {
230
+ return "[" + obj.map((v) => canonicalJsonString(v)).join(",") + "]";
231
+ }
232
+ if (typeof obj === "object") {
233
+ const keys = Object.keys(obj).sort();
234
+ const parts = keys.map((k) => {
235
+ const v = obj[k];
236
+ return JSON.stringify(k) + ":" + canonicalJsonString(v);
237
+ });
238
+ return "{" + parts.join(",") + "}";
239
+ }
240
+ throw new Error(`canonical_json cannot encode value of type ${typeof obj}`);
241
+ }
242
+ function sha256(bytes) {
243
+ const h = createHash("sha256");
244
+ h.update(bytes);
245
+ return new Uint8Array(h.digest());
246
+ }
247
+ function leafHash(payload) {
248
+ const prefixed = new Uint8Array(payload.length + 1);
249
+ prefixed[0] = 0;
250
+ prefixed.set(payload, 1);
251
+ return sha256(prefixed);
252
+ }
253
+ function nodeHash(left, right) {
254
+ const prefixed = new Uint8Array(1 + left.length + right.length);
255
+ prefixed[0] = 1;
256
+ prefixed.set(left, 1);
257
+ prefixed.set(right, 1 + left.length);
258
+ return sha256(prefixed);
259
+ }
260
+ function toHex(bytes) {
261
+ let out = "";
262
+ for (const b of bytes) {
263
+ out += b.toString(16).padStart(2, "0");
264
+ }
265
+ return out;
266
+ }
267
+ function buildMerkleRoot(leaves) {
268
+ if (leaves.length === 0) {
269
+ throw new Error("cannot build Merkle root over empty leaf list");
270
+ }
271
+ let level = leaves.map((l) => leafHash(l));
272
+ while (level.length > 1) {
273
+ if (level.length % 2 === 1) {
274
+ level.push(level[level.length - 1]);
275
+ }
276
+ const next = [];
277
+ for (let i = 0; i < level.length; i += 2) {
278
+ next.push(nodeHash(level[i], level[i + 1]));
279
+ }
280
+ level = next;
281
+ }
282
+ return "sha256:" + toHex(level[0]);
283
+ }
284
+ var REASONS = [
285
+ "profile_not_found",
286
+ "profile_signature_invalid",
287
+ "profile_not_empirical",
288
+ "profile_expired",
289
+ "profile_revoked",
290
+ "profile_no_freshness_window",
291
+ "profile_trace_mismatch",
292
+ "runtime_not_supported",
293
+ "trace_schema_unknown",
294
+ "missing_merkle_root",
295
+ "merkle_inclusion_failed",
296
+ "threshold_violation",
297
+ "runtime_section_missing",
298
+ "retrieval_section_missing",
299
+ // v28.5 §4 — closed-API lifecycle downgrade codes. Mirrors the
300
+ // primust_verify (Python) 1.0.3 vocabulary so cross-verifier
301
+ // conformance holds. The TS verifier consults
302
+ // `model_profiles.lifecycle_state` (mig 123) BEFORE the legacy
303
+ // profile_class check; rows with non-empirical lifecycle states
304
+ // downgrade to `execution` with the listed reason regardless of class.
305
+ "closed_api_pre_cohort",
306
+ "closed_api_pre_promote",
307
+ "closed_api_v2_promoted",
308
+ "profile_deprecated",
309
+ "unknown_lifecycle_state"
310
+ ];
311
+ var KNOWN_LIFECYCLE_STATES = /* @__PURE__ */ new Set([
312
+ "v1_placeholder",
313
+ "v2_run_complete",
314
+ "v2_staged",
315
+ "cohort_validated",
316
+ "v2_empirical_closed_api",
317
+ "deprecated"
318
+ ]);
319
+ var LIFECYCLE_DOWNGRADE_REASON = {
320
+ v1_placeholder: "closed_api_pre_cohort",
321
+ v2_run_complete: "closed_api_pre_cohort",
322
+ v2_staged: "closed_api_pre_cohort",
323
+ cohort_validated: "closed_api_pre_promote",
324
+ deprecated: "profile_deprecated"
325
+ };
326
+ function asDate(x) {
327
+ if (!x) return null;
328
+ if (x instanceof Date) return x;
329
+ if (typeof x === "string") {
330
+ const d = new Date(x);
331
+ if (!isNaN(d.getTime())) return d;
332
+ }
333
+ return null;
334
+ }
335
+ function checkProfile(profile, now) {
336
+ if (!profile || Object.keys(profile).length === 0) {
337
+ return { ok: false, reason: "profile_not_found" };
338
+ }
339
+ const sig = String(profile.profile_signature ?? profile.signature ?? "");
340
+ if (sig.includes("PLACEHOLDER") || sig.includes("UNSIGNED_PENDING")) {
341
+ return { ok: false, reason: "profile_signature_invalid" };
342
+ }
343
+ const status = String(profile.status ?? "active").toLowerCase();
344
+ if (status === "revoked" || status === "suspended") {
345
+ return { ok: false, reason: "profile_revoked" };
346
+ }
347
+ const lifecycle = profile.lifecycle_state;
348
+ if (lifecycle !== null && lifecycle !== void 0) {
349
+ const ls = String(lifecycle).toLowerCase();
350
+ if (!KNOWN_LIFECYCLE_STATES.has(ls)) {
351
+ return { ok: false, reason: "unknown_lifecycle_state" };
352
+ }
353
+ const downgrade = LIFECYCLE_DOWNGRADE_REASON[ls];
354
+ if (downgrade !== void 0) {
355
+ return { ok: false, reason: downgrade };
356
+ }
357
+ }
358
+ const cls = String(profile.profile_class ?? "placeholder").toLowerCase();
359
+ if (cls === "revoked") return { ok: false, reason: "profile_revoked" };
360
+ if (cls === "synthetic" || cls === "placeholder") {
361
+ return { ok: false, reason: "profile_not_empirical" };
362
+ }
363
+ if (cls === "expired") return { ok: false, reason: "profile_expired" };
364
+ if (cls !== "empirical") return { ok: false, reason: "profile_not_empirical" };
365
+ const validFrom = asDate(profile.freshness_valid_from);
366
+ const validUntil = asDate(profile.freshness_valid_until);
367
+ if (!validFrom || !validUntil) {
368
+ return { ok: false, reason: "profile_no_freshness_window" };
369
+ }
370
+ const graceDays = Number(profile.freshness_grace_days) || 0;
371
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
372
+ if (now.getTime() > validUntil.getTime() + graceMs) {
373
+ return { ok: false, reason: "profile_expired" };
374
+ }
375
+ if (now.getTime() < validFrom.getTime()) {
376
+ return { ok: false, reason: "profile_expired" };
377
+ }
378
+ return { ok: true, reason: "ok" };
379
+ }
380
+ function asDict(v) {
381
+ if (v && typeof v === "object" && !Array.isArray(v)) {
382
+ return v;
383
+ }
384
+ if (typeof v === "string") {
385
+ try {
386
+ const parsed = JSON.parse(v);
387
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
388
+ return parsed;
389
+ }
390
+ } catch {
391
+ }
392
+ }
393
+ return {};
394
+ }
395
+ function checkRuntime(trace, profile) {
396
+ const runtime = asDict(trace.runtime);
397
+ const kernel = runtime.kernel_profile_id;
398
+ const gpu = runtime.gpu_class;
399
+ const supportedRaw = profile.supported_runtime_classes;
400
+ const supported = new Set(Array.isArray(supportedRaw) ? supportedRaw : []);
401
+ if (supported.size > 0) {
402
+ if (kernel && supported.has(kernel)) return { ok: true, reason: "ok" };
403
+ if (gpu && supported.has(gpu)) return { ok: true, reason: "ok" };
404
+ return { ok: false, reason: "runtime_not_supported" };
405
+ }
406
+ const profileData = asDict(profile.profile_data);
407
+ const calibrated = profileData.calibrated_gpu_classes;
408
+ if (calibrated && typeof calibrated === "object") {
409
+ const keys = Array.isArray(calibrated) ? calibrated : Object.keys(calibrated);
410
+ if (gpu && keys.includes(gpu)) return { ok: true, reason: "ok" };
411
+ if (kernel && keys.includes(kernel)) return { ok: true, reason: "ok" };
412
+ }
413
+ return { ok: false, reason: "runtime_not_supported" };
414
+ }
415
+ function recomputeMerkleRoot(operators) {
416
+ const leaves = operators.map(
417
+ (op) => canonicalJson2({ v: "bounded_trace_v1", op })
418
+ );
419
+ return buildMerkleRoot(leaves);
420
+ }
421
+ function compareThresholds(trace, profile) {
422
+ const runtime = asDict(trace.runtime);
423
+ const gpu = runtime.gpu_class;
424
+ const profileData = asDict(profile.profile_data);
425
+ let opThresholds = {};
426
+ const calibrated = profileData.calibrated_gpu_classes;
427
+ if (calibrated && typeof calibrated === "object" && !Array.isArray(calibrated) && gpu && gpu in calibrated) {
428
+ const gpuBlock = calibrated[gpu];
429
+ if (gpuBlock && typeof gpuBlock === "object") {
430
+ const ops = gpuBlock.operators;
431
+ if (ops && typeof ops === "object" && !Array.isArray(ops)) {
432
+ opThresholds = ops;
433
+ }
434
+ }
435
+ }
436
+ if (Object.keys(opThresholds).length === 0) {
437
+ const legacyOps = profileData.operators;
438
+ if (legacyOps && typeof legacyOps === "object" && !Array.isArray(legacyOps)) {
439
+ opThresholds = legacyOps;
440
+ }
441
+ }
442
+ if (Object.keys(opThresholds).length === 0) {
443
+ return {
444
+ ok: false,
445
+ violations: [{ reason: "no_operator_thresholds_in_profile" }]
446
+ };
447
+ }
448
+ let marginFactor = 1;
449
+ const safetyMargin = profile.safety_margin;
450
+ if (typeof safetyMargin === "number" && !Number.isNaN(safetyMargin)) {
451
+ marginFactor = 1 + safetyMargin;
452
+ }
453
+ const absOrViolation = (raw) => {
454
+ if (raw === null || raw === void 0) return null;
455
+ if (typeof raw !== "number") return null;
456
+ if (Number.isNaN(raw) || !Number.isFinite(raw)) return null;
457
+ return Math.abs(raw);
458
+ };
459
+ const violations = [];
460
+ const tops = Array.isArray(trace.operators) ? trace.operators : [];
461
+ for (const rawOp of tops) {
462
+ const op = asDict(rawOp);
463
+ const opType = op.operator_type;
464
+ const thresholds = opType && typeof opType === "string" ? opThresholds[opType] : void 0;
465
+ if (!thresholds) {
466
+ violations.push({
467
+ operator_index: op.operator_index,
468
+ operator_type: opType,
469
+ stat: "operator_type",
470
+ observed: opType,
471
+ allowed: Object.keys(opThresholds)
472
+ });
473
+ continue;
474
+ }
475
+ const stats = asDict(op.stats);
476
+ const p99 = thresholds.p99;
477
+ const p99_99 = thresholds.p99_99;
478
+ const rawP99 = stats.p99_abs;
479
+ const rawMax = stats.max_abs;
480
+ const observedP99 = absOrViolation(rawP99);
481
+ const observedMax = absOrViolation(rawMax);
482
+ if (rawP99 !== null && rawP99 !== void 0 && observedP99 === null) {
483
+ violations.push({
484
+ operator_index: op.operator_index,
485
+ operator_type: opType,
486
+ stat: "p99_abs",
487
+ observed: rawP99,
488
+ allowed: "finite non-negative number"
489
+ });
490
+ } else if (observedP99 !== null && typeof p99 === "number" && observedP99 > p99 * marginFactor) {
491
+ violations.push({
492
+ operator_index: op.operator_index,
493
+ operator_type: opType,
494
+ stat: "p99_abs",
495
+ observed: observedP99,
496
+ allowed: p99 * marginFactor
497
+ });
498
+ }
499
+ if (rawMax !== null && rawMax !== void 0 && observedMax === null) {
500
+ violations.push({
501
+ operator_index: op.operator_index,
502
+ operator_type: opType,
503
+ stat: "max_abs",
504
+ observed: rawMax,
505
+ allowed: "finite non-negative number"
506
+ });
507
+ } else if (observedMax !== null && typeof p99_99 === "number" && observedMax > p99_99 * marginFactor) {
508
+ violations.push({
509
+ operator_index: op.operator_index,
510
+ operator_type: opType,
511
+ stat: "max_abs",
512
+ observed: observedMax,
513
+ allowed: p99_99 * marginFactor
514
+ });
515
+ }
516
+ }
517
+ return { ok: violations.length === 0, violations };
518
+ }
519
+ function verifyBoundedTrace(trace, profile, options = {}) {
520
+ const now = options.now ?? /* @__PURE__ */ new Date();
521
+ const schema = trace.trace_schema_version;
522
+ if (schema !== "bounded_trace_v1") {
523
+ return {
524
+ valid: false,
525
+ proof_level: "execution",
526
+ reason: "trace_schema_unknown",
527
+ disclosed_operator_count: 0,
528
+ verified_merkle_paths: 0,
529
+ details: { trace_schema_version: schema }
530
+ };
531
+ }
532
+ const profileProfileId = profile.profile_id;
533
+ const traceProfileId = trace.profile_id;
534
+ if (profileProfileId !== void 0 && profileProfileId !== null && traceProfileId !== profileProfileId) {
535
+ return {
536
+ valid: false,
537
+ proof_level: "execution",
538
+ reason: "profile_trace_mismatch",
539
+ disclosed_operator_count: 0,
540
+ verified_merkle_paths: 0,
541
+ details: {
542
+ trace_profile_id: traceProfileId,
543
+ profile_profile_id: profileProfileId
544
+ }
545
+ };
546
+ }
547
+ const claimedRoot = trace.merkle_root;
548
+ if (typeof claimedRoot !== "string" || !claimedRoot.startsWith("sha256:")) {
549
+ return {
550
+ valid: false,
551
+ proof_level: "execution",
552
+ reason: "missing_merkle_root",
553
+ disclosed_operator_count: 0,
554
+ verified_merkle_paths: 0,
555
+ details: { merkle_root: claimedRoot }
556
+ };
557
+ }
558
+ const operators = Array.isArray(trace.operators) ? trace.operators : [];
559
+ const declaredCount = trace.operator_count;
560
+ if (typeof declaredCount === "number" && Number.isInteger(declaredCount) && declaredCount !== operators.length) {
561
+ return {
562
+ valid: false,
563
+ proof_level: "execution",
564
+ reason: "merkle_inclusion_failed",
565
+ disclosed_operator_count: operators.length,
566
+ verified_merkle_paths: 0,
567
+ details: {
568
+ declared_operator_count: declaredCount,
569
+ disclosed_operator_count: operators.length,
570
+ error: "operator_count_mismatch_without_inclusion_proofs"
571
+ }
572
+ };
573
+ }
574
+ let recomputed;
575
+ try {
576
+ recomputed = recomputeMerkleRoot(operators);
577
+ } catch (exc) {
578
+ return {
579
+ valid: false,
580
+ proof_level: "execution",
581
+ reason: "merkle_inclusion_failed",
582
+ disclosed_operator_count: operators.length,
583
+ verified_merkle_paths: 0,
584
+ details: { error: String(exc) }
585
+ };
586
+ }
587
+ if (recomputed !== claimedRoot) {
588
+ return {
589
+ valid: false,
590
+ proof_level: "execution",
591
+ reason: "merkle_inclusion_failed",
592
+ disclosed_operator_count: operators.length,
593
+ verified_merkle_paths: 0,
594
+ details: { claimed: claimedRoot, recomputed }
595
+ };
596
+ }
597
+ const policy = checkProfile(profile, now);
598
+ if (!policy.ok) {
599
+ return {
600
+ valid: false,
601
+ proof_level: "execution",
602
+ reason: policy.reason,
603
+ disclosed_operator_count: operators.length,
604
+ verified_merkle_paths: operators.length,
605
+ details: { profile_class: profile.profile_class }
606
+ };
607
+ }
608
+ const runtime = checkRuntime(trace, profile);
609
+ if (!runtime.ok) {
610
+ return {
611
+ valid: false,
612
+ proof_level: "execution",
613
+ reason: runtime.reason,
614
+ disclosed_operator_count: operators.length,
615
+ verified_merkle_paths: operators.length,
616
+ details: { trace_runtime: trace.runtime }
617
+ };
618
+ }
619
+ const thresholds = compareThresholds(trace, profile);
620
+ if (!thresholds.ok) {
621
+ return {
622
+ valid: false,
623
+ proof_level: "execution",
624
+ reason: "threshold_violation",
625
+ disclosed_operator_count: operators.length,
626
+ verified_merkle_paths: operators.length,
627
+ details: { violations: thresholds.violations }
628
+ };
629
+ }
630
+ return {
631
+ valid: true,
632
+ proof_level: "operator_bound",
633
+ reason: "thresholds_verified",
634
+ disclosed_operator_count: operators.length,
635
+ verified_merkle_paths: operators.length,
636
+ details: {
637
+ profile_id: profile.profile_id,
638
+ profile_class: profile.profile_class
639
+ }
640
+ };
641
+ }
642
+
643
+ // src/scoped.ts
644
+ import { createHash as createHash2 } from "crypto";
645
+ function canonicalSha256(obj) {
646
+ const h = createHash2("sha256");
647
+ h.update(canonicalJson2(obj));
648
+ return "sha256:" + h.digest("hex");
649
+ }
650
+ var HASH_RE = /^sha256:[0-9a-f]{64}$/;
651
+ function isCommitment(s) {
652
+ return typeof s === "string" && HASH_RE.test(s);
653
+ }
654
+ var SURFACE_SCHEMA = {
655
+ required: {
656
+ kind: { enum: ["field", "field_group", "document", "process", "workflow_step"] },
657
+ id: { type: "string" }
658
+ },
659
+ optional: {
660
+ path: { type: "string", nullable: true }
661
+ }
662
+ };
663
+ var SUPPORT_REF_SCHEMA = {
664
+ required: {
665
+ doc_id: { type: "string" },
666
+ span_commitment: { type: "string", pattern: "commitment" }
667
+ },
668
+ optional: {}
669
+ };
670
+ var SHADOW_RESULT_SCHEMA = {
671
+ required: {
672
+ shadow_id: { type: "string" },
673
+ result: { enum: ["certified", "not_certified", "abstain"] }
674
+ },
675
+ optional: {
676
+ shadow_type: {
677
+ enum: ["quantized", "distilled", "architecture_diverse", "base"],
678
+ nullable: true
679
+ },
680
+ score: { type: "number", nullable: true }
681
+ }
682
+ };
683
+ var RETRIEVAL_REF_SCHEMA = {
684
+ required: {
685
+ chunk_id: { type: "string" },
686
+ span_commitment: { type: "string", pattern: "commitment" }
687
+ },
688
+ optional: {}
689
+ };
690
+ var NAMED_SCHEMAS = {
691
+ _surface: SURFACE_SCHEMA,
692
+ _support_ref: SUPPORT_REF_SCHEMA,
693
+ _shadow_result: SHADOW_RESULT_SCHEMA,
694
+ _retrieval_ref: RETRIEVAL_REF_SCHEMA
695
+ };
696
+ var CERTIFICATE_LEVELS = [
697
+ "scoped_weak",
698
+ "scoped_moderate",
699
+ "scoped_strong",
700
+ "scoped_asymptotic",
701
+ "bounded_agreement"
702
+ ];
703
+ var ARTIFACT_SCHEMAS = {
704
+ local_manifold: {
705
+ required: {
706
+ certificate_type: { literal: "local_manifold" },
707
+ certificate_level: { enum: CERTIFICATE_LEVELS },
708
+ calibration_epoch: { type: "string" },
709
+ localizer_id: { type: "string" },
710
+ manifold_id: { type: "string" },
711
+ neighborhood_commitment: { type: "string", pattern: "commitment" },
712
+ local_threshold: { type: "number", min: 0, max: 1 },
713
+ surface: { schema: "_surface" },
714
+ result: { enum: ["certified", "not_certified", "abstain"] }
715
+ },
716
+ optional: {
717
+ fallback_to_global: { type: "boolean" },
718
+ signature: { type: "string", nullable: true }
719
+ }
720
+ },
721
+ hierarchical_output: {
722
+ required: {
723
+ certificate_type: { literal: "hierarchical_output" },
724
+ hierarchy_id: { type: "string" },
725
+ certified_paths: {
726
+ type: "array",
727
+ items: {
728
+ type: "array",
729
+ items: { type: "array", items: { type: "string" } }
730
+ }
731
+ },
732
+ uncertified_paths: {
733
+ type: "array",
734
+ items: {
735
+ type: "array",
736
+ items: { type: "array", items: { type: "string" } }
737
+ }
738
+ }
739
+ },
740
+ optional: {
741
+ uncertified_remainder: { type: "object", nullable: true },
742
+ signature: { type: "string", nullable: true }
743
+ }
744
+ },
745
+ model_continuity: {
746
+ required: {
747
+ certificate_type: { literal: "model_continuity" },
748
+ service_id: { type: "string" },
749
+ continuity_epoch: { type: "string" },
750
+ envelope_id: { type: "string" },
751
+ continuity_state: {
752
+ enum: [
753
+ "within_envelope",
754
+ "outside_envelope",
755
+ "epoch_change",
756
+ "insufficient_probes"
757
+ ]
758
+ },
759
+ probe_suite_id: { type: "string" },
760
+ probe_suite_commitment: { type: "string", pattern: "commitment" },
761
+ probe_response_commitment: { type: "string", pattern: "commitment" }
762
+ },
763
+ optional: {
764
+ signature: { type: "string", nullable: true }
765
+ }
766
+ },
767
+ retrieval_grounding: {
768
+ required: {
769
+ certificate_type: { literal: "retrieval_grounding" },
770
+ surface: { schema: "_surface" },
771
+ support_set: { type: "array", items: { schema: "_support_ref" } },
772
+ grounded: { type: "boolean" }
773
+ },
774
+ optional: {
775
+ per_stage_fn_bounds: {
776
+ type: "array",
777
+ items: { type: "number", min: 0, max: 1 },
778
+ nullable: true
779
+ },
780
+ aggregation_model: { type: "string", nullable: true },
781
+ signature: { type: "string", nullable: true }
782
+ }
783
+ },
784
+ workflow_composition: {
785
+ required: {
786
+ certificate_type: { literal: "workflow_composition" },
787
+ workflow_id: { type: "string" },
788
+ step_vpec_ids: { type: "array", items: { type: "string" } },
789
+ hard_relevant_steps: { type: "array", items: { type: "string" } },
790
+ composition_rule: { literal: "weakest_link_hard" },
791
+ composed_result: { enum: CERTIFICATE_LEVELS }
792
+ },
793
+ optional: {
794
+ informational_summary: { type: "object", nullable: true },
795
+ signature: { type: "string", nullable: true }
796
+ }
797
+ },
798
+ proof_of_absence: {
799
+ required: {
800
+ certificate_type: { literal: "proof_of_absence" },
801
+ surface: { schema: "_surface" },
802
+ absence_class: {
803
+ enum: [
804
+ "no_phi",
805
+ "no_restricted_identifier",
806
+ "no_prohibited_financial_claim"
807
+ ]
808
+ },
809
+ result: { type: "boolean" }
810
+ },
811
+ optional: {
812
+ detector_family: { type: "string", nullable: true },
813
+ per_stage_fn_bounds: {
814
+ type: "array",
815
+ items: { type: "number", min: 0, max: 1 },
816
+ nullable: true
817
+ },
818
+ signature: { type: "string", nullable: true }
819
+ }
820
+ },
821
+ calibration_epoch: {
822
+ required: {
823
+ certificate_type: { literal: "calibration_epoch" },
824
+ calibration_epoch: { type: "string" },
825
+ drift_state: { enum: ["stable", "drifting", "break"] },
826
+ source_mix: { type: "object" }
827
+ },
828
+ optional: {
829
+ parent_epoch: { type: "string", nullable: true },
830
+ signature: { type: "string", nullable: true }
831
+ }
832
+ },
833
+ shadow_committee: {
834
+ required: {
835
+ certificate_type: { literal: "shadow_committee" },
836
+ committee_rule: { type: "string" },
837
+ shadow_results: { type: "array", items: { schema: "_shadow_result" } },
838
+ committee_result: { enum: ["certified", "not_certified", "abstain"] }
839
+ },
840
+ optional: {
841
+ disagreement: { type: "boolean" }
842
+ }
843
+ },
844
+ decision_context: {
845
+ required: {
846
+ certificate_type: { literal: "decision_context" },
847
+ primitive_type: { literal: "DCE" },
848
+ scope: { type: "string" },
849
+ capture_mode: {
850
+ enum: [
851
+ "reasoning_block",
852
+ "planner_output",
853
+ "prompt_and_output_binding",
854
+ "declared_context_only"
855
+ ]
856
+ },
857
+ rationale_commitment: { type: "string", pattern: "commitment" },
858
+ context_commitment: { type: "string", pattern: "commitment" },
859
+ source_binding: { type: "string" },
860
+ proof_level_achieved: { enum: ["execution", "witnessed", "attestation"] },
861
+ scope_disclosure: { type: "string" }
862
+ },
863
+ optional: {
864
+ retrieval_references: { type: "array", items: { schema: "_retrieval_ref" } },
865
+ tool_output_commitments: {
866
+ type: "array",
867
+ items: { type: "string", pattern: "commitment" }
868
+ },
869
+ intent_declaration_ids: { type: "array", items: { type: "string" } },
870
+ constraints: { type: "array", items: { type: "string" } }
871
+ }
872
+ },
873
+ temporal_comparison: {
874
+ required: {
875
+ certificate_type: { literal: "temporal_comparison" },
876
+ pack_a_id: { type: "string" },
877
+ pack_b_id: { type: "string" },
878
+ pack_a_commitment: { type: "string", pattern: "commitment" },
879
+ pack_b_commitment: { type: "string", pattern: "commitment" },
880
+ pack_a_period_start: { type: "string" },
881
+ pack_a_period_end: { type: "string" },
882
+ pack_b_period_start: { type: "string" },
883
+ pack_b_period_end: { type: "string" },
884
+ checks_added: { type: "array", items: { type: "string" } },
885
+ checks_removed: { type: "array", items: { type: "string" } },
886
+ checks_retained_count: { type: "integer" },
887
+ coverage_changes_count: { type: "integer" },
888
+ performance_changes_count: { type: "integer" },
889
+ redaction_consistent: { type: "boolean" },
890
+ diff_commitment: { type: "string", pattern: "commitment" }
891
+ },
892
+ optional: {
893
+ redaction_profile_a: { type: "string", nullable: true },
894
+ redaction_profile_b: { type: "string", nullable: true },
895
+ signature: { type: "string", nullable: true }
896
+ }
897
+ }
898
+ };
899
+ function isPlainObject(v) {
900
+ return typeof v === "object" && v !== null && !Array.isArray(v);
901
+ }
902
+ function checkValue(value, spec) {
903
+ if (spec.nullable && value === null) return null;
904
+ if ("literal" in spec && spec.literal !== void 0) {
905
+ if (value !== spec.literal) return `expected literal ${JSON.stringify(spec.literal)}, got ${JSON.stringify(value)}`;
906
+ return null;
907
+ }
908
+ if (spec.enum) {
909
+ if (!spec.enum.includes(value)) {
910
+ return `value ${JSON.stringify(value)} not in enum ${JSON.stringify(spec.enum)}`;
911
+ }
912
+ return null;
913
+ }
914
+ if (spec.schema) {
915
+ const sub = NAMED_SCHEMAS[spec.schema];
916
+ return checkObject(value, sub);
917
+ }
918
+ switch (spec.type) {
919
+ case "string":
920
+ if (typeof value !== "string") return `expected string, got ${typeof value}`;
921
+ if (spec.pattern === "commitment" && !isCommitment(value)) {
922
+ return "expected sha256:[64-hex] commitment";
923
+ }
924
+ return null;
925
+ case "number":
926
+ if (typeof value !== "number" || Number.isNaN(value)) return `expected number, got ${typeof value}`;
927
+ if (spec.min !== void 0 && value < spec.min) return `value ${value} below minimum ${spec.min}`;
928
+ if (spec.max !== void 0 && value > spec.max) return `value ${value} above maximum ${spec.max}`;
929
+ return null;
930
+ case "integer":
931
+ if (typeof value !== "number" || !Number.isInteger(value)) return `expected integer, got ${typeof value}`;
932
+ return null;
933
+ case "boolean":
934
+ if (typeof value !== "boolean") return `expected boolean, got ${typeof value}`;
935
+ return null;
936
+ case "object":
937
+ if (!isPlainObject(value)) return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
938
+ return null;
939
+ case "array":
940
+ if (!Array.isArray(value)) return `expected array, got ${typeof value}`;
941
+ if (spec.items) {
942
+ for (let i = 0; i < value.length; i++) {
943
+ const err = checkValue(value[i], spec.items);
944
+ if (err) return `[${i}]: ${err}`;
945
+ }
946
+ }
947
+ return null;
948
+ }
949
+ return `unknown field spec`;
950
+ }
951
+ function checkObject(value, schema) {
952
+ if (!isPlainObject(value)) {
953
+ return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
954
+ }
955
+ const known = /* @__PURE__ */ new Set([...Object.keys(schema.required), ...Object.keys(schema.optional)]);
956
+ const extras = Object.keys(value).filter((k) => !known.has(k));
957
+ if (extras.length > 0) {
958
+ extras.sort();
959
+ return `unexpected extra fields: ${JSON.stringify(extras)}`;
960
+ }
961
+ for (const [name, spec] of Object.entries(schema.required)) {
962
+ if (!(name in value)) return `missing required field '${name}'`;
963
+ const err = checkValue(value[name], spec);
964
+ if (err) return `${name}: ${err}`;
965
+ }
966
+ for (const [name, spec] of Object.entries(schema.optional)) {
967
+ if (name in value) {
968
+ const err = checkValue(value[name], spec);
969
+ if (err) return `${name}: ${err}`;
970
+ }
971
+ }
972
+ return null;
973
+ }
974
+ function checkWorkflowCompositionSubset(entry) {
975
+ const stepIds = entry.step_vpec_ids;
976
+ const hard = entry.hard_relevant_steps;
977
+ if (!Array.isArray(stepIds) || !Array.isArray(hard)) return null;
978
+ const stepSet = new Set(stepIds);
979
+ const extras = hard.filter((s) => !stepSet.has(s));
980
+ if (extras.length > 0) {
981
+ return `hard_relevant_steps must be a subset of step_vpec_ids; unknown step ids: ${JSON.stringify(extras)}`;
982
+ }
983
+ return null;
984
+ }
985
+ function checkSourceMix(value) {
986
+ if (!isPlainObject(value)) return "source_mix is not an object";
987
+ let total = 0;
988
+ for (const [k, v] of Object.entries(value)) {
989
+ if (typeof k !== "string") return "source_mix has non-string key";
990
+ if (typeof v !== "number" || Number.isNaN(v)) {
991
+ return `source_mix[${JSON.stringify(k)}] is not a number`;
992
+ }
993
+ if (!Number.isFinite(v)) {
994
+ return `source_mix[${JSON.stringify(k)}] is not finite`;
995
+ }
996
+ if (v < 0) {
997
+ return `source_mix[${JSON.stringify(k)}] is negative`;
998
+ }
999
+ total += v;
1000
+ }
1001
+ if (Math.abs(total - 1) > 1e-6) return `source_mix sums to ${total}, expected 1.0`;
1002
+ return null;
1003
+ }
1004
+ var SCOPED_REASONS = [
1005
+ "ok",
1006
+ "missing_section",
1007
+ "malformed_section",
1008
+ "missing_commitment",
1009
+ "malformed_commitment",
1010
+ "missing_discriminator",
1011
+ "unknown_certificate_type",
1012
+ "schema_validation_failed",
1013
+ "ordering_violation",
1014
+ "commitment_mismatch"
1015
+ ];
1016
+ function emptyBundleCommitment() {
1017
+ return canonicalSha256({ scoped_certificates: [] });
1018
+ }
1019
+ function validateEntry(entry) {
1020
+ if (!isPlainObject(entry)) {
1021
+ return {
1022
+ ok: false,
1023
+ reason: "schema_validation_failed",
1024
+ details: { detail: "entry is not an object" }
1025
+ };
1026
+ }
1027
+ const ct = entry.certificate_type;
1028
+ if (typeof ct !== "string" || ct.length === 0) {
1029
+ return { ok: false, reason: "missing_discriminator", details: {} };
1030
+ }
1031
+ const schema = ARTIFACT_SCHEMAS[ct];
1032
+ if (!schema) {
1033
+ return {
1034
+ ok: false,
1035
+ reason: "unknown_certificate_type",
1036
+ details: { certificate_type: ct }
1037
+ };
1038
+ }
1039
+ const err = checkObject(entry, schema);
1040
+ if (err) {
1041
+ return {
1042
+ ok: false,
1043
+ reason: "schema_validation_failed",
1044
+ details: { certificate_type: ct, detail: err }
1045
+ };
1046
+ }
1047
+ if (ct === "calibration_epoch") {
1048
+ const smErr = checkSourceMix(entry.source_mix);
1049
+ if (smErr) {
1050
+ return {
1051
+ ok: false,
1052
+ reason: "schema_validation_failed",
1053
+ details: { certificate_type: ct, detail: smErr }
1054
+ };
1055
+ }
1056
+ }
1057
+ if (ct === "workflow_composition") {
1058
+ const subErr = checkWorkflowCompositionSubset(entry);
1059
+ if (subErr) {
1060
+ return {
1061
+ ok: false,
1062
+ reason: "schema_validation_failed",
1063
+ details: { certificate_type: ct, detail: subErr }
1064
+ };
1065
+ }
1066
+ }
1067
+ return { ok: true, certificateType: ct };
1068
+ }
1069
+ function verifyScopedCertificates(vpecLike) {
1070
+ if (!("scoped_certificates" in vpecLike)) {
1071
+ return { valid: false, reason: "missing_section", entry_count: 0, details: {} };
1072
+ }
1073
+ const entries = vpecLike.scoped_certificates;
1074
+ if (!Array.isArray(entries)) {
1075
+ return {
1076
+ valid: false,
1077
+ reason: "malformed_section",
1078
+ entry_count: 0,
1079
+ details: {
1080
+ detail: "scoped_certificates is not an array",
1081
+ got_type: typeof entries
1082
+ }
1083
+ };
1084
+ }
1085
+ if (!("scoped_certificates_commitment" in vpecLike)) {
1086
+ return {
1087
+ valid: false,
1088
+ reason: "missing_commitment",
1089
+ entry_count: entries.length,
1090
+ details: {}
1091
+ };
1092
+ }
1093
+ const declared = vpecLike.scoped_certificates_commitment;
1094
+ if (!isCommitment(declared)) {
1095
+ return {
1096
+ valid: false,
1097
+ reason: "malformed_commitment",
1098
+ entry_count: entries.length,
1099
+ details: { declared }
1100
+ };
1101
+ }
1102
+ const orderKeys = [];
1103
+ for (let i = 0; i < entries.length; i++) {
1104
+ const res = validateEntry(entries[i]);
1105
+ if (!res.ok) {
1106
+ return {
1107
+ valid: false,
1108
+ reason: res.reason,
1109
+ entry_count: entries.length,
1110
+ details: { index: i, ...res.details }
1111
+ };
1112
+ }
1113
+ const payloadHash = canonicalSha256(entries[i]);
1114
+ orderKeys.push([res.certificateType, payloadHash]);
1115
+ }
1116
+ const sortedKeys = [...orderKeys].sort((a, b) => {
1117
+ if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
1118
+ if (a[1] !== b[1]) return a[1] < b[1] ? -1 : 1;
1119
+ return 0;
1120
+ });
1121
+ for (let i = 0; i < orderKeys.length; i++) {
1122
+ if (orderKeys[i][0] !== sortedKeys[i][0] || orderKeys[i][1] !== sortedKeys[i][1]) {
1123
+ return {
1124
+ valid: false,
1125
+ reason: "ordering_violation",
1126
+ entry_count: entries.length,
1127
+ details: {
1128
+ index: i,
1129
+ got: orderKeys[i],
1130
+ expected: sortedKeys[i]
1131
+ }
1132
+ };
1133
+ }
1134
+ }
1135
+ let recomputed;
1136
+ if (entries.length === 0) {
1137
+ recomputed = emptyBundleCommitment();
1138
+ } else {
1139
+ const leaves = entries.map((e) => canonicalJson2(e));
1140
+ recomputed = buildMerkleRoot(leaves);
1141
+ }
1142
+ if (recomputed !== declared) {
1143
+ return {
1144
+ valid: false,
1145
+ reason: "commitment_mismatch",
1146
+ entry_count: entries.length,
1147
+ details: { declared, recomputed }
1148
+ };
1149
+ }
1150
+ return { valid: true, reason: "ok", entry_count: entries.length, details: {} };
1151
+ }
1152
+ export {
1153
+ ALLOWED_KINDS,
1154
+ ALLOWED_TRUST_EDGES,
1155
+ ENVELOPE_VERSION,
1156
+ PROOF_TIER_HIERARCHY,
1157
+ REASONS,
1158
+ SCOPED_REASONS,
1159
+ buildMerkleRoot,
1160
+ canonicalHash as canonicalHashV29,
1161
+ canonicalJson2 as canonicalJson,
1162
+ canonicalJsonString,
1163
+ canonicalJson as canonicalJsonV29,
1164
+ canonicalNumber,
1165
+ closeResolver,
1166
+ createUpstreamRootResolver,
1167
+ generateVerifyHtml,
1168
+ reproduceAggregations,
1169
+ reproduceManifestCanonicalHash,
1170
+ reproduceTierCeiling,
1171
+ seedKeyCache,
1172
+ validateAggregations,
1173
+ validateEnvelopeShape,
1174
+ validateManifestCanonicalHash,
1175
+ validateRecordsAgainstHarness,
1176
+ validateRuntimeBindingHash,
1177
+ verify,
1178
+ verifyBoundedTrace,
1179
+ verifyScopedCertificates,
1180
+ verifyV29
1181
+ };