@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/cli.js
CHANGED
|
@@ -1,391 +1,339 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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-ZADQUKKN.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
|
-
|
|
19
|
+
return PROOF_LEVEL_DISPLAY[level] ?? level;
|
|
31
20
|
}
|
|
32
21
|
function formatDistribution(dist) {
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
console.log(`
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
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(`
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
console.log(` Warning: ${w}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
console.log('');
|
|
188
|
+
}
|
|
189
|
+
console.log("");
|
|
220
190
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
console.log(formatHumanCertificate(artifact, result));
|
|
313
|
+
console.error(msg);
|
|
314
|
+
} else {
|
|
315
|
+
console.error(msg);
|
|
374
316
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
335
|
+
main().then((code) => process.exit(code));
|
|
390
336
|
}
|
|
391
|
-
|
|
337
|
+
export {
|
|
338
|
+
main
|
|
339
|
+
};
|