@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/cli.js CHANGED
@@ -1,391 +1,339 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * primust-verify CLI
4
- *
5
- * Usage:
6
- * primust-verify vpec_<id>.json
7
- * primust-verify vpec_<id>.json --production
8
- * primust-verify vpec_<id>.json --trust-root ./my-pubkey.pem
9
- * primust-verify vpec_<id>.json --skip-network
10
- * primust-verify vpec_<id>.json --json
11
- *
12
- * Exit codes:
13
- * 0 = valid (production)
14
- * 1 = invalid / tampered
15
- * 2 = valid but sandbox-only (or system error)
16
- */
17
- import { readFileSync } from 'node:fs';
18
- import { parseArgs } from 'node:util';
19
- import { verify } from './verifier.js';
20
- import { createUpstreamRootResolver } from './upstream_resolver.js';
21
- const PROOF_LEVEL_DISPLAY = {
22
- mathematical: 'mathematical',
23
- verifiable_inference: 'verifiable_inference',
24
- operator_bound: 'operator_bound',
25
- execution: 'execution',
26
- witnessed: 'witnessed',
27
- attestation: 'attestation',
2
+ import {
3
+ createUpstreamRootResolver,
4
+ verify
5
+ } from "./chunk-NOADQWB6.js";
6
+
7
+ // src/cli.ts
8
+ import { readFileSync } from "fs";
9
+ import { parseArgs } from "util";
10
+ var PROOF_LEVEL_DISPLAY = {
11
+ mathematical: "mathematical",
12
+ verifiable_inference: "verifiable_inference",
13
+ operator_bound: "operator_bound",
14
+ execution: "execution",
15
+ witnessed: "witnessed",
16
+ attestation: "attestation"
28
17
  };
29
18
  function formatProofLevel(level) {
30
- return PROOF_LEVEL_DISPLAY[level] ?? level;
19
+ return PROOF_LEVEL_DISPLAY[level] ?? level;
31
20
  }
32
21
  function formatDistribution(dist) {
33
- const levels = ['mathematical', 'verifiable_inference', 'operator_bound', 'execution', 'witnessed', 'attestation'];
34
- return levels
35
- .filter((l) => typeof dist[l] === 'number' && dist[l] > 0)
36
- .map((l) => `${formatProofLevel(l)}: ${dist[l]}`)
37
- .join(' ');
22
+ const levels = ["mathematical", "verifiable_inference", "operator_bound", "execution", "witnessed", "attestation"];
23
+ return levels.filter((l) => typeof dist[l] === "number" && dist[l] > 0).map((l) => `${formatProofLevel(l)}: ${dist[l]}`).join(" ");
38
24
  }
39
25
  function formatGapsSummary(gaps) {
40
- if (gaps.length === 0)
41
- return '0';
42
- const counts = {};
43
- for (const g of gaps) {
44
- counts[g.severity] = (counts[g.severity] ?? 0) + 1;
45
- }
46
- const parts = Object.entries(counts).map(([sev, n]) => `${n} ${sev}`);
47
- return `${gaps.length} (${parts.join(', ')})`;
26
+ if (gaps.length === 0) return "0";
27
+ const counts = {};
28
+ for (const g of gaps) {
29
+ counts[g.severity] = (counts[g.severity] ?? 0) + 1;
30
+ }
31
+ const parts = Object.entries(counts).map(([sev, n]) => `${n} ${sev}`);
32
+ return `${gaps.length} (${parts.join(", ")})`;
48
33
  }
49
34
  function formatTimestamp(result) {
50
- let ts = result.signed_at;
51
- if (result.timestamp_anchor_valid === true) {
52
- ts += ' (RFC 3161 \u2713)';
53
- }
54
- return ts;
35
+ let ts = result.signed_at;
36
+ if (result.timestamp_anchor_valid === true) {
37
+ ts += " (RFC 3161 \u2713)";
38
+ }
39
+ return ts;
55
40
  }
56
- /* ── V23-2: --format human — relying-party readable certificate ── */
57
41
  function formatHumanCertificate(artifact, result) {
58
- const sig = result.valid ? 'VALID \u2713' : 'INVALID \u2717';
59
- const env = artifact.environment === 'sandbox'
60
- ? 'SANDBOX \u2014 not audit-acceptable'
61
- : 'PRODUCTION';
62
- const lines = [];
63
- lines.push(`GOVERNANCE CREDENTIAL${' '.repeat(28)}${sig}`);
64
- lines.push('\u2500'.repeat(56));
65
- lines.push('');
66
- lines.push(`Credential ID: ${result.vpec_id}`);
67
- lines.push(`System: ${result.workflow_id}`);
68
- lines.push(`Environment: ${env}`);
69
- const started = artifact.started_at ?? '';
70
- const closed = artifact.closed_at ?? '';
71
- if (started || closed) {
72
- lines.push(`Run: ${started} \u2192 ${closed}`);
42
+ const sig = result.valid ? "VALID \u2713" : "INVALID \u2717";
43
+ const env = artifact.environment === "sandbox" ? "SANDBOX \u2014 not audit-acceptable" : "PRODUCTION";
44
+ const lines = [];
45
+ lines.push(`GOVERNANCE CREDENTIAL${" ".repeat(28)}${sig}`);
46
+ lines.push("\u2500".repeat(56));
47
+ lines.push("");
48
+ lines.push(`Credential ID: ${result.vpec_id}`);
49
+ lines.push(`System: ${result.workflow_id}`);
50
+ lines.push(`Environment: ${env}`);
51
+ const started = artifact.started_at ?? "";
52
+ const closed = artifact.closed_at ?? "";
53
+ if (started || closed) {
54
+ lines.push(`Run: ${started} \u2192 ${closed}`);
55
+ }
56
+ lines.push("");
57
+ lines.push("SIGNATURE");
58
+ lines.push(` Status: ${sig}`);
59
+ lines.push(` Signed by: Primust, Inc.`);
60
+ lines.push(` Timestamp: ${result.timestamp_anchor_valid ? "DigiCert RFC 3161" : "unsigned"}`);
61
+ lines.push(` Issued: ${result.signed_at}`);
62
+ lines.push("");
63
+ lines.push("GOVERNANCE PROOF");
64
+ const ps = artifact.provable_surface;
65
+ lines.push(` Provable surface: ${ps != null ? `${(ps * 100).toFixed(1)}%` : "\u2014"}`);
66
+ const records = artifact.records ?? [];
67
+ lines.push(` Checks run: ${records.length}`);
68
+ lines.push(` Open gaps: ${result.gaps.length}`);
69
+ const ac = artifact.action_coverage;
70
+ if (ac) {
71
+ lines.push("");
72
+ lines.push("ACTION COVERAGE");
73
+ const total = ac.total_actions;
74
+ const gov = ac.governed_actions;
75
+ const ungov = ac.ungoverned_actions;
76
+ const cov = ac.coverage_pct ?? 0;
77
+ lines.push(` Actions taken: ${total}`);
78
+ lines.push(` Governed: ${gov} (${(cov * 100).toFixed(1)}%)`);
79
+ lines.push(` Ungoverned: ${ungov} (${((1 - cov) * 100).toFixed(1)}%)`);
80
+ const ungovList = ac.ungoverned;
81
+ if (ungovList && ungovList.length > 0) {
82
+ lines.push("");
83
+ const cu = ac.consequential_ungoverned ?? {};
84
+ const cuIndices = cu.sequence_indices ?? [];
85
+ for (const u of ungovList) {
86
+ const idx = u.sequence_index ?? "?";
87
+ const atype = u.action_type ?? "unknown";
88
+ const tname = u.tool_name;
89
+ let label = ` #${idx} ${atype}`;
90
+ if (tname) label += `: ${tname}`;
91
+ label += cuIndices.includes(idx) ? " \u26A0 consequential \u2014 no check ran" : " \u2014 no check ran";
92
+ lines.push(label);
93
+ }
73
94
  }
74
- lines.push('');
75
- lines.push('SIGNATURE');
76
- lines.push(` Status: ${sig}`);
77
- lines.push(` Signed by: Primust, Inc.`);
78
- lines.push(` Timestamp: ${result.timestamp_anchor_valid ? 'DigiCert RFC 3161' : 'unsigned'}`);
79
- lines.push(` Issued: ${result.signed_at}`);
80
- lines.push('');
81
- lines.push('GOVERNANCE PROOF');
82
- const ps = artifact.provable_surface;
83
- lines.push(` Provable surface: ${ps != null ? `${(ps * 100).toFixed(1)}%` : '\u2014'}`);
84
- const records = artifact.records ?? [];
85
- lines.push(` Checks run: ${records.length}`);
86
- lines.push(` Open gaps: ${result.gaps.length}`);
87
- // Action coverage (v23)
88
- const ac = artifact.action_coverage;
89
- if (ac) {
90
- lines.push('');
91
- lines.push('ACTION COVERAGE');
92
- const total = ac.total_actions;
93
- const gov = ac.governed_actions;
94
- const ungov = ac.ungoverned_actions;
95
- const cov = ac.coverage_pct ?? 0;
96
- lines.push(` Actions taken: ${total}`);
97
- lines.push(` Governed: ${gov} (${(cov * 100).toFixed(1)}%)`);
98
- lines.push(` Ungoverned: ${ungov} (${((1 - cov) * 100).toFixed(1)}%)`);
99
- // Rich mode — per-action detail
100
- const ungovList = ac.ungoverned;
101
- if (ungovList && ungovList.length > 0) {
102
- lines.push('');
103
- const cu = ac.consequential_ungoverned ?? {};
104
- const cuIndices = cu.sequence_indices ?? [];
105
- for (const u of ungovList) {
106
- const idx = u.sequence_index ?? '?';
107
- const atype = u.action_type ?? 'unknown';
108
- const tname = u.tool_name;
109
- let label = ` #${idx} ${atype}`;
110
- if (tname)
111
- label += `: ${tname}`;
112
- label += cuIndices.includes(idx)
113
- ? ' \u26A0 consequential \u2014 no check ran'
114
- : ' \u2014 no check ran';
115
- lines.push(label);
116
- }
117
- }
118
- // Strict mode — indices only
119
- const ungovIndices = ac.ungoverned_sequence_indices;
120
- if (ungovIndices && ungovIndices.length > 0 && !ungovList) {
121
- lines.push('');
122
- lines.push(` Ungoverned at indices: ${JSON.stringify(ungovIndices)}`);
123
- const cuCount = ac.consequential_ungoverned_count;
124
- if (cuCount)
125
- lines.push(` ${cuCount} consequential`);
126
- lines.push('');
127
- lines.push(' Per-action detail available via:');
128
- lines.push(' primust verify-activity vpec.json activity_export.json');
129
- }
95
+ const ungovIndices = ac.ungoverned_sequence_indices;
96
+ if (ungovIndices && ungovIndices.length > 0 && !ungovList) {
97
+ lines.push("");
98
+ lines.push(` Ungoverned at indices: ${JSON.stringify(ungovIndices)}`);
99
+ const cuCount = ac.consequential_ungoverned_count;
100
+ if (cuCount) lines.push(` ${cuCount} consequential`);
101
+ lines.push("");
102
+ lines.push(" Per-action detail available via:");
103
+ lines.push(" primust verify-activity vpec.json activity_export.json");
130
104
  }
131
- // Check summaries with evidence_metadata (rich mode)
132
- const metadataMode = artifact.metadata_mode;
133
- if (records.length > 0 && metadataMode === 'rich') {
134
- lines.push('');
135
- lines.push('GOVERNANCE CHECKS');
136
- const shown = records.slice(0, 10);
137
- for (const r of shown) {
138
- let checkId = r.manifest_id ?? r.check_id ?? 'unknown';
139
- if (checkId.includes('@'))
140
- checkId = checkId.split('@')[0];
141
- const checkResult = r.check_result ?? 'unknown';
142
- const em = r.evidence_metadata;
143
- let line = ` ${checkId}: ${checkResult}`;
144
- if (em) {
145
- const details = [];
146
- if ('entity_count' in em)
147
- details.push(`${em.entity_count} entities`);
148
- if ('risk_score' in em)
149
- details.push(`risk ${em.risk_score}`);
150
- if ('action_taken' in em)
151
- details.push(em.action_taken);
152
- if (details.length > 0)
153
- line += ` (${details.join(', ')})`;
154
- }
155
- lines.push(line);
156
- }
157
- if (records.length > 10) {
158
- lines.push(` ... and ${records.length - 10} more checks`);
159
- }
105
+ }
106
+ const metadataMode = artifact.metadata_mode;
107
+ if (records.length > 0 && metadataMode === "rich") {
108
+ lines.push("");
109
+ lines.push("GOVERNANCE CHECKS");
110
+ const shown = records.slice(0, 10);
111
+ for (const r of shown) {
112
+ let checkId = r.manifest_id ?? r.check_id ?? "unknown";
113
+ if (checkId.includes("@")) checkId = checkId.split("@")[0];
114
+ const checkResult = r.check_result ?? "unknown";
115
+ const em = r.evidence_metadata;
116
+ let line = ` ${checkId}: ${checkResult}`;
117
+ if (em) {
118
+ const details = [];
119
+ if ("entity_count" in em) details.push(`${em.entity_count} entities`);
120
+ if ("risk_score" in em) details.push(`risk ${em.risk_score}`);
121
+ if ("action_taken" in em) details.push(em.action_taken);
122
+ if (details.length > 0) line += ` (${details.join(", ")})`;
123
+ }
124
+ lines.push(line);
125
+ }
126
+ if (records.length > 10) {
127
+ lines.push(` ... and ${records.length - 10} more checks`);
160
128
  }
161
- lines.push('');
162
- lines.push('WHAT THIS PROVES');
163
- lines.push(' Governance checks ran on the actions listed in this credential');
164
- lines.push(' at the stated proof levels. This credential was issued at the');
165
- lines.push(' time of the governed run and has not been altered since.');
166
- lines.push('');
167
- lines.push('WHAT THIS DOES NOT PROVE');
168
- lines.push(' That the checks run constitute sufficient evidence for any');
169
- lines.push(' specific regulatory requirement. That determination belongs');
170
- lines.push(' to the customer, their compliance function, and their auditors.');
171
- lines.push('');
172
- lines.push('VERIFY INDEPENDENTLY');
173
- lines.push(` verify.primust.com/${result.vpec_id}`);
174
- lines.push(` primust verify ${result.vpec_id}.json`);
175
- lines.push('\u2500'.repeat(56));
176
- return lines.join('\n');
129
+ }
130
+ lines.push("");
131
+ lines.push("WHAT THIS PROVES");
132
+ lines.push(" Governance checks ran on the actions listed in this credential");
133
+ lines.push(" at the stated proof levels. This credential was issued at the");
134
+ lines.push(" time of the governed run and has not been altered since.");
135
+ lines.push("");
136
+ lines.push("WHAT THIS DOES NOT PROVE");
137
+ lines.push(" That the checks run constitute sufficient evidence for any");
138
+ lines.push(" specific regulatory requirement. That determination belongs");
139
+ lines.push(" to the customer, their compliance function, and their auditors.");
140
+ lines.push("");
141
+ lines.push("VERIFY INDEPENDENTLY");
142
+ lines.push(` verify.primust.com/${result.vpec_id}`);
143
+ lines.push(` primust verify ${result.vpec_id}.json`);
144
+ lines.push("\u2500".repeat(56));
145
+ return lines.join("\n");
177
146
  }
178
147
  function printHumanResult(result) {
179
- const dist = result.proof_distribution;
180
- const cov = result.coverage;
181
- if (result.valid) {
182
- console.log(`\n \u2713 VPEC ${result.vpec_id} \u2014 VALID`);
183
- }
184
- else {
185
- console.log(`\n \u2717 VPEC ${result.vpec_id} \u2014 INVALID`);
186
- for (const err of result.errors) {
187
- console.log(` Error: ${err}`);
188
- }
189
- }
190
- const distStr = formatDistribution(dist);
191
- if (distStr) {
192
- console.log(` Distribution: ${distStr}`);
148
+ const dist = result.proof_distribution;
149
+ const cov = result.coverage;
150
+ if (result.valid) {
151
+ console.log(`
152
+ \u2713 VPEC ${result.vpec_id} \u2014 VALID`);
153
+ } else {
154
+ console.log(`
155
+ \u2717 VPEC ${result.vpec_id} \u2014 INVALID`);
156
+ for (const err of result.errors) {
157
+ console.log(` Error: ${err}`);
193
158
  }
194
- console.log(` Workflow: ${result.workflow_id}`);
195
- console.log(` Org: ${result.org_id}`);
196
- console.log(` Signed: ${formatTimestamp(result)}`);
197
- console.log(` Signer: ${result.signer_id} / kid: ${result.kid}`);
198
- console.log(` Rekor: ${result.rekor_status}`);
199
- if (cov && typeof cov.policy_coverage_pct === 'number') {
200
- let covStr = `${cov.policy_coverage_pct}% policy`;
201
- if (typeof cov.instrumentation_surface_pct === 'number') {
202
- covStr += ` | ${cov.instrumentation_surface_pct}% instrumentation surface`;
203
- }
204
- console.log(` Coverage: ${covStr}`);
205
- }
206
- console.log(` Gaps: ${formatGapsSummary(result.gaps)}`);
207
- if (result.process_context_hash) {
208
- console.log(` Process hash: ${result.process_context_hash}`);
159
+ }
160
+ const distStr = formatDistribution(dist);
161
+ if (distStr) {
162
+ console.log(` Distribution: ${distStr}`);
163
+ }
164
+ console.log(` Workflow: ${result.workflow_id}`);
165
+ console.log(` Org: ${result.org_id}`);
166
+ console.log(` Signed: ${formatTimestamp(result)}`);
167
+ console.log(` Signer: ${result.signer_id} / kid: ${result.kid}`);
168
+ console.log(` Rekor: ${result.rekor_status}`);
169
+ if (cov && typeof cov.policy_coverage_pct === "number") {
170
+ let covStr = `${cov.policy_coverage_pct}% policy`;
171
+ if (typeof cov.instrumentation_surface_pct === "number") {
172
+ covStr += ` | ${cov.instrumentation_surface_pct}% instrumentation surface`;
209
173
  }
210
- console.log(` Test mode: ${result.test_mode}`);
211
- if (result.test_mode && result.valid) {
212
- console.log(` \u26A0 TEST CREDENTIAL \u2014 not for production use`);
174
+ console.log(` Coverage: ${covStr}`);
175
+ }
176
+ console.log(` Gaps: ${formatGapsSummary(result.gaps)}`);
177
+ if (result.process_context_hash) {
178
+ console.log(` Process hash: ${result.process_context_hash}`);
179
+ }
180
+ console.log(` Test mode: ${result.test_mode}`);
181
+ if (result.test_mode && result.valid) {
182
+ console.log(` \u26A0 TEST CREDENTIAL \u2014 not for production use`);
183
+ }
184
+ if (result.warnings.length > 0) {
185
+ for (const w of result.warnings) {
186
+ console.log(` Warning: ${w}`);
213
187
  }
214
- if (result.warnings.length > 0) {
215
- for (const w of result.warnings) {
216
- console.log(` Warning: ${w}`);
217
- }
218
- }
219
- console.log('');
188
+ }
189
+ console.log("");
220
190
  }
221
- export async function main(args) {
222
- let parsed;
223
- try {
224
- parsed = parseArgs({
225
- args: args ?? process.argv.slice(2),
226
- options: {
227
- production: { type: 'boolean', default: false },
228
- 'skip-network': { type: 'boolean', default: false },
229
- 'trust-root': { type: 'string' },
230
- json: { type: 'boolean', default: false },
231
- format: { type: 'string' },
232
- human: { type: 'boolean', default: false },
233
- help: { type: 'boolean', short: 'h', default: false },
234
- // Follow-6 #79: parent-root upstream-VPEC anchor check. When the
235
- // local SqliteStore (or an envelope file) contains the parent
236
- // VPEC's commitment_root, the verifier upgrades the legacy
237
- // "unanchored" warning to a cryptographic equality check.
238
- 'upstream-db': { type: 'string' },
239
- 'upstream-envelope': { type: 'string', multiple: true },
240
- },
241
- allowPositionals: true,
242
- });
243
- }
244
- catch (err) {
245
- console.error(`Error: ${err.message}`);
246
- return 2;
247
- }
248
- if (parsed.values.help || parsed.positionals.length === 0) {
249
- console.log('Usage: primust-verify [format] <artifact.json> [options]');
250
- console.log('');
251
- console.log('Formats: vpec, pack, w3c-vc, dsse, scitt, receipt, all');
252
- console.log('If format omitted, auto-detects from file contents.');
253
- console.log('');
254
- console.log('Options:');
255
- console.log(' --production Require production environment');
256
- console.log(' --skip-network Skip online checks');
257
- console.log(' --trust-root Custom public key path');
258
- console.log(' --json Output as JSON');
259
- console.log(' --format human Human-readable certificate for non-technical relying parties');
260
- console.log(' --human Shorthand for --format human');
261
- console.log(' --upstream-db <path> SqliteStore with cached parent VPEC roots');
262
- console.log(' (defaults to PRIMUST_RUNTIME_DB_PATH or');
263
- console.log(' ~/.primust/runtime.db when present).');
264
- console.log(' --upstream-envelope <path> JSON envelope file mapping');
265
- console.log(' upstream_vpec_id commitment_root_poseidon2');
266
- console.log(' (repeatable; merged with --upstream-db).');
267
- return parsed.values.help ? 0 : 2;
268
- }
269
- // Support subcommand: primust verify vpec artifact.json
270
- const FORMATS = new Set(['vpec', 'pack', 'w3c-vc', 'dsse', 'scitt', 'receipt', 'all']);
271
- let format = null;
272
- let filePath;
273
- if (parsed.positionals.length >= 2 && FORMATS.has(parsed.positionals[0])) {
274
- format = parsed.positionals[0];
275
- filePath = parsed.positionals[1];
276
- }
277
- else {
278
- filePath = parsed.positionals[0];
279
- }
280
- const jsonOutput = parsed.values.json ?? false;
281
- const humanCert = parsed.values.human || parsed.values.format === 'human';
282
- // Read artifact file
283
- let rawJson;
284
- try {
285
- rawJson = readFileSync(filePath, 'utf-8');
286
- }
287
- catch {
288
- const msg = `Error: file not found: ${filePath}`;
289
- if (jsonOutput) {
290
- console.error(msg);
291
- }
292
- else {
293
- console.error(msg);
294
- }
295
- return 2;
296
- }
297
- let artifact;
298
- try {
299
- artifact = JSON.parse(rawJson);
300
- }
301
- catch {
302
- const msg = `Error: invalid JSON in ${filePath}`;
303
- if (jsonOutput) {
304
- console.error(msg);
305
- }
306
- else {
307
- console.error(msg);
308
- }
309
- return 2;
310
- }
311
- // ── Follow-6 #79: build upstream-root resolver ──
312
- // The CLI wires the parent-VPEC anchor check whenever the caller
313
- // passes --upstream-db (or --upstream-envelope). When neither flag
314
- // is supplied we leave the resolver undefined so legacy callers
315
- // keep the existing "unanchored" warning behavior — strict backward
316
- // compat for tests / scripts that don't know about the flag.
317
- let resolver;
318
- const dbPath = parsed.values['upstream-db'];
319
- const envelopePaths = parsed.values['upstream-envelope'] ?? [];
320
- if (dbPath || envelopePaths.length > 0) {
321
- const envelopes = new Map();
322
- for (const ep of envelopePaths) {
323
- try {
324
- const env = JSON.parse(readFileSync(ep, 'utf-8'));
325
- // Two accepted shapes:
326
- // - { vpec_id: 'vpec_xxx', commitment_root_poseidon2: 'poseidon2:…' }
327
- // - { '<vpec_id>': '<root>', ... } (flat map)
328
- if (typeof env.vpec_id === 'string' && typeof env.commitment_root_poseidon2 === 'string') {
329
- envelopes.set(env.vpec_id, env.commitment_root_poseidon2);
330
- }
331
- else {
332
- for (const [k, v] of Object.entries(env)) {
333
- if (typeof v === 'string')
334
- envelopes.set(k, v);
335
- }
336
- }
337
- }
338
- catch (err) {
339
- const msg = `Error: cannot read upstream envelope ${ep}: ${err.message}`;
340
- console.error(msg);
341
- return 2;
342
- }
343
- }
344
- resolver = createUpstreamRootResolver({
345
- dbPath,
346
- envelopes: envelopes.size > 0 ? envelopes : undefined,
347
- });
191
+ async function main(args) {
192
+ let parsed;
193
+ try {
194
+ parsed = parseArgs({
195
+ args: args ?? process.argv.slice(2),
196
+ options: {
197
+ production: { type: "boolean", default: false },
198
+ "skip-network": { type: "boolean", default: false },
199
+ "trust-root": { type: "string" },
200
+ json: { type: "boolean", default: false },
201
+ format: { type: "string" },
202
+ human: { type: "boolean", default: false },
203
+ help: { type: "boolean", short: "h", default: false },
204
+ // Follow-6 #79: parent-root upstream-VPEC anchor check. When the
205
+ // local SqliteStore (or an envelope file) contains the parent
206
+ // VPEC's commitment_root, the verifier upgrades the legacy
207
+ // "unanchored" warning to a cryptographic equality check.
208
+ "upstream-db": { type: "string" },
209
+ "upstream-envelope": { type: "string", multiple: true }
210
+ },
211
+ allowPositionals: true
212
+ });
213
+ } catch (err) {
214
+ console.error(`Error: ${err.message}`);
215
+ return 2;
216
+ }
217
+ if (parsed.values.help || parsed.positionals.length === 0) {
218
+ console.log("Usage: primust-verify [format] <artifact.json> [options]");
219
+ console.log("");
220
+ console.log("Formats: vpec, pack, w3c-vc, dsse, scitt, receipt, all");
221
+ console.log("If format omitted, auto-detects from file contents.");
222
+ console.log("");
223
+ console.log("Options:");
224
+ console.log(" --production Require production environment");
225
+ console.log(" --skip-network Skip online checks");
226
+ console.log(" --trust-root Custom public key path");
227
+ console.log(" --json Output as JSON");
228
+ console.log(" --format human Human-readable certificate for non-technical relying parties");
229
+ console.log(" --human Shorthand for --format human");
230
+ console.log(" --upstream-db <path> SqliteStore with cached parent VPEC roots");
231
+ console.log(" (defaults to PRIMUST_RUNTIME_DB_PATH or");
232
+ console.log(" ~/.primust/runtime.db when present).");
233
+ console.log(" --upstream-envelope <path> JSON envelope file mapping");
234
+ console.log(" upstream_vpec_id \u2192 commitment_root_poseidon2");
235
+ console.log(" (repeatable; merged with --upstream-db).");
236
+ return parsed.values.help ? 0 : 2;
237
+ }
238
+ const FORMATS = /* @__PURE__ */ new Set(["vpec", "pack", "w3c-vc", "dsse", "scitt", "receipt", "all"]);
239
+ let format = null;
240
+ let filePath;
241
+ if (parsed.positionals.length >= 2 && FORMATS.has(parsed.positionals[0])) {
242
+ format = parsed.positionals[0];
243
+ filePath = parsed.positionals[1];
244
+ } else {
245
+ filePath = parsed.positionals[0];
246
+ }
247
+ const jsonOutput = parsed.values.json ?? false;
248
+ const humanCert = parsed.values.human || parsed.values.format === "human";
249
+ let rawJson;
250
+ try {
251
+ rawJson = readFileSync(filePath, "utf-8");
252
+ } catch {
253
+ const msg = `Error: file not found: ${filePath}`;
254
+ if (jsonOutput) {
255
+ console.error(msg);
256
+ } else {
257
+ console.error(msg);
348
258
  }
349
- // Run verification
350
- let result;
351
- try {
352
- result = await verify(artifact, {
353
- production: parsed.values.production,
354
- skip_network: parsed.values['skip-network'],
355
- trust_root: parsed.values['trust-root'],
356
- }, resolver);
259
+ return 2;
260
+ }
261
+ let artifact;
262
+ try {
263
+ artifact = JSON.parse(rawJson);
264
+ } catch {
265
+ const msg = `Error: invalid JSON in ${filePath}`;
266
+ if (jsonOutput) {
267
+ console.error(msg);
268
+ } else {
269
+ console.error(msg);
357
270
  }
358
- catch (err) {
359
- const msg = `Error: verification failed: ${err.message}`;
360
- if (jsonOutput) {
361
- console.error(msg);
362
- }
363
- else {
364
- console.error(msg);
271
+ return 2;
272
+ }
273
+ let resolver;
274
+ const dbPath = parsed.values["upstream-db"];
275
+ const envelopePaths = parsed.values["upstream-envelope"] ?? [];
276
+ if (dbPath || envelopePaths.length > 0) {
277
+ const envelopes = /* @__PURE__ */ new Map();
278
+ for (const ep of envelopePaths) {
279
+ try {
280
+ const env = JSON.parse(readFileSync(ep, "utf-8"));
281
+ if (typeof env.vpec_id === "string" && typeof env.commitment_root_poseidon2 === "string") {
282
+ envelopes.set(env.vpec_id, env.commitment_root_poseidon2);
283
+ } else {
284
+ for (const [k, v] of Object.entries(env)) {
285
+ if (typeof v === "string") envelopes.set(k, v);
286
+ }
365
287
  }
288
+ } catch (err) {
289
+ const msg = `Error: cannot read upstream envelope ${ep}: ${err.message}`;
290
+ console.error(msg);
366
291
  return 2;
292
+ }
367
293
  }
368
- // Output
294
+ resolver = createUpstreamRootResolver({
295
+ dbPath,
296
+ envelopes: envelopes.size > 0 ? envelopes : void 0
297
+ });
298
+ }
299
+ let result;
300
+ try {
301
+ result = await verify(
302
+ artifact,
303
+ {
304
+ production: parsed.values.production,
305
+ skip_network: parsed.values["skip-network"],
306
+ trust_root: parsed.values["trust-root"]
307
+ },
308
+ resolver
309
+ );
310
+ } catch (err) {
311
+ const msg = `Error: verification failed: ${err.message}`;
369
312
  if (jsonOutput) {
370
- console.log(JSON.stringify(result, null, 2));
371
- }
372
- else if (humanCert) {
373
- console.log(formatHumanCertificate(artifact, result));
313
+ console.error(msg);
314
+ } else {
315
+ console.error(msg);
374
316
  }
375
- else {
376
- printHumanResult(result);
377
- }
378
- if (result.valid && artifact.environment === 'sandbox') {
379
- console.log('\nSANDBOX not audit-acceptable.');
380
- console.log('Change key to pk_live_xxx for production.\n');
381
- return 2;
382
- }
383
- return result.valid ? 0 : 1;
317
+ return 2;
318
+ }
319
+ if (jsonOutput) {
320
+ console.log(JSON.stringify(result, null, 2));
321
+ } else if (humanCert) {
322
+ console.log(formatHumanCertificate(artifact, result));
323
+ } else {
324
+ printHumanResult(result);
325
+ }
326
+ if (result.valid && artifact.environment === "sandbox") {
327
+ console.log("\nSANDBOX \u2014 not audit-acceptable.");
328
+ console.log("Change key to pk_live_xxx for production.\n");
329
+ return 2;
330
+ }
331
+ return result.valid ? 0 : 1;
384
332
  }
385
- // Run if invoked directly
386
- const isDirectRun = process.argv[1] && (process.argv[1].endsWith('/cli.js') ||
387
- process.argv[1].endsWith('/cli.ts'));
333
+ var isDirectRun = process.argv[1] && (process.argv[1].endsWith("/cli.js") || process.argv[1].endsWith("/cli.ts"));
388
334
  if (isDirectRun) {
389
- main().then((code) => process.exit(code));
335
+ main().then((code) => process.exit(code));
390
336
  }
391
- //# sourceMappingURL=cli.js.map
337
+ export {
338
+ main
339
+ };