@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.
Files changed (49) hide show
  1. package/dist/chunk-LTWQK3HT.js +432 -0
  2. package/dist/chunk-NOADQWB6.js +3012 -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/tsa-chain-7KSQ5LAH.js +235 -0
  8. package/dist/v29-envelope-GFVVA2S6.js +42 -0
  9. package/package.json +7 -8
  10. package/dist/bounded-trace.d.ts +0 -46
  11. package/dist/bounded-trace.d.ts.map +0 -1
  12. package/dist/bounded-trace.js +0 -558
  13. package/dist/bounded-trace.js.map +0 -1
  14. package/dist/cli.d.ts.map +0 -1
  15. package/dist/cli.js.map +0 -1
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/index.js.map +0 -1
  18. package/dist/key-cache.d.ts +0 -20
  19. package/dist/key-cache.d.ts.map +0 -1
  20. package/dist/key-cache.js +0 -68
  21. package/dist/key-cache.js.map +0 -1
  22. package/dist/scoped.d.ts +0 -35
  23. package/dist/scoped.d.ts.map +0 -1
  24. package/dist/scoped.js +0 -582
  25. package/dist/scoped.js.map +0 -1
  26. package/dist/types.d.ts +0 -60
  27. package/dist/types.d.ts.map +0 -1
  28. package/dist/types.js +0 -5
  29. package/dist/types.js.map +0 -1
  30. package/dist/upstream_resolver.d.ts +0 -60
  31. package/dist/upstream_resolver.d.ts.map +0 -1
  32. package/dist/upstream_resolver.js +0 -126
  33. package/dist/upstream_resolver.js.map +0 -1
  34. package/dist/v29-envelope.d.ts +0 -55
  35. package/dist/v29-envelope.d.ts.map +0 -1
  36. package/dist/v29-envelope.js +0 -450
  37. package/dist/v29-envelope.js.map +0 -1
  38. package/dist/verifier.d.ts +0 -36
  39. package/dist/verifier.d.ts.map +0 -1
  40. package/dist/verifier.js +0 -1235
  41. package/dist/verifier.js.map +0 -1
  42. package/dist/verifier.test.d.ts +0 -2
  43. package/dist/verifier.test.d.ts.map +0 -1
  44. package/dist/verifier.test.js +0 -395
  45. package/dist/verifier.test.js.map +0 -1
  46. package/dist/verify-html-template.d.ts +0 -45
  47. package/dist/verify-html-template.d.ts.map +0 -1
  48. package/dist/verify-html-template.js +0 -182
  49. package/dist/verify-html-template.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,13 +1,335 @@
