@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.
- package/dist/chunk-LTWQK3HT.js +432 -0
- package/dist/chunk-ZADQUKKN.js +2963 -0
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +309 -361
- package/dist/index.d.ts +335 -13
- package/dist/index.js +1181 -13
- package/dist/v29-envelope-GFVVA2S6.js +42 -0
- package/package.json +7 -8
- package/dist/bounded-trace.d.ts +0 -46
- package/dist/bounded-trace.d.ts.map +0 -1
- package/dist/bounded-trace.js +0 -558
- package/dist/bounded-trace.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/key-cache.d.ts +0 -20
- package/dist/key-cache.d.ts.map +0 -1
- package/dist/key-cache.js +0 -68
- package/dist/key-cache.js.map +0 -1
- package/dist/scoped.d.ts +0 -35
- package/dist/scoped.d.ts.map +0 -1
- package/dist/scoped.js +0 -582
- package/dist/scoped.js.map +0 -1
- package/dist/types.d.ts +0 -60
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/upstream_resolver.d.ts +0 -60
- package/dist/upstream_resolver.d.ts.map +0 -1
- package/dist/upstream_resolver.js +0 -126
- package/dist/upstream_resolver.js.map +0 -1
- package/dist/v29-envelope.d.ts +0 -55
- package/dist/v29-envelope.d.ts.map +0 -1
- package/dist/v29-envelope.js +0 -450
- package/dist/v29-envelope.js.map +0 -1
- package/dist/verifier.d.ts +0 -36
- package/dist/verifier.d.ts.map +0 -1
- package/dist/verifier.js +0 -1235
- package/dist/verifier.js.map +0 -1
- package/dist/verifier.test.d.ts +0 -2
- package/dist/verifier.test.d.ts.map +0 -1
- package/dist/verifier.test.js +0 -395
- package/dist/verifier.test.js.map +0 -1
- package/dist/verify-html-template.d.ts +0 -45
- package/dist/verify-html-template.d.ts.map +0 -1
- package/dist/verify-html-template.js +0 -182
- package/dist/verify-html-template.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,1181 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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} · 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> — <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> — <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
|
+
};
|