@primust/verifier 1.0.0 → 1.1.0
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-NOADQWB6.js +3012 -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/tsa-chain-7KSQ5LAH.js +235 -0
- 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/key-cache.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public key cache for VPEC signature verification.
|
|
3
|
-
*
|
|
4
|
-
* Fetches public keys from trust anchor URLs and caches by key ID.
|
|
5
|
-
* Supports pinned trust roots for offline/air-gapped verification.
|
|
6
|
-
*/
|
|
7
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
8
|
-
// In-memory cache: kid → base64url public key
|
|
9
|
-
const cache = new Map();
|
|
10
|
-
/**
|
|
11
|
-
* Resolve a public key by key ID.
|
|
12
|
-
*
|
|
13
|
-
* @param kid - Key identifier from VPEC signature field
|
|
14
|
-
* @param publicKeyUrl - URL to fetch the PEM/base64url key
|
|
15
|
-
* @param trustRoot - Optional local PEM file path or content for offline mode
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Pre-seed the in-memory key cache. Used by evidence-pack assembler
|
|
19
|
-
* to inject the signer's public key before verification runs.
|
|
20
|
-
*/
|
|
21
|
-
export function seedKeyCache(kid, publicKey) {
|
|
22
|
-
cache.set(kid, publicKey);
|
|
23
|
-
}
|
|
24
|
-
export async function getKey(kid, publicKeyUrl, trustRoot) {
|
|
25
|
-
// Pinned trust root — zero network calls
|
|
26
|
-
if (trustRoot) {
|
|
27
|
-
// If it looks like a file path, read it
|
|
28
|
-
if (existsSync(trustRoot)) {
|
|
29
|
-
return readFileSync(trustRoot, 'utf-8').trim();
|
|
30
|
-
}
|
|
31
|
-
// Otherwise treat as inline key content
|
|
32
|
-
return trustRoot.trim();
|
|
33
|
-
}
|
|
34
|
-
// Check cache
|
|
35
|
-
const cached = cache.get(kid);
|
|
36
|
-
if (cached)
|
|
37
|
-
return cached;
|
|
38
|
-
// Fetch from URL with retries
|
|
39
|
-
if (!publicKeyUrl) {
|
|
40
|
-
throw new Error(`No public_key_url for kid=${kid}`);
|
|
41
|
-
}
|
|
42
|
-
let lastError = null;
|
|
43
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
44
|
-
try {
|
|
45
|
-
const resp = await fetch(publicKeyUrl, {
|
|
46
|
-
headers: { Accept: 'application/x-pem-file' },
|
|
47
|
-
signal: AbortSignal.timeout(10_000),
|
|
48
|
-
});
|
|
49
|
-
if (!resp.ok) {
|
|
50
|
-
throw new Error(`HTTP ${resp.status}`);
|
|
51
|
-
}
|
|
52
|
-
const pem = (await resp.text()).trim();
|
|
53
|
-
if (!pem) {
|
|
54
|
-
throw new Error('Empty response');
|
|
55
|
-
}
|
|
56
|
-
cache.set(kid, pem);
|
|
57
|
-
return pem;
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
lastError = e instanceof Error ? e : new Error(String(e));
|
|
61
|
-
if (attempt < 2) {
|
|
62
|
-
await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
throw new Error(`Failed to fetch key from ${publicKeyUrl} after 3 attempts: ${lastError?.message}`);
|
|
67
|
-
}
|
|
68
|
-
//# sourceMappingURL=key-cache.js.map
|
package/dist/key-cache.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"key-cache.js","sourceRoot":"","sources":["../src/key-cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEnD,8CAA8C;AAC9C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExC;;;;;;GAMG;AACH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,SAAiB;IACzD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,YAAoB,EACpB,SAAkB;IAElB,yCAAyC;IACzC,IAAI,SAAS,EAAE,CAAC;QACd,wCAAwC;QACxC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,wCAAwC;QACxC,OAAO,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,cAAc;IACd,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,8BAA8B;IAC9B,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,SAAS,GAAiB,IAAI,CAAC;IACnC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,YAAY,EAAE;gBACrC,OAAO,EAAE,EAAE,MAAM,EAAE,wBAAwB,EAAE;gBAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;aACpC,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACzC,CAAC;YAED,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;YACpC,CAAC;YAED,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACpB,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1D,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,sBAAsB,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AACtG,CAAC"}
|
package/dist/scoped.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reference TypeScript verifier for the `scoped_certificates` VPEC section.
|
|
3
|
-
*
|
|
4
|
-
* Ported from verifier-py/src/primust_verify/scoped.py. Validates the
|
|
5
|
-
* top-level `scoped_certificates` array + matching
|
|
6
|
-
* `scoped_certificates_commitment` emitted by the SDK's
|
|
7
|
-
* `primust.scoped.bundle` module (SCOPED_CERT_SPEC_v27 §14).
|
|
8
|
-
*
|
|
9
|
-
* Trust-chain scope:
|
|
10
|
-
* 1. Every entry carries a known `certificate_type` discriminator.
|
|
11
|
-
* 2. Every entry has the required structural fields for its type.
|
|
12
|
-
* 3. The array is in canonical order: lexicographic by
|
|
13
|
-
* (certificate_type, canonical_sha256(payload)).
|
|
14
|
-
* 4. The declared commitment byte-matches the recomputed Merkle root
|
|
15
|
-
* over the ordered canonical-JSON leaves. Empty bundle uses the
|
|
16
|
-
* well-known `sha256(canonical({scoped_certificates: []}))`.
|
|
17
|
-
*
|
|
18
|
-
* SI-3 (VPEC verifiability): this module's output MUST agree with the
|
|
19
|
-
* Python reference (`primust_verify.scoped`) byte-for-byte across the
|
|
20
|
-
* shared fixture corpus at
|
|
21
|
-
* packages/verifier-py/tests/fixtures/scoped_v1/. The conformance runner
|
|
22
|
-
* in scripts/scoped_conformance.py gates that in CI.
|
|
23
|
-
*/
|
|
24
|
-
import { buildMerkleRoot, canonicalJson, canonicalJsonString } from "./bounded-trace";
|
|
25
|
-
export declare const SCOPED_REASONS: readonly ["ok", "missing_section", "malformed_section", "missing_commitment", "malformed_commitment", "missing_discriminator", "unknown_certificate_type", "schema_validation_failed", "ordering_violation", "commitment_mismatch"];
|
|
26
|
-
export type ScopedReason = (typeof SCOPED_REASONS)[number];
|
|
27
|
-
export interface ScopedCertificatesResult {
|
|
28
|
-
valid: boolean;
|
|
29
|
-
reason: ScopedReason;
|
|
30
|
-
entry_count: number;
|
|
31
|
-
details: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
export declare function verifyScopedCertificates(vpecLike: Record<string, unknown>): ScopedCertificatesResult;
|
|
34
|
-
export { canonicalJson, canonicalJsonString, buildMerkleRoot };
|
|
35
|
-
//# sourceMappingURL=scoped.d.ts.map
|
package/dist/scoped.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scoped.d.ts","sourceRoot":"","sources":["../src/scoped.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AAmazB,eAAO,MAAM,cAAc,qOAWjB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AA+DD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,wBAAwB,CA8F1B;AAGD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/scoped.js
DELETED
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reference TypeScript verifier for the `scoped_certificates` VPEC section.
|
|
3
|
-
*
|
|
4
|
-
* Ported from verifier-py/src/primust_verify/scoped.py. Validates the
|
|
5
|
-
* top-level `scoped_certificates` array + matching
|
|
6
|
-
* `scoped_certificates_commitment` emitted by the SDK's
|
|
7
|
-
* `primust.scoped.bundle` module (SCOPED_CERT_SPEC_v27 §14).
|
|
8
|
-
*
|
|
9
|
-
* Trust-chain scope:
|
|
10
|
-
* 1. Every entry carries a known `certificate_type` discriminator.
|
|
11
|
-
* 2. Every entry has the required structural fields for its type.
|
|
12
|
-
* 3. The array is in canonical order: lexicographic by
|
|
13
|
-
* (certificate_type, canonical_sha256(payload)).
|
|
14
|
-
* 4. The declared commitment byte-matches the recomputed Merkle root
|
|
15
|
-
* over the ordered canonical-JSON leaves. Empty bundle uses the
|
|
16
|
-
* well-known `sha256(canonical({scoped_certificates: []}))`.
|
|
17
|
-
*
|
|
18
|
-
* SI-3 (VPEC verifiability): this module's output MUST agree with the
|
|
19
|
-
* Python reference (`primust_verify.scoped`) byte-for-byte across the
|
|
20
|
-
* shared fixture corpus at
|
|
21
|
-
* packages/verifier-py/tests/fixtures/scoped_v1/. The conformance runner
|
|
22
|
-
* in scripts/scoped_conformance.py gates that in CI.
|
|
23
|
-
*/
|
|
24
|
-
import { createHash } from "node:crypto";
|
|
25
|
-
import { buildMerkleRoot, canonicalJson, canonicalJsonString, } from "./bounded-trace";
|
|
26
|
-
// ── Per-certificate-type strict schema (mirrors verifier-py/scoped.py) ──
|
|
27
|
-
//
|
|
28
|
-
// Mirrors primust.scoped.schemas.py structurally. Zero SDK dep: schema
|
|
29
|
-
// values are duplicated into this file so the verifier package stays
|
|
30
|
-
// self-contained. When the SDK schemas change, update this table AND
|
|
31
|
-
// primust_verify/scoped.py in lockstep — the scoped_conformance.py CI
|
|
32
|
-
// gate catches drift via shared fixtures.
|
|
33
|
-
// ── Helpers ──
|
|
34
|
-
function canonicalSha256(obj) {
|
|
35
|
-
const h = createHash("sha256");
|
|
36
|
-
h.update(canonicalJson(obj));
|
|
37
|
-
return "sha256:" + h.digest("hex");
|
|
38
|
-
}
|
|
39
|
-
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
40
|
-
function isCommitment(s) {
|
|
41
|
-
return typeof s === "string" && HASH_RE.test(s);
|
|
42
|
-
}
|
|
43
|
-
const SURFACE_SCHEMA = {
|
|
44
|
-
required: {
|
|
45
|
-
kind: { enum: ["field", "field_group", "document", "process", "workflow_step"] },
|
|
46
|
-
id: { type: "string" },
|
|
47
|
-
},
|
|
48
|
-
optional: {
|
|
49
|
-
path: { type: "string", nullable: true },
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
const SUPPORT_REF_SCHEMA = {
|
|
53
|
-
required: {
|
|
54
|
-
doc_id: { type: "string" },
|
|
55
|
-
span_commitment: { type: "string", pattern: "commitment" },
|
|
56
|
-
},
|
|
57
|
-
optional: {},
|
|
58
|
-
};
|
|
59
|
-
const SHADOW_RESULT_SCHEMA = {
|
|
60
|
-
required: {
|
|
61
|
-
shadow_id: { type: "string" },
|
|
62
|
-
result: { enum: ["certified", "not_certified", "abstain"] },
|
|
63
|
-
},
|
|
64
|
-
optional: {
|
|
65
|
-
shadow_type: {
|
|
66
|
-
enum: ["quantized", "distilled", "architecture_diverse", "base"],
|
|
67
|
-
nullable: true,
|
|
68
|
-
},
|
|
69
|
-
score: { type: "number", nullable: true },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
const RETRIEVAL_REF_SCHEMA = {
|
|
73
|
-
required: {
|
|
74
|
-
chunk_id: { type: "string" },
|
|
75
|
-
span_commitment: { type: "string", pattern: "commitment" },
|
|
76
|
-
},
|
|
77
|
-
optional: {},
|
|
78
|
-
};
|
|
79
|
-
const NAMED_SCHEMAS = {
|
|
80
|
-
_surface: SURFACE_SCHEMA,
|
|
81
|
-
_support_ref: SUPPORT_REF_SCHEMA,
|
|
82
|
-
_shadow_result: SHADOW_RESULT_SCHEMA,
|
|
83
|
-
_retrieval_ref: RETRIEVAL_REF_SCHEMA,
|
|
84
|
-
};
|
|
85
|
-
const CERTIFICATE_LEVELS = [
|
|
86
|
-
"scoped_weak",
|
|
87
|
-
"scoped_moderate",
|
|
88
|
-
"scoped_strong",
|
|
89
|
-
"scoped_asymptotic",
|
|
90
|
-
"bounded_agreement",
|
|
91
|
-
];
|
|
92
|
-
const ARTIFACT_SCHEMAS = {
|
|
93
|
-
local_manifold: {
|
|
94
|
-
required: {
|
|
95
|
-
certificate_type: { literal: "local_manifold" },
|
|
96
|
-
certificate_level: { enum: CERTIFICATE_LEVELS },
|
|
97
|
-
calibration_epoch: { type: "string" },
|
|
98
|
-
localizer_id: { type: "string" },
|
|
99
|
-
manifold_id: { type: "string" },
|
|
100
|
-
neighborhood_commitment: { type: "string", pattern: "commitment" },
|
|
101
|
-
local_threshold: { type: "number", min: 0.0, max: 1.0 },
|
|
102
|
-
surface: { schema: "_surface" },
|
|
103
|
-
result: { enum: ["certified", "not_certified", "abstain"] },
|
|
104
|
-
},
|
|
105
|
-
optional: {
|
|
106
|
-
fallback_to_global: { type: "boolean" },
|
|
107
|
-
signature: { type: "string", nullable: true },
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
hierarchical_output: {
|
|
111
|
-
required: {
|
|
112
|
-
certificate_type: { literal: "hierarchical_output" },
|
|
113
|
-
hierarchy_id: { type: "string" },
|
|
114
|
-
certified_paths: {
|
|
115
|
-
type: "array",
|
|
116
|
-
items: {
|
|
117
|
-
type: "array",
|
|
118
|
-
items: { type: "array", items: { type: "string" } },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
uncertified_paths: {
|
|
122
|
-
type: "array",
|
|
123
|
-
items: {
|
|
124
|
-
type: "array",
|
|
125
|
-
items: { type: "array", items: { type: "string" } },
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
optional: {
|
|
130
|
-
uncertified_remainder: { type: "object", nullable: true },
|
|
131
|
-
signature: { type: "string", nullable: true },
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
model_continuity: {
|
|
135
|
-
required: {
|
|
136
|
-
certificate_type: { literal: "model_continuity" },
|
|
137
|
-
service_id: { type: "string" },
|
|
138
|
-
continuity_epoch: { type: "string" },
|
|
139
|
-
envelope_id: { type: "string" },
|
|
140
|
-
continuity_state: {
|
|
141
|
-
enum: [
|
|
142
|
-
"within_envelope",
|
|
143
|
-
"outside_envelope",
|
|
144
|
-
"epoch_change",
|
|
145
|
-
"insufficient_probes",
|
|
146
|
-
],
|
|
147
|
-
},
|
|
148
|
-
probe_suite_id: { type: "string" },
|
|
149
|
-
probe_suite_commitment: { type: "string", pattern: "commitment" },
|
|
150
|
-
probe_response_commitment: { type: "string", pattern: "commitment" },
|
|
151
|
-
},
|
|
152
|
-
optional: {
|
|
153
|
-
signature: { type: "string", nullable: true },
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
retrieval_grounding: {
|
|
157
|
-
required: {
|
|
158
|
-
certificate_type: { literal: "retrieval_grounding" },
|
|
159
|
-
surface: { schema: "_surface" },
|
|
160
|
-
support_set: { type: "array", items: { schema: "_support_ref" } },
|
|
161
|
-
grounded: { type: "boolean" },
|
|
162
|
-
},
|
|
163
|
-
optional: {
|
|
164
|
-
per_stage_fn_bounds: {
|
|
165
|
-
type: "array",
|
|
166
|
-
items: { type: "number", min: 0.0, max: 1.0 },
|
|
167
|
-
nullable: true,
|
|
168
|
-
},
|
|
169
|
-
aggregation_model: { type: "string", nullable: true },
|
|
170
|
-
signature: { type: "string", nullable: true },
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
workflow_composition: {
|
|
174
|
-
required: {
|
|
175
|
-
certificate_type: { literal: "workflow_composition" },
|
|
176
|
-
workflow_id: { type: "string" },
|
|
177
|
-
step_vpec_ids: { type: "array", items: { type: "string" } },
|
|
178
|
-
hard_relevant_steps: { type: "array", items: { type: "string" } },
|
|
179
|
-
composition_rule: { literal: "weakest_link_hard" },
|
|
180
|
-
composed_result: { enum: CERTIFICATE_LEVELS },
|
|
181
|
-
},
|
|
182
|
-
optional: {
|
|
183
|
-
informational_summary: { type: "object", nullable: true },
|
|
184
|
-
signature: { type: "string", nullable: true },
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
proof_of_absence: {
|
|
188
|
-
required: {
|
|
189
|
-
certificate_type: { literal: "proof_of_absence" },
|
|
190
|
-
surface: { schema: "_surface" },
|
|
191
|
-
absence_class: {
|
|
192
|
-
enum: [
|
|
193
|
-
"no_phi",
|
|
194
|
-
"no_restricted_identifier",
|
|
195
|
-
"no_prohibited_financial_claim",
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
result: { type: "boolean" },
|
|
199
|
-
},
|
|
200
|
-
optional: {
|
|
201
|
-
detector_family: { type: "string", nullable: true },
|
|
202
|
-
per_stage_fn_bounds: {
|
|
203
|
-
type: "array",
|
|
204
|
-
items: { type: "number", min: 0.0, max: 1.0 },
|
|
205
|
-
nullable: true,
|
|
206
|
-
},
|
|
207
|
-
signature: { type: "string", nullable: true },
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
calibration_epoch: {
|
|
211
|
-
required: {
|
|
212
|
-
certificate_type: { literal: "calibration_epoch" },
|
|
213
|
-
calibration_epoch: { type: "string" },
|
|
214
|
-
drift_state: { enum: ["stable", "drifting", "break"] },
|
|
215
|
-
source_mix: { type: "object" },
|
|
216
|
-
},
|
|
217
|
-
optional: {
|
|
218
|
-
parent_epoch: { type: "string", nullable: true },
|
|
219
|
-
signature: { type: "string", nullable: true },
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
shadow_committee: {
|
|
223
|
-
required: {
|
|
224
|
-
certificate_type: { literal: "shadow_committee" },
|
|
225
|
-
committee_rule: { type: "string" },
|
|
226
|
-
shadow_results: { type: "array", items: { schema: "_shadow_result" } },
|
|
227
|
-
committee_result: { enum: ["certified", "not_certified", "abstain"] },
|
|
228
|
-
},
|
|
229
|
-
optional: {
|
|
230
|
-
disagreement: { type: "boolean" },
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
decision_context: {
|
|
234
|
-
required: {
|
|
235
|
-
certificate_type: { literal: "decision_context" },
|
|
236
|
-
primitive_type: { literal: "DCE" },
|
|
237
|
-
scope: { type: "string" },
|
|
238
|
-
capture_mode: {
|
|
239
|
-
enum: [
|
|
240
|
-
"reasoning_block",
|
|
241
|
-
"planner_output",
|
|
242
|
-
"prompt_and_output_binding",
|
|
243
|
-
"declared_context_only",
|
|
244
|
-
],
|
|
245
|
-
},
|
|
246
|
-
rationale_commitment: { type: "string", pattern: "commitment" },
|
|
247
|
-
context_commitment: { type: "string", pattern: "commitment" },
|
|
248
|
-
source_binding: { type: "string" },
|
|
249
|
-
proof_level_achieved: { enum: ["execution", "witnessed", "attestation"] },
|
|
250
|
-
scope_disclosure: { type: "string" },
|
|
251
|
-
},
|
|
252
|
-
optional: {
|
|
253
|
-
retrieval_references: { type: "array", items: { schema: "_retrieval_ref" } },
|
|
254
|
-
tool_output_commitments: {
|
|
255
|
-
type: "array",
|
|
256
|
-
items: { type: "string", pattern: "commitment" },
|
|
257
|
-
},
|
|
258
|
-
intent_declaration_ids: { type: "array", items: { type: "string" } },
|
|
259
|
-
constraints: { type: "array", items: { type: "string" } },
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
temporal_comparison: {
|
|
263
|
-
required: {
|
|
264
|
-
certificate_type: { literal: "temporal_comparison" },
|
|
265
|
-
pack_a_id: { type: "string" },
|
|
266
|
-
pack_b_id: { type: "string" },
|
|
267
|
-
pack_a_commitment: { type: "string", pattern: "commitment" },
|
|
268
|
-
pack_b_commitment: { type: "string", pattern: "commitment" },
|
|
269
|
-
pack_a_period_start: { type: "string" },
|
|
270
|
-
pack_a_period_end: { type: "string" },
|
|
271
|
-
pack_b_period_start: { type: "string" },
|
|
272
|
-
pack_b_period_end: { type: "string" },
|
|
273
|
-
checks_added: { type: "array", items: { type: "string" } },
|
|
274
|
-
checks_removed: { type: "array", items: { type: "string" } },
|
|
275
|
-
checks_retained_count: { type: "integer" },
|
|
276
|
-
coverage_changes_count: { type: "integer" },
|
|
277
|
-
performance_changes_count: { type: "integer" },
|
|
278
|
-
redaction_consistent: { type: "boolean" },
|
|
279
|
-
diff_commitment: { type: "string", pattern: "commitment" },
|
|
280
|
-
},
|
|
281
|
-
optional: {
|
|
282
|
-
redaction_profile_a: { type: "string", nullable: true },
|
|
283
|
-
redaction_profile_b: { type: "string", nullable: true },
|
|
284
|
-
signature: { type: "string", nullable: true },
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
function isPlainObject(v) {
|
|
289
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
290
|
-
}
|
|
291
|
-
function checkValue(value, spec) {
|
|
292
|
-
if (spec.nullable && value === null)
|
|
293
|
-
return null;
|
|
294
|
-
if ("literal" in spec && spec.literal !== undefined) {
|
|
295
|
-
if (value !== spec.literal)
|
|
296
|
-
return `expected literal ${JSON.stringify(spec.literal)}, got ${JSON.stringify(value)}`;
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
if (spec.enum) {
|
|
300
|
-
if (!spec.enum.includes(value)) {
|
|
301
|
-
return `value ${JSON.stringify(value)} not in enum ${JSON.stringify(spec.enum)}`;
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
if (spec.schema) {
|
|
306
|
-
const sub = NAMED_SCHEMAS[spec.schema];
|
|
307
|
-
return checkObject(value, sub);
|
|
308
|
-
}
|
|
309
|
-
switch (spec.type) {
|
|
310
|
-
case "string":
|
|
311
|
-
if (typeof value !== "string")
|
|
312
|
-
return `expected string, got ${typeof value}`;
|
|
313
|
-
if (spec.pattern === "commitment" && !isCommitment(value)) {
|
|
314
|
-
return "expected sha256:[64-hex] commitment";
|
|
315
|
-
}
|
|
316
|
-
return null;
|
|
317
|
-
case "number":
|
|
318
|
-
if (typeof value !== "number" || Number.isNaN(value))
|
|
319
|
-
return `expected number, got ${typeof value}`;
|
|
320
|
-
if (spec.min !== undefined && value < spec.min)
|
|
321
|
-
return `value ${value} below minimum ${spec.min}`;
|
|
322
|
-
if (spec.max !== undefined && value > spec.max)
|
|
323
|
-
return `value ${value} above maximum ${spec.max}`;
|
|
324
|
-
return null;
|
|
325
|
-
case "integer":
|
|
326
|
-
if (typeof value !== "number" || !Number.isInteger(value))
|
|
327
|
-
return `expected integer, got ${typeof value}`;
|
|
328
|
-
return null;
|
|
329
|
-
case "boolean":
|
|
330
|
-
if (typeof value !== "boolean")
|
|
331
|
-
return `expected boolean, got ${typeof value}`;
|
|
332
|
-
return null;
|
|
333
|
-
case "object":
|
|
334
|
-
if (!isPlainObject(value))
|
|
335
|
-
return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
|
|
336
|
-
return null;
|
|
337
|
-
case "array":
|
|
338
|
-
if (!Array.isArray(value))
|
|
339
|
-
return `expected array, got ${typeof value}`;
|
|
340
|
-
if (spec.items) {
|
|
341
|
-
for (let i = 0; i < value.length; i++) {
|
|
342
|
-
const err = checkValue(value[i], spec.items);
|
|
343
|
-
if (err)
|
|
344
|
-
return `[${i}]: ${err}`;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
return `unknown field spec`;
|
|
350
|
-
}
|
|
351
|
-
function checkObject(value, schema) {
|
|
352
|
-
if (!isPlainObject(value)) {
|
|
353
|
-
return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
|
|
354
|
-
}
|
|
355
|
-
const known = new Set([...Object.keys(schema.required), ...Object.keys(schema.optional)]);
|
|
356
|
-
const extras = Object.keys(value).filter((k) => !known.has(k));
|
|
357
|
-
if (extras.length > 0) {
|
|
358
|
-
extras.sort();
|
|
359
|
-
return `unexpected extra fields: ${JSON.stringify(extras)}`;
|
|
360
|
-
}
|
|
361
|
-
for (const [name, spec] of Object.entries(schema.required)) {
|
|
362
|
-
if (!(name in value))
|
|
363
|
-
return `missing required field '${name}'`;
|
|
364
|
-
const err = checkValue(value[name], spec);
|
|
365
|
-
if (err)
|
|
366
|
-
return `${name}: ${err}`;
|
|
367
|
-
}
|
|
368
|
-
for (const [name, spec] of Object.entries(schema.optional)) {
|
|
369
|
-
if (name in value) {
|
|
370
|
-
const err = checkValue(value[name], spec);
|
|
371
|
-
if (err)
|
|
372
|
-
return `${name}: ${err}`;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
function checkWorkflowCompositionSubset(entry) {
|
|
378
|
-
// WorkflowCompositionArtifact.hard_relevant_steps must be a subset of
|
|
379
|
-
// step_vpec_ids (mirrors the SDK's model_validator + Python verifier's
|
|
380
|
-
// _check_workflow_composition_subset). Without this an artifact can
|
|
381
|
-
// declare a "hard relevant" step that isn't in the step list —
|
|
382
|
-
// structurally impossible since weakest-link-hard cannot evaluate it.
|
|
383
|
-
const stepIds = entry.step_vpec_ids;
|
|
384
|
-
const hard = entry.hard_relevant_steps;
|
|
385
|
-
if (!Array.isArray(stepIds) || !Array.isArray(hard))
|
|
386
|
-
return null;
|
|
387
|
-
const stepSet = new Set(stepIds);
|
|
388
|
-
const extras = hard.filter((s) => !stepSet.has(s));
|
|
389
|
-
if (extras.length > 0) {
|
|
390
|
-
return `hard_relevant_steps must be a subset of step_vpec_ids; unknown step ids: ${JSON.stringify(extras)}`;
|
|
391
|
-
}
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
function checkSourceMix(value) {
|
|
395
|
-
// CalibrationEpoch.source_mix must be dict[str, float] summing to
|
|
396
|
-
// 1.0 ± 1e-6 with every value FINITE and NON-NEGATIVE (mirrors SDK
|
|
397
|
-
// Pydantic validator + the post-Phase-1-#3 review hardening). Without
|
|
398
|
-
// the finite/non-negative checks {"a": 1.2, "b": -0.2} would sum to
|
|
399
|
-
// 1.0 but represent an invalid distribution.
|
|
400
|
-
if (!isPlainObject(value))
|
|
401
|
-
return "source_mix is not an object";
|
|
402
|
-
let total = 0;
|
|
403
|
-
for (const [k, v] of Object.entries(value)) {
|
|
404
|
-
if (typeof k !== "string")
|
|
405
|
-
return "source_mix has non-string key";
|
|
406
|
-
if (typeof v !== "number" || Number.isNaN(v)) {
|
|
407
|
-
return `source_mix[${JSON.stringify(k)}] is not a number`;
|
|
408
|
-
}
|
|
409
|
-
if (!Number.isFinite(v)) {
|
|
410
|
-
return `source_mix[${JSON.stringify(k)}] is not finite`;
|
|
411
|
-
}
|
|
412
|
-
if (v < 0) {
|
|
413
|
-
return `source_mix[${JSON.stringify(k)}] is negative`;
|
|
414
|
-
}
|
|
415
|
-
total += v;
|
|
416
|
-
}
|
|
417
|
-
if (Math.abs(total - 1.0) > 1e-6)
|
|
418
|
-
return `source_mix sums to ${total}, expected 1.0`;
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
// ── Result type + reason codes ──
|
|
422
|
-
export const SCOPED_REASONS = [
|
|
423
|
-
"ok",
|
|
424
|
-
"missing_section",
|
|
425
|
-
"malformed_section",
|
|
426
|
-
"missing_commitment",
|
|
427
|
-
"malformed_commitment",
|
|
428
|
-
"missing_discriminator",
|
|
429
|
-
"unknown_certificate_type",
|
|
430
|
-
"schema_validation_failed",
|
|
431
|
-
"ordering_violation",
|
|
432
|
-
"commitment_mismatch",
|
|
433
|
-
];
|
|
434
|
-
// ── Verification ──
|
|
435
|
-
function emptyBundleCommitment() {
|
|
436
|
-
return canonicalSha256({ scoped_certificates: [] });
|
|
437
|
-
}
|
|
438
|
-
function validateEntry(entry) {
|
|
439
|
-
if (!isPlainObject(entry)) {
|
|
440
|
-
return {
|
|
441
|
-
ok: false,
|
|
442
|
-
reason: "schema_validation_failed",
|
|
443
|
-
details: { detail: "entry is not an object" },
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
const ct = entry.certificate_type;
|
|
447
|
-
if (typeof ct !== "string" || ct.length === 0) {
|
|
448
|
-
return { ok: false, reason: "missing_discriminator", details: {} };
|
|
449
|
-
}
|
|
450
|
-
const schema = ARTIFACT_SCHEMAS[ct];
|
|
451
|
-
if (!schema) {
|
|
452
|
-
return {
|
|
453
|
-
ok: false,
|
|
454
|
-
reason: "unknown_certificate_type",
|
|
455
|
-
details: { certificate_type: ct },
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
const err = checkObject(entry, schema);
|
|
459
|
-
if (err) {
|
|
460
|
-
return {
|
|
461
|
-
ok: false,
|
|
462
|
-
reason: "schema_validation_failed",
|
|
463
|
-
details: { certificate_type: ct, detail: err },
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
if (ct === "calibration_epoch") {
|
|
467
|
-
const smErr = checkSourceMix(entry.source_mix);
|
|
468
|
-
if (smErr) {
|
|
469
|
-
return {
|
|
470
|
-
ok: false,
|
|
471
|
-
reason: "schema_validation_failed",
|
|
472
|
-
details: { certificate_type: ct, detail: smErr },
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (ct === "workflow_composition") {
|
|
477
|
-
const subErr = checkWorkflowCompositionSubset(entry);
|
|
478
|
-
if (subErr) {
|
|
479
|
-
return {
|
|
480
|
-
ok: false,
|
|
481
|
-
reason: "schema_validation_failed",
|
|
482
|
-
details: { certificate_type: ct, detail: subErr },
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return { ok: true, certificateType: ct };
|
|
487
|
-
}
|
|
488
|
-
export function verifyScopedCertificates(vpecLike) {
|
|
489
|
-
if (!("scoped_certificates" in vpecLike)) {
|
|
490
|
-
return { valid: false, reason: "missing_section", entry_count: 0, details: {} };
|
|
491
|
-
}
|
|
492
|
-
const entries = vpecLike.scoped_certificates;
|
|
493
|
-
if (!Array.isArray(entries)) {
|
|
494
|
-
// Present-but-not-an-array is a malformed section, NOT informational.
|
|
495
|
-
// Returning missing_section here would let a credential with
|
|
496
|
-
// `scoped_certificates: "not-an-array"` slip past verification.
|
|
497
|
-
return {
|
|
498
|
-
valid: false,
|
|
499
|
-
reason: "malformed_section",
|
|
500
|
-
entry_count: 0,
|
|
501
|
-
details: {
|
|
502
|
-
detail: "scoped_certificates is not an array",
|
|
503
|
-
got_type: typeof entries,
|
|
504
|
-
},
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
if (!("scoped_certificates_commitment" in vpecLike)) {
|
|
508
|
-
return {
|
|
509
|
-
valid: false,
|
|
510
|
-
reason: "missing_commitment",
|
|
511
|
-
entry_count: entries.length,
|
|
512
|
-
details: {},
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
const declared = vpecLike.scoped_certificates_commitment;
|
|
516
|
-
if (!isCommitment(declared)) {
|
|
517
|
-
return {
|
|
518
|
-
valid: false,
|
|
519
|
-
reason: "malformed_commitment",
|
|
520
|
-
entry_count: entries.length,
|
|
521
|
-
details: { declared },
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
// Per-entry structural validation + discriminator check.
|
|
525
|
-
const orderKeys = [];
|
|
526
|
-
for (let i = 0; i < entries.length; i++) {
|
|
527
|
-
const res = validateEntry(entries[i]);
|
|
528
|
-
if (!res.ok) {
|
|
529
|
-
return {
|
|
530
|
-
valid: false,
|
|
531
|
-
reason: res.reason,
|
|
532
|
-
entry_count: entries.length,
|
|
533
|
-
details: { index: i, ...res.details },
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
const payloadHash = canonicalSha256(entries[i]);
|
|
537
|
-
orderKeys.push([res.certificateType, payloadHash]);
|
|
538
|
-
}
|
|
539
|
-
// Canonical ordering check.
|
|
540
|
-
const sortedKeys = [...orderKeys].sort((a, b) => {
|
|
541
|
-
if (a[0] !== b[0])
|
|
542
|
-
return a[0] < b[0] ? -1 : 1;
|
|
543
|
-
if (a[1] !== b[1])
|
|
544
|
-
return a[1] < b[1] ? -1 : 1;
|
|
545
|
-
return 0;
|
|
546
|
-
});
|
|
547
|
-
for (let i = 0; i < orderKeys.length; i++) {
|
|
548
|
-
if (orderKeys[i][0] !== sortedKeys[i][0] || orderKeys[i][1] !== sortedKeys[i][1]) {
|
|
549
|
-
return {
|
|
550
|
-
valid: false,
|
|
551
|
-
reason: "ordering_violation",
|
|
552
|
-
entry_count: entries.length,
|
|
553
|
-
details: {
|
|
554
|
-
index: i,
|
|
555
|
-
got: orderKeys[i],
|
|
556
|
-
expected: sortedKeys[i],
|
|
557
|
-
},
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
// Commitment recomputation.
|
|
562
|
-
let recomputed;
|
|
563
|
-
if (entries.length === 0) {
|
|
564
|
-
recomputed = emptyBundleCommitment();
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
const leaves = entries.map((e) => canonicalJson(e));
|
|
568
|
-
recomputed = buildMerkleRoot(leaves);
|
|
569
|
-
}
|
|
570
|
-
if (recomputed !== declared) {
|
|
571
|
-
return {
|
|
572
|
-
valid: false,
|
|
573
|
-
reason: "commitment_mismatch",
|
|
574
|
-
entry_count: entries.length,
|
|
575
|
-
details: { declared, recomputed },
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
return { valid: true, reason: "ok", entry_count: entries.length, details: {} };
|
|
579
|
-
}
|
|
580
|
-
// Re-export canonical helpers for consumers that want to compose them.
|
|
581
|
-
export { canonicalJson, canonicalJsonString, buildMerkleRoot };
|
|
582
|
-
//# sourceMappingURL=scoped.js.map
|