1
- export { verify } from './verifier';
2
- export type { UpstreamRootResolver, VerifyOptions, VerificationResult, RekorStatus, } from './types';
3
- export { generateVerifyHtml } from './verify-html-template';
4
- export { seedKeyCache } from './key-cache';
5
- export { REASONS, buildMerkleRoot, canonicalJson, canonicalJsonString, canonicalNumber, verifyBoundedTrace, } from './bounded-trace';
6
- export type { BoundedTraceResult, ReasonCode } from './bounded-trace';
7
- export { SCOPED_REASONS, verifyScopedCertificates, } from './scoped';
8
- export type { ScopedCertificatesResult, ScopedReason } from './scoped';
9
- export { createUpstreamRootResolver, closeResolver, } from './upstream_resolver';
10
- export type { UpstreamResolverOptions } from './upstream_resolver';
11
- 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';
12
- export type { V29VerifyResult, ProofTier } from './v29-envelope';
13
- //# sourceMappingURL=index.d.ts.map
1
+ import { ProofLevel, ProofDistribution, Coverage } from '@primust/artifact-core';
2
+
3
+ /**
4
+ * primust-verify Types for offline verifier.
5
+ */
6
+
7
+ type RekorStatus = 'active' | 'not_found' | 'revoked' | 'unavailable' | 'skipped';
8
+ /**
9
+ * Synchronous resolver that returns the parent VPEC's
10
+ * `commitment_root_poseidon2` (canonical "poseidon2:<hex>" form) for a
11
+ * given upstream VPEC envelope ID, or null if the parent's root is not
12
+ * locally available.
13
+ *
14
+ * Implementations typically wrap `SqliteStore.getUpstreamVpecRoot(vpecId)`.
15
+ * The verifier core deliberately avoids a SqliteStore dependency to
16
+ * preserve its zero-runtime-deps invariant — callers inject this resolver
17
+ * when they have local store context (CLI / dashboard verifier paths).
18
+ *
19
+ * Returning null is offline-tolerant: the verifier surfaces a warning
20
+ * (`upstream_vpec_proof_no_anchor_root_in_artifact`) rather than a hard
21
+ * mismatch, so cross-org chains still verify at boundaries.
22
+ */
23
+ type UpstreamRootResolver = (vpecId: string) => string | null | undefined;
24
+ interface VerifyOptions {
25
+ /** Reject test_mode: true artifacts. */
26
+ production?: boolean;
27
+ /** Skip Rekor check — fully offline mode. */
28
+ skip_network?: boolean;
29
+ /** Path to custom public key PEM (for enterprise self-hosting). */
30
+ trust_root?: string;
31
+ }
32
+ interface VerificationResult {
33
+ vpec_id: string;
34
+ valid: boolean;
35
+ schema_version: string;
36
+ proof_level: ProofLevel | string;
37
+ proof_distribution: ProofDistribution | Record<string, unknown>;
38
+ org_id: string;
39
+ workflow_id: string;
40
+ process_context_hash: string | null;
41
+ partial: boolean;
42
+ test_mode: boolean;
43
+ signer_id: string;
44
+ kid: string;
45
+ signed_at: string;
46
+ timestamp_anchor_valid: boolean | null;
47
+ rekor_status: RekorStatus;
48
+ zk_proof_valid: boolean | null;
49
+ commitment_root_valid: boolean | null;
50
+ manifest_hashes: Record<string, string>;
51
+ gaps: Array<{
52
+ gap_id: string;
53
+ gap_type: string;
54
+ severity: string;
55
+ }>;
56
+ coverage: Coverage | Record<string, unknown>;
57
+ violations_present: boolean;
58
+ violation_count: number;
59
+ errors: string[];
60
+ warnings: string[];
61
+ }
62
+
63
+ /**
64
+ * primust-verify — Offline VPEC artifact verifier.
65
+ *
66
+ * ZERO runtime dependencies on Primust infrastructure after initial
67
+ * public key fetch. Must verify a VPEC produced today in 10 years.
68
+ *
69
+ * Verification steps (in order):
70
+ * 1. Schema validation
71
+ * 2. SHA-256 integrity + Ed25519 signature (combined)
72
+ * 3. Kid resolution
73
+ * 4. Signer status check (Rekor — stubbed in v1)
74
+ * 5. RFC 3161 timestamp verification (stubbed in v1)
75
+ * 6. Proof level integrity
76
+ * 7. Manifest hash audit
77
+ * 8. ZK proof verification (stubbed in v1)
78
+ * 9. test_mode check
79
+ */
80
+
81
+ /**
82
+ * Verify a VPEC artifact.
83
+ *
84
+ * @param artifact - Parsed artifact JSON (Record<string, unknown>)
85
+ * @param options - Verification options
86
+ * @param upstreamRootResolver - Optional synchronous resolver returning
87
+ * the PARENT VPEC's `commitment_root_poseidon2` for a given upstream
88
+ * VPEC envelope ID. When provided, governance_upstream_vpec_inclusion
89
+ * proofs are anchored to `Poseidon2(lineage_commitment(child_run_id,
90
+ * upstream_vpec_ids), parentRoot)` — the same reduction
91
+ * `PolicySnapshotService.openRun` performs at issuance. When omitted,
92
+ * the verifier falls back to anchoring against the artifact's own
93
+ * `commitment_root_poseidon2` (legacy behavior — preserves backward
94
+ * compat for callers that lack upstream-store context).
95
+ * @returns VerificationResult with errors/warnings
96
+ */
97
+ declare function verify(artifact: Record<string, unknown>, options?: VerifyOptions, upstreamRootResolver?: UpstreamRootResolver): Promise<VerificationResult>;
98
+
99
+ /**
100
+ * verify.html template generator for Evidence Pack output.
101
+ *
102
+ * Generates a self-contained HTML file that:
103
+ * 1. Embeds the Ed25519 public key
104
+ * 2. Pre-loads the Evidence Pack summary
105
+ * 3. Verifies using WebCrypto API on page load
106
+ * 4. Works completely offline
107
+ * 5. Shows chain of VPECs with verification status
108
+ */
109
+ interface VerifyHtmlOptions {
110
+ publicKeyB64: string;
111
+ packData: {
112
+ pack_id: string;
113
+ org_id: string;
114
+ period_start: string;
115
+ period_end: string;
116
+ vpec_count: number;
117
+ vpecs: Array<{
118
+ vpec_id: string;
119
+ proof_level_floor: string;
120
+ provable_surface: number;
121
+ gap_count: number;
122
+ issued_at: string;
123
+ environment: string;
124
+ signature: string;
125
+ signed_payload: string;
126
+ }>;
127
+ provable_surface_summary: {
128
+ mathematical: number;
129
+ execution: number;
130
+ witnessed: number;
131
+ attestation: number;
132
+ };
133
+ upstream_vpecs?: Array<{
134
+ vpec_id: string;
135
+ org_id: string;
136
+ proof_level_floor: string;
137
+ signature: string;
138
+ signed_payload: string;
139
+ }>;
140
+ };
141
+ }
142
+ declare function generateVerifyHtml(options: VerifyHtmlOptions): string;
143
+
144
+ /**
145
+ * Public key cache for VPEC signature verification.
146
+ *
147
+ * Fetches public keys from trust anchor URLs and caches by key ID.
148
+ * Supports pinned trust roots for offline/air-gapped verification.
149
+ *
150
+ * SI-3: untrusted public_key_url values from the artifact are NOT honored.
151
+ * Without a caller-supplied trust_root, the URL host must be on the
152
+ * built-in allowlist. Otherwise an attacker can mint a VPEC pointing at
153
+ * their own key-server and `verify()` would return valid.
154
+ */
155
+ /**
156
+ * Resolve a public key by key ID.
157
+ *
158
+ * @param kid - Key identifier from VPEC signature field
159
+ * @param publicKeyUrl - URL to fetch the PEM/base64url key
160
+ * @param trustRoot - Optional local PEM file path or content for offline mode
161
+ */
162
+ /**
163
+ * Pre-seed the in-memory key cache. Used by evidence-pack assembler
164
+ * to inject the signer's public key before verification runs.
165
+ */
166
+ declare function seedKeyCache(kid: string, publicKey: string): void;
167
+
168
+ /**
169
+ * Reference TypeScript interpreter for bounded_trace_v1 verification.
170
+ *
171
+ * This is the canonical second-opinion verifier described in PRIMUST_V27 §20.2,
172
+ * ported from verifier-py/src/primust_verify/bounded_trace.py. It is
173
+ * intentionally small and obviously-correct: consumes a BoundedTraceV1 payload
174
+ * plus a ProfileRecord and produces one of the §16.3 canonical downgrade
175
+ * reason codes (or "ok" on success).
176
+ *
177
+ * Crypto used: SHA-256 with RFC 6962-style domain-separated leaves (0x00)
178
+ * and nodes (0x01). Canonical JSON follows RFC 8785 + ECMAScript 7.1.12.1
179
+ * number serialization for cross-language parity.
180
+ *
181
+ * SI-3 (VPEC verifiability): this module's output MUST match the Python
182
+ * reference byte-for-byte across the shared conformance fixture corpus at
183
+ * packages/verifier-py/tests/fixtures/bounded_trace_v1/. The conformance
184
+ * runner in scripts/lane_a_conformance.* enforces this.
185
+ *
186
+ * Zero runtime dependencies besides node:crypto.
187
+ */
188
+ declare function canonicalNumber(x: number): string;
189
+ /**
190
+ * Canonical JSON serialization:
191
+ * - object keys sorted lexicographically
192
+ * - UTF-8
193
+ * - separators ",":":", no whitespace
194
+ * - numbers via canonicalNumber
195
+ * - strings via JSON.stringify (same escape rules as Python json with ensure_ascii=False)
196
+ */
197
+ declare function canonicalJson$1(obj: unknown): Uint8Array;
198
+ declare function canonicalJsonString(obj: unknown): string;
199
+ declare function buildMerkleRoot(leaves: Uint8Array[]): string;
200
+ interface BoundedTraceResult {
201
+ valid: boolean;
202
+ proof_level: "operator_bound" | "execution";
203
+ reason: string;
204
+ disclosed_operator_count: number;
205
+ verified_merkle_paths: number;
206
+ details: Record<string, unknown>;
207
+ }
208
+ declare const REASONS: readonly ["profile_not_found", "profile_signature_invalid", "profile_not_empirical", "profile_expired", "profile_revoked", "profile_no_freshness_window", "profile_trace_mismatch", "runtime_not_supported", "trace_schema_unknown", "missing_merkle_root", "merkle_inclusion_failed", "threshold_violation", "runtime_section_missing", "retrieval_section_missing", "closed_api_pre_cohort", "closed_api_pre_promote", "closed_api_v2_promoted", "profile_deprecated", "unknown_lifecycle_state"];
209
+ type ReasonCode = (typeof REASONS)[number] | "thresholds_verified";
210
+ declare function verifyBoundedTrace(trace: Record<string, unknown>, profile: Record<string, unknown>, options?: {
211
+ now?: Date;
212
+ }): BoundedTraceResult;
213
+
214
+ /**
215
+ * Reference TypeScript verifier for the `scoped_certificates` VPEC section.
216
+ *
217
+ * Ported from verifier-py/src/primust_verify/scoped.py. Validates the
218
+ * top-level `scoped_certificates` array + matching
219
+ * `scoped_certificates_commitment` emitted by the SDK's
220
+ * `primust.scoped.bundle` module (SCOPED_CERT_SPEC_v27 §14).
221
+ *
222
+ * Trust-chain scope:
223
+ * 1. Every entry carries a known `certificate_type` discriminator.
224
+ * 2. Every entry has the required structural fields for its type.
225
+ * 3. The array is in canonical order: lexicographic by
226
+ * (certificate_type, canonical_sha256(payload)).
227
+ * 4. The declared commitment byte-matches the recomputed Merkle root
228
+ * over the ordered canonical-JSON leaves. Empty bundle uses the
229
+ * well-known `sha256(canonical({scoped_certificates: []}))`.
230
+ *
231
+ * SI-3 (VPEC verifiability): this module's output MUST agree with the
232
+ * Python reference (`primust_verify.scoped`) byte-for-byte across the
233
+ * shared fixture corpus at
234
+ * packages/verifier-py/tests/fixtures/scoped_v1/. The conformance runner
235
+ * in scripts/scoped_conformance.py gates that in CI.
236
+ */
237
+
238
+ 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"];
239
+ type ScopedReason = (typeof SCOPED_REASONS)[number];
240
+ interface ScopedCertificatesResult {
241
+ valid: boolean;
242
+ reason: ScopedReason;
243
+ entry_count: number;
244
+ details: Record<string, unknown>;
245
+ }
246
+ declare function verifyScopedCertificates(vpecLike: Record<string, unknown>): ScopedCertificatesResult;
247
+
248
+ interface UpstreamResolverOptions {
249
+ /**
250
+ * Optional path to the runtime-core SqliteStore database. When provided,
251
+ * the resolver opens a read-only handle on first use and queries
252
+ * `vpec_commitment_roots` for `kind='vpec'` first, then `kind='run'`
253
+ * (matches `SqliteStore.getUpstreamVpecRoot`). Resolver close is the
254
+ * caller's responsibility — see `closeResolver`.
255
+ */
256
+ dbPath?: string;
257
+ /**
258
+ * Optional in-memory map of `vpec_id` → `'poseidon2:<64-hex>'`. Useful
259
+ * for tests, deterministic offline verification of a bundle, and air-
260
+ * gapped flows that inline the upstream commitment alongside the VPEC.
261
+ */
262
+ envelopes?: Map<string, string>;
263
+ }
264
+
265
+ /**
266
+ * Build a synchronous resolver that maps a vpec_id to its cached
267
+ * `commitment_root_poseidon2` value, or null if no row is cached.
268
+ *
269
+ * Lookup order: envelopes Map → SqliteStore vpec row → SqliteStore run
270
+ * row. This mirrors `SqliteStore.getUpstreamVpecRoot` so SDK and verifier
271
+ * never disagree about which root applies to a given lineage edge.
272
+ */
273
+ declare function createUpstreamRootResolver(opts: UpstreamResolverOptions): UpstreamRootResolver;
274
+ /**
275
+ * Best-effort close hook for resolvers that opened a SQLite handle.
276
+ *
277
+ * The resolver is closure-scoped, so we expose a static helper instead
278
+ * of returning an object. Callers may also let the handle be GC'd —
279
+ * better-sqlite3 closes on finalizer — but explicit close is safer in
280
+ * long-running CLI batches.
281
+ */
282
+ declare function closeResolver(resolver: UpstreamRootResolver): void;
283
+
284
+ /**
285
+ * v29 ProofCheck envelope + manifest + runtime-binding conformance.
286
+ *
287
+ * Mirror of packages/verifier-py/src/primust_verify/v29_envelope.py.
288
+ * The Py↔TS parity contract: canonicalJson(x) MUST produce byte-identical
289
+ * output for identical inputs across both languages, and every reproduction
290
+ * function MUST return the same value.
291
+ *
292
+ * Per docs/v29-draft/PROOFCHECK_ENVELOPE_SPEC_v0_1.md, AGENT_MANIFEST_SPEC,
293
+ * HARNESS_MANIFEST_SPEC, RUNTIME_BINDING_SPEC.
294
+ *
295
+ * Pure functions only. No DB, no network, no KMS.
296
+ */
297
+ /**
298
+ * Resolve a key id to its raw 32-byte Ed25519 public key. Sync to keep
299
+ * the verifier pure / pluggable; relying parties typically wire this to
300
+ * an embedded trust root (e.g. a JSON map of pinned kids).
301
+ */
302
+ type PubkeyResolver = (kid: string) => Uint8Array;
303
+ declare const ENVELOPE_VERSION = "v0.1";
304
+ declare const ALLOWED_KINDS: Set<string>;
305
+ declare const ALLOWED_TRUST_EDGES: Set<string>;
306
+ declare const PROOF_TIER_HIERARCHY: readonly ["attestation", "witnessed", "execution", "operator_bound", "verifiable_inference", "mathematical"];
307
+ type ProofTier = (typeof PROOF_TIER_HIERARCHY)[number];
308
+ interface V29VerifyResult {
309
+ ok: boolean;
310
+ reasonCode: string | null;
311
+ detail?: string;
312
+ }
313
+ declare function canonicalJson(value: unknown): string;
314
+ declare function canonicalHash(value: unknown): string;
315
+ declare function validateEnvelopeShape(envelope: unknown): V29VerifyResult;
316
+ declare function reproduceAggregations(records: Array<Record<string, unknown>>): Record<string, unknown>;
317
+ declare function validateAggregations(envelope: Record<string, unknown>): V29VerifyResult;
318
+ declare function reproduceTierCeiling(harnessManifest: Record<string, unknown>, surface: string, tierClaimed: string): {
319
+ effective_tier: string;
320
+ tier_ceiling_applied: string | null;
321
+ };
322
+ declare function validateRecordsAgainstHarness(records: Array<Record<string, unknown>>, harnessManifest: Record<string, unknown> | null): V29VerifyResult;
323
+ declare function validateRuntimeBindingHash(binding: Record<string, unknown>): V29VerifyResult;
324
+ declare function reproduceManifestCanonicalHash(manifest: Record<string, unknown>, manifestKind: "agent" | "harness"): string;
325
+ declare function validateManifestCanonicalHash(manifest: Record<string, unknown>, manifestKind: "agent" | "harness"): V29VerifyResult;
326
+ declare function verifyV29(opts: {
327
+ envelope: Record<string, unknown>;
328
+ runtimeBinding?: Record<string, unknown>;
329
+ agentManifest?: Record<string, unknown>;
330
+ harnessManifest?: Record<string, unknown>;
331
+ pubkeyResolver?: PubkeyResolver | null;
332
+ requireSignatures?: boolean;
333
+ }): V29VerifyResult;
334
+
335
+ export { ALLOWED_KINDS, ALLOWED_TRUST_EDGES, type BoundedTraceResult, ENVELOPE_VERSION, PROOF_TIER_HIERARCHY, type ProofTier, REASONS, type ReasonCode, type RekorStatus, SCOPED_REASONS, type ScopedCertificatesResult, type ScopedReason, type UpstreamResolverOptions, type UpstreamRootResolver, type V29VerifyResult, type VerificationResult, type VerifyOptions, buildMerkleRoot, canonicalHash as canonicalHashV29, canonicalJson$1 as canonicalJson, canonicalJsonString, canonicalJson as canonicalJsonV29, canonicalNumber, closeResolver, createUpstreamRootResolver, generateVerifyHtml, reproduceAggregations, reproduceManifestCanonicalHash, reproduceTierCeiling, seedKeyCache, validateAggregations, validateEnvelopeShape, validateManifestCanonicalHash, validateRecordsAgainstHarness, validateRuntimeBindingHash, verify, verifyBoundedTrace, verifyScopedCertificates, verifyV29 };