veriskit 0.3.0 → 0.4.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/CHANGELOG.md +20 -0
- package/README.md +168 -193
- package/dist/index.js +763 -69
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,20 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/version.ts
|
|
12
|
+
import { readFileSync } from "fs";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var pkgUrl, VERSION;
|
|
15
|
+
var init_version = __esm({
|
|
16
|
+
"src/version.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
pkgUrl = new URL("../package.json", import.meta.url);
|
|
19
|
+
VERSION = JSON.parse(
|
|
20
|
+
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
21
|
+
).version;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
11
25
|
// src/util/fs-safe.ts
|
|
12
26
|
import { existsSync } from "fs";
|
|
13
27
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -57,6 +71,7 @@ async function detectProject(root) {
|
|
|
57
71
|
];
|
|
58
72
|
return {
|
|
59
73
|
root,
|
|
74
|
+
name: pkg.name ?? void 0,
|
|
60
75
|
packageManager: detectPackageManager(root),
|
|
61
76
|
frameworks,
|
|
62
77
|
languages,
|
|
@@ -153,7 +168,7 @@ function renderDoctor(project, env) {
|
|
|
153
168
|
const ok = (s) => plain ? s : pc.green(s);
|
|
154
169
|
const dim = (s) => plain ? s : pc.dim(s);
|
|
155
170
|
const lines = [];
|
|
156
|
-
lines.push("
|
|
171
|
+
lines.push("VerisKit doctor");
|
|
157
172
|
lines.push("");
|
|
158
173
|
lines.push(`Package manager ${project.packageManager}`);
|
|
159
174
|
lines.push(`Node ${env.node}`);
|
|
@@ -189,8 +204,80 @@ var init_doctor = __esm({
|
|
|
189
204
|
}
|
|
190
205
|
});
|
|
191
206
|
|
|
207
|
+
// src/evidence/record.ts
|
|
208
|
+
import { createHash } from "crypto";
|
|
209
|
+
import { basename } from "path";
|
|
210
|
+
function canonicalize(value) {
|
|
211
|
+
return JSON.stringify(sortValue(value));
|
|
212
|
+
}
|
|
213
|
+
function sortValue(value) {
|
|
214
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
215
|
+
if (value && typeof value === "object") {
|
|
216
|
+
const src = value;
|
|
217
|
+
const out = {};
|
|
218
|
+
for (const key of Object.keys(src).sort()) {
|
|
219
|
+
if (src[key] !== void 0) out[key] = sortValue(src[key]);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
function sha256(text) {
|
|
226
|
+
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
|
|
227
|
+
}
|
|
228
|
+
function computeDigest(record) {
|
|
229
|
+
const { digest: _omit, ...rest } = record;
|
|
230
|
+
return sha256(canonicalize(rest));
|
|
231
|
+
}
|
|
232
|
+
function buildRecord(run, git, logDigests, toolVersion) {
|
|
233
|
+
const runnerOf = new Map(
|
|
234
|
+
run.project.capabilities.map((c) => [c.id, c.runner])
|
|
235
|
+
);
|
|
236
|
+
const checks = run.results.map((r) => {
|
|
237
|
+
const check = {
|
|
238
|
+
id: r.checkId,
|
|
239
|
+
status: r.status,
|
|
240
|
+
durationMs: r.durationMs,
|
|
241
|
+
summary: r.summary
|
|
242
|
+
};
|
|
243
|
+
const runner = runnerOf.get(r.checkId);
|
|
244
|
+
if (runner) check.runner = runner;
|
|
245
|
+
if (r.counts) check.counts = r.counts;
|
|
246
|
+
const digest = logDigests[r.checkId];
|
|
247
|
+
if (digest) check.logDigest = digest;
|
|
248
|
+
return check;
|
|
249
|
+
});
|
|
250
|
+
const scope = run.scope ? { kind: run.scope.kind, changedCount: run.scope.changedCount } : { kind: "full", changedCount: 0 };
|
|
251
|
+
const base = {
|
|
252
|
+
schema: EVIDENCE_SCHEMA,
|
|
253
|
+
id: run.id,
|
|
254
|
+
startedAt: run.startedAt,
|
|
255
|
+
tool: { name: "veriskit", version: toolVersion },
|
|
256
|
+
git,
|
|
257
|
+
env: run.env,
|
|
258
|
+
project: {
|
|
259
|
+
name: run.project.name ?? basename(run.project.root),
|
|
260
|
+
packageManager: run.project.packageManager,
|
|
261
|
+
frameworks: run.project.frameworks,
|
|
262
|
+
languages: run.project.languages
|
|
263
|
+
},
|
|
264
|
+
scope,
|
|
265
|
+
checks,
|
|
266
|
+
verdict: run.verdict
|
|
267
|
+
};
|
|
268
|
+
return { ...base, digest: computeDigest(base) };
|
|
269
|
+
}
|
|
270
|
+
var EVIDENCE_SCHEMA;
|
|
271
|
+
var init_record = __esm({
|
|
272
|
+
"src/evidence/record.ts"() {
|
|
273
|
+
"use strict";
|
|
274
|
+
EVIDENCE_SCHEMA = "veriskit/evidence@1";
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
192
278
|
// src/evidence/store.ts
|
|
193
|
-
import {
|
|
279
|
+
import { readdirSync } from "fs";
|
|
280
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
194
281
|
import { join as join2 } from "path";
|
|
195
282
|
function newRunId() {
|
|
196
283
|
counter += 1;
|
|
@@ -207,11 +294,6 @@ async function writeLog(runDir, checkId, content) {
|
|
|
207
294
|
await writeFile2(ref, content, "utf8");
|
|
208
295
|
return ref;
|
|
209
296
|
}
|
|
210
|
-
async function writeMetadata(runDir, run) {
|
|
211
|
-
const ref = join2(runDir, "metadata.json");
|
|
212
|
-
await writeFile2(ref, JSON.stringify(run, null, 2), "utf8");
|
|
213
|
-
return ref;
|
|
214
|
-
}
|
|
215
297
|
async function writeReport(root, id, markdown) {
|
|
216
298
|
const dir = join2(root, ".veris", "reports");
|
|
217
299
|
await ensureDir(dir);
|
|
@@ -219,11 +301,54 @@ async function writeReport(root, id, markdown) {
|
|
|
219
301
|
await writeFile2(ref, markdown, "utf8");
|
|
220
302
|
return ref;
|
|
221
303
|
}
|
|
304
|
+
async function writeEvidence(runDir, record) {
|
|
305
|
+
const ref = join2(runDir, "evidence.json");
|
|
306
|
+
await writeFile2(ref, `${JSON.stringify(record, null, 2)}
|
|
307
|
+
`, "utf8");
|
|
308
|
+
return ref;
|
|
309
|
+
}
|
|
310
|
+
async function digestLogs(run) {
|
|
311
|
+
const out = {};
|
|
312
|
+
for (const r of run.results) {
|
|
313
|
+
if (!r.logRef) continue;
|
|
314
|
+
try {
|
|
315
|
+
out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
async function readRunLogs(runDir, checkIds) {
|
|
322
|
+
const out = {};
|
|
323
|
+
for (const id of checkIds) {
|
|
324
|
+
try {
|
|
325
|
+
out[id] = await readFile2(join2(runDir, `${id}.log`), "utf8");
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
function latestRunDir(root) {
|
|
332
|
+
const runs = join2(root, ".veris", "runs");
|
|
333
|
+
try {
|
|
334
|
+
const dirs = readdirSync(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name).sort();
|
|
335
|
+
const latest = dirs.at(-1);
|
|
336
|
+
return latest ? join2(runs, latest) : null;
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function ensureEvidenceDir(root) {
|
|
342
|
+
const dir = join2(root, ".veris", "evidence");
|
|
343
|
+
await ensureDir(dir);
|
|
344
|
+
return dir;
|
|
345
|
+
}
|
|
222
346
|
var counter;
|
|
223
347
|
var init_store = __esm({
|
|
224
348
|
"src/evidence/store.ts"() {
|
|
225
349
|
"use strict";
|
|
226
350
|
init_fs_safe();
|
|
351
|
+
init_record();
|
|
227
352
|
counter = 0;
|
|
228
353
|
}
|
|
229
354
|
});
|
|
@@ -611,18 +736,18 @@ function glyph(status, plain) {
|
|
|
611
736
|
function secs(ms) {
|
|
612
737
|
return ms === 0 ? "" : `${(ms / 1e3).toFixed(1)}s`;
|
|
613
738
|
}
|
|
614
|
-
function renderRun(run) {
|
|
739
|
+
function renderRun(run, record) {
|
|
615
740
|
const plain = isPlain();
|
|
616
741
|
const bold = (s) => plain ? s : pc2.bold(s);
|
|
617
742
|
const dim = (s) => plain ? s : pc2.dim(s);
|
|
618
743
|
const lines = [];
|
|
619
744
|
const scoped = run.scope?.kind;
|
|
620
745
|
if (scoped) {
|
|
621
|
-
lines.push(bold(`
|
|
746
|
+
lines.push(bold(`VerisKit \u2014 ${scoped}`));
|
|
622
747
|
lines.push("");
|
|
623
748
|
lines.push(`Scope ${run.scope?.changedCount ?? 0} changed file(s)`);
|
|
624
749
|
} else {
|
|
625
|
-
lines.push(bold("
|
|
750
|
+
lines.push(bold("VerisKit"));
|
|
626
751
|
lines.push("");
|
|
627
752
|
lines.push(
|
|
628
753
|
`Project ${run.project.root.split("/").pop() ?? run.project.root}`
|
|
@@ -646,6 +771,13 @@ function renderRun(run) {
|
|
|
646
771
|
const label = run.verdict.state === "verified" ? scoped ? "\u2713 Affected checks passed" : "\u2713 Verified" : run.verdict.state === "failed" ? scoped ? "\u2717 Affected checks failed" : "\u2717 Failed" : scoped ? "\u25B2 Affected: partial" : "\u25B2 Partial";
|
|
647
772
|
const color = run.verdict.state === "verified" ? pc2.green : run.verdict.state === "failed" ? pc2.red : pc2.yellow;
|
|
648
773
|
lines.push(` ${plain ? label : color(label)}`);
|
|
774
|
+
if (record?.git) {
|
|
775
|
+
const g = record.git;
|
|
776
|
+
lines.push("");
|
|
777
|
+
lines.push(
|
|
778
|
+
`Commit ${g.commit.slice(0, 7)} ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
649
781
|
if (run.reportRef) {
|
|
650
782
|
lines.push("");
|
|
651
783
|
lines.push("Report");
|
|
@@ -701,7 +833,7 @@ async function runInit(root) {
|
|
|
701
833
|
await writeIfAbsent(join4(dir, ".gitignore"), GITIGNORE);
|
|
702
834
|
process.stdout.write(
|
|
703
835
|
wroteConfig ? `Veris initialized. Detected checks: ${defaultChecks.join(", ") || "none"}.
|
|
704
|
-
` : "
|
|
836
|
+
` : "VerisKit already initialized (.veris/config.json exists). Nothing overwritten.\n"
|
|
705
837
|
);
|
|
706
838
|
return 0;
|
|
707
839
|
}
|
|
@@ -711,7 +843,15 @@ var init_init = __esm({
|
|
|
711
843
|
"use strict";
|
|
712
844
|
init_detect();
|
|
713
845
|
init_fs_safe();
|
|
714
|
-
GITIGNORE = [
|
|
846
|
+
GITIGNORE = [
|
|
847
|
+
"runs/",
|
|
848
|
+
"reports/",
|
|
849
|
+
"cache/",
|
|
850
|
+
"graph.json",
|
|
851
|
+
"evidence/",
|
|
852
|
+
"keys/",
|
|
853
|
+
""
|
|
854
|
+
].join("\n");
|
|
715
855
|
}
|
|
716
856
|
});
|
|
717
857
|
|
|
@@ -727,15 +867,60 @@ var init_load = __esm({
|
|
|
727
867
|
}
|
|
728
868
|
});
|
|
729
869
|
|
|
870
|
+
// src/git/changes.ts
|
|
871
|
+
function collect(set, out) {
|
|
872
|
+
for (const line of out.split("\n")) {
|
|
873
|
+
const f = line.trim();
|
|
874
|
+
if (f) set.add(f);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function changedFiles(root, opts = {}) {
|
|
878
|
+
const base = opts.base ?? null;
|
|
879
|
+
const files = /* @__PURE__ */ new Set();
|
|
880
|
+
const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
|
|
881
|
+
const diff = await exec("git", diffArgs, { cwd: root });
|
|
882
|
+
if (diff.code === 0) collect(files, diff.stdout);
|
|
883
|
+
const untracked = await exec(
|
|
884
|
+
"git",
|
|
885
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
886
|
+
{ cwd: root }
|
|
887
|
+
);
|
|
888
|
+
if (untracked.code === 0) collect(files, untracked.stdout);
|
|
889
|
+
return { files: [...files].sort(), base };
|
|
890
|
+
}
|
|
891
|
+
async function gitAnchor(root) {
|
|
892
|
+
const head = await exec("git", ["rev-parse", "HEAD"], { cwd: root });
|
|
893
|
+
if (head.code !== 0) return null;
|
|
894
|
+
const commit = head.stdout.trim();
|
|
895
|
+
const branchRes = await exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
896
|
+
cwd: root
|
|
897
|
+
});
|
|
898
|
+
const branch = branchRes.code === 0 ? branchRes.stdout.trim() : "HEAD";
|
|
899
|
+
const status = await exec("git", ["status", "--porcelain"], { cwd: root });
|
|
900
|
+
const lines = status.code === 0 ? status.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
|
|
901
|
+
return {
|
|
902
|
+
commit,
|
|
903
|
+
branch,
|
|
904
|
+
dirty: lines.length > 0,
|
|
905
|
+
changedFiles: lines.length
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
var init_changes = __esm({
|
|
909
|
+
"src/git/changes.ts"() {
|
|
910
|
+
"use strict";
|
|
911
|
+
init_exec();
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
|
|
730
915
|
// src/reporters/markdown.ts
|
|
731
916
|
import { relative } from "path";
|
|
732
917
|
function cell(value) {
|
|
733
918
|
return value.replace(/\|/g, "\\|");
|
|
734
919
|
}
|
|
735
|
-
function renderMarkdown(run) {
|
|
920
|
+
function renderMarkdown(run, record) {
|
|
736
921
|
const root = run.project.root;
|
|
737
922
|
const lines = [];
|
|
738
|
-
lines.push("#
|
|
923
|
+
lines.push("# VerisKit Verification Report");
|
|
739
924
|
if (run.scope?.kind) {
|
|
740
925
|
lines.push("");
|
|
741
926
|
lines.push(
|
|
@@ -748,6 +933,13 @@ function renderMarkdown(run) {
|
|
|
748
933
|
lines.push(
|
|
749
934
|
`**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
|
|
750
935
|
);
|
|
936
|
+
if (record?.git) {
|
|
937
|
+
const g = record.git;
|
|
938
|
+
const tree = g.dirty ? `tree dirty, ${g.changedFiles} uncommitted file(s)` : "tree clean";
|
|
939
|
+
lines.push(`**Commit:** ${g.commit.slice(0, 7)} (${g.branch}) \xB7 ${tree}`);
|
|
940
|
+
} else if (record) {
|
|
941
|
+
lines.push("**Commit:** no git anchor available");
|
|
942
|
+
}
|
|
751
943
|
lines.push("");
|
|
752
944
|
lines.push("## Checks");
|
|
753
945
|
lines.push("");
|
|
@@ -779,9 +971,16 @@ function renderMarkdown(run) {
|
|
|
779
971
|
lines.push("```");
|
|
780
972
|
}
|
|
781
973
|
}
|
|
974
|
+
if (record) {
|
|
975
|
+
lines.push("");
|
|
976
|
+
lines.push(`**Evidence digest:** \`${record.digest}\``);
|
|
977
|
+
lines.push(
|
|
978
|
+
"_Integrity digest over the canonical record. Detects edits and corruption; it is not forgery-proof on its own. Publish the digest separately or sign it to prove authorship._"
|
|
979
|
+
);
|
|
980
|
+
}
|
|
782
981
|
lines.push("");
|
|
783
982
|
lines.push(
|
|
784
|
-
"_Generated by
|
|
983
|
+
"_Generated by VerisKit. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
|
|
785
984
|
);
|
|
786
985
|
lines.push("");
|
|
787
986
|
return lines.join("\n");
|
|
@@ -808,11 +1007,18 @@ async function runVerify(root, opts = {}) {
|
|
|
808
1007
|
const config = await loadConfig(root);
|
|
809
1008
|
const checks = config?.checks?.length ? config.checks : DEFAULT_CHECKS;
|
|
810
1009
|
const run = await runChecks(project, checks, root);
|
|
811
|
-
const
|
|
1010
|
+
const git = await gitAnchor(root);
|
|
1011
|
+
const logDigests = await digestLogs(run);
|
|
1012
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1013
|
+
const reportRef = await writeReport(
|
|
1014
|
+
root,
|
|
1015
|
+
run.id,
|
|
1016
|
+
renderMarkdown(run, record)
|
|
1017
|
+
);
|
|
812
1018
|
run.reportRef = reportRef;
|
|
813
1019
|
const runDir = await createRunDir(root, run.id);
|
|
814
|
-
await
|
|
815
|
-
process.stdout.write(`${renderRun(run)}
|
|
1020
|
+
await writeEvidence(runDir, record);
|
|
1021
|
+
process.stdout.write(`${renderRun(run, record)}
|
|
816
1022
|
`);
|
|
817
1023
|
return verdictExitCode(run.verdict, opts);
|
|
818
1024
|
}
|
|
@@ -824,9 +1030,12 @@ var init_verify = __esm({
|
|
|
824
1030
|
init_load();
|
|
825
1031
|
init_orchestrator();
|
|
826
1032
|
init_verdict();
|
|
1033
|
+
init_record();
|
|
827
1034
|
init_store();
|
|
1035
|
+
init_changes();
|
|
828
1036
|
init_markdown();
|
|
829
1037
|
init_terminal();
|
|
1038
|
+
init_version();
|
|
830
1039
|
DEFAULT_CHECKS = ["types", "lint", "unit"];
|
|
831
1040
|
}
|
|
832
1041
|
});
|
|
@@ -836,13 +1045,13 @@ var report_exports = {};
|
|
|
836
1045
|
__export(report_exports, {
|
|
837
1046
|
runReport: () => runReport
|
|
838
1047
|
});
|
|
839
|
-
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
1048
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
840
1049
|
import { join as join6 } from "path";
|
|
841
1050
|
async function runReport(root) {
|
|
842
1051
|
const dir = join6(root, ".veris", "reports");
|
|
843
1052
|
let files;
|
|
844
1053
|
try {
|
|
845
|
-
files =
|
|
1054
|
+
files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
|
|
846
1055
|
} catch {
|
|
847
1056
|
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
848
1057
|
return 0;
|
|
@@ -915,36 +1124,8 @@ var init_gate = __esm({
|
|
|
915
1124
|
}
|
|
916
1125
|
});
|
|
917
1126
|
|
|
918
|
-
// src/git/changes.ts
|
|
919
|
-
function collect(set, out) {
|
|
920
|
-
for (const line of out.split("\n")) {
|
|
921
|
-
const f = line.trim();
|
|
922
|
-
if (f) set.add(f);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
async function changedFiles(root, opts = {}) {
|
|
926
|
-
const base = opts.base ?? null;
|
|
927
|
-
const files = /* @__PURE__ */ new Set();
|
|
928
|
-
const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
|
|
929
|
-
const diff = await exec("git", diffArgs, { cwd: root });
|
|
930
|
-
if (diff.code === 0) collect(files, diff.stdout);
|
|
931
|
-
const untracked = await exec(
|
|
932
|
-
"git",
|
|
933
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
934
|
-
{ cwd: root }
|
|
935
|
-
);
|
|
936
|
-
if (untracked.code === 0) collect(files, untracked.stdout);
|
|
937
|
-
return { files: [...files].sort(), base };
|
|
938
|
-
}
|
|
939
|
-
var init_changes = __esm({
|
|
940
|
-
"src/git/changes.ts"() {
|
|
941
|
-
"use strict";
|
|
942
|
-
init_exec();
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
|
|
946
1127
|
// src/project-graph/discover.ts
|
|
947
|
-
import { readdirSync as
|
|
1128
|
+
import { readdirSync as readdirSync3, statSync } from "fs";
|
|
948
1129
|
import { join as join7, relative as relative2, sep } from "path";
|
|
949
1130
|
function toPosix(p) {
|
|
950
1131
|
return sep === "/" ? p : p.split(sep).join("/");
|
|
@@ -959,7 +1140,7 @@ function discoverFiles(root) {
|
|
|
959
1140
|
const walk = (dir) => {
|
|
960
1141
|
let entries;
|
|
961
1142
|
try {
|
|
962
|
-
entries =
|
|
1143
|
+
entries = readdirSync3(dir);
|
|
963
1144
|
} catch {
|
|
964
1145
|
return;
|
|
965
1146
|
}
|
|
@@ -1322,11 +1503,18 @@ async function runAffected(root, opts = {}) {
|
|
|
1322
1503
|
});
|
|
1323
1504
|
}
|
|
1324
1505
|
}
|
|
1325
|
-
const
|
|
1506
|
+
const git = await gitAnchor(root);
|
|
1507
|
+
const logDigests = await digestLogs(run);
|
|
1508
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1509
|
+
const reportRef = await writeReport(
|
|
1510
|
+
root,
|
|
1511
|
+
run.id,
|
|
1512
|
+
renderMarkdown(run, record)
|
|
1513
|
+
);
|
|
1326
1514
|
run.reportRef = reportRef;
|
|
1327
1515
|
const runDir = await createRunDir(root, run.id);
|
|
1328
|
-
await
|
|
1329
|
-
process.stdout.write(`${renderRun(run)}
|
|
1516
|
+
await writeEvidence(runDir, record);
|
|
1517
|
+
process.stdout.write(`${renderRun(run, record)}
|
|
1330
1518
|
`);
|
|
1331
1519
|
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1332
1520
|
`);
|
|
@@ -1339,23 +1527,25 @@ var init_affected = __esm({
|
|
|
1339
1527
|
init_detect();
|
|
1340
1528
|
init_orchestrator();
|
|
1341
1529
|
init_verdict();
|
|
1530
|
+
init_record();
|
|
1342
1531
|
init_store();
|
|
1343
1532
|
init_changes();
|
|
1344
1533
|
init_markdown();
|
|
1345
1534
|
init_terminal();
|
|
1535
|
+
init_version();
|
|
1346
1536
|
}
|
|
1347
1537
|
});
|
|
1348
1538
|
|
|
1349
1539
|
// src/watch/watcher.ts
|
|
1350
1540
|
import {
|
|
1351
1541
|
watch as fsWatch,
|
|
1352
|
-
readdirSync as
|
|
1542
|
+
readdirSync as readdirSync4,
|
|
1353
1543
|
statSync as statSync3
|
|
1354
1544
|
} from "fs";
|
|
1355
|
-
import { basename, join as join10, relative as relative5 } from "path";
|
|
1545
|
+
import { basename as basename2, join as join10, relative as relative5 } from "path";
|
|
1356
1546
|
function watch(root, opts, onBatch) {
|
|
1357
1547
|
const debounceMs = opts.debounceMs ?? 150;
|
|
1358
|
-
const rootBase =
|
|
1548
|
+
const rootBase = basename2(root);
|
|
1359
1549
|
let pending = /* @__PURE__ */ new Set();
|
|
1360
1550
|
let timer = null;
|
|
1361
1551
|
const flush = () => {
|
|
@@ -1406,7 +1596,7 @@ function scanMtimes(root) {
|
|
|
1406
1596
|
const walk = (dir) => {
|
|
1407
1597
|
let entries;
|
|
1408
1598
|
try {
|
|
1409
|
-
entries =
|
|
1599
|
+
entries = readdirSync4(dir);
|
|
1410
1600
|
} catch {
|
|
1411
1601
|
return;
|
|
1412
1602
|
}
|
|
@@ -1582,7 +1772,7 @@ function renderScan(graph, analysis) {
|
|
|
1582
1772
|
const dim = (s) => plain ? s : pc3.dim(s);
|
|
1583
1773
|
const warn = (s) => plain ? s : pc3.yellow(s);
|
|
1584
1774
|
const lines = [];
|
|
1585
|
-
lines.push(bold("
|
|
1775
|
+
lines.push(bold("VerisKit \u2014 scan"));
|
|
1586
1776
|
lines.push("");
|
|
1587
1777
|
lines.push(
|
|
1588
1778
|
`Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
|
|
@@ -1644,7 +1834,7 @@ function renderPlan(project, graph, analysis, changed) {
|
|
|
1644
1834
|
const dim = (s) => plain ? s : pc4.dim(s);
|
|
1645
1835
|
const warn = (s) => plain ? s : pc4.yellow(s);
|
|
1646
1836
|
const lines = [];
|
|
1647
|
-
lines.push(bold("
|
|
1837
|
+
lines.push(bold("VerisKit \u2014 plan"));
|
|
1648
1838
|
lines.push(dim(`(graph via ${graph.resolver})`));
|
|
1649
1839
|
lines.push("");
|
|
1650
1840
|
lines.push("Test these first (high impact, untested)");
|
|
@@ -1708,21 +1898,479 @@ var init_plan = __esm({
|
|
|
1708
1898
|
}
|
|
1709
1899
|
});
|
|
1710
1900
|
|
|
1901
|
+
// src/evidence/signing.ts
|
|
1902
|
+
import {
|
|
1903
|
+
createHash as createHash2,
|
|
1904
|
+
createPrivateKey,
|
|
1905
|
+
createPublicKey,
|
|
1906
|
+
sign as cryptoSign,
|
|
1907
|
+
verify as cryptoVerify,
|
|
1908
|
+
generateKeyPairSync
|
|
1909
|
+
} from "crypto";
|
|
1910
|
+
function derOf(publicKey) {
|
|
1911
|
+
return publicKey.export({ type: "spki", format: "der" });
|
|
1912
|
+
}
|
|
1913
|
+
function keyId(publicKey) {
|
|
1914
|
+
const key = typeof publicKey === "string" ? createPublicKey(publicKey) : publicKey;
|
|
1915
|
+
return createHash2("sha256").update(derOf(key)).digest("hex").slice(0, 8);
|
|
1916
|
+
}
|
|
1917
|
+
function signatureKeyId(sig) {
|
|
1918
|
+
const key = createPublicKey({
|
|
1919
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
1920
|
+
format: "der",
|
|
1921
|
+
type: "spki"
|
|
1922
|
+
});
|
|
1923
|
+
return keyId(key);
|
|
1924
|
+
}
|
|
1925
|
+
function generateKeyPair() {
|
|
1926
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
1927
|
+
return {
|
|
1928
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
|
|
1929
|
+
privateKeyPem: privateKey.export({
|
|
1930
|
+
type: "pkcs8",
|
|
1931
|
+
format: "pem"
|
|
1932
|
+
}),
|
|
1933
|
+
keyId: keyId(publicKey)
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function signDigest(digest, privateKeyPem) {
|
|
1937
|
+
const privateKey = createPrivateKey(privateKeyPem);
|
|
1938
|
+
const publicKey = createPublicKey(
|
|
1939
|
+
privateKey
|
|
1940
|
+
);
|
|
1941
|
+
const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
|
|
1942
|
+
return {
|
|
1943
|
+
schema: SIGNATURE_SCHEMA,
|
|
1944
|
+
alg: "ed25519",
|
|
1945
|
+
keyId: keyId(publicKey),
|
|
1946
|
+
publicKey: derOf(publicKey).toString("base64"),
|
|
1947
|
+
digest,
|
|
1948
|
+
signature: sig.toString("base64"),
|
|
1949
|
+
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
function verifySignature(sig) {
|
|
1953
|
+
try {
|
|
1954
|
+
const publicKey = createPublicKey({
|
|
1955
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
1956
|
+
format: "der",
|
|
1957
|
+
type: "spki"
|
|
1958
|
+
});
|
|
1959
|
+
return cryptoVerify(
|
|
1960
|
+
null,
|
|
1961
|
+
Buffer.from(sig.digest, "utf8"),
|
|
1962
|
+
publicKey,
|
|
1963
|
+
Buffer.from(sig.signature, "base64")
|
|
1964
|
+
);
|
|
1965
|
+
} catch {
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
var SIGNATURE_SCHEMA;
|
|
1970
|
+
var init_signing = __esm({
|
|
1971
|
+
"src/evidence/signing.ts"() {
|
|
1972
|
+
"use strict";
|
|
1973
|
+
SIGNATURE_SCHEMA = "veriskit/signature@1";
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// src/evidence/verify-evidence.ts
|
|
1978
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1979
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1980
|
+
function verifyRecord(record) {
|
|
1981
|
+
const recomputed = computeDigest(
|
|
1982
|
+
record
|
|
1983
|
+
);
|
|
1984
|
+
const ok = recomputed === record.digest;
|
|
1985
|
+
const checks = [
|
|
1986
|
+
{
|
|
1987
|
+
name: "record digest",
|
|
1988
|
+
ok,
|
|
1989
|
+
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
1990
|
+
}
|
|
1991
|
+
];
|
|
1992
|
+
return { ok, kind: "record", record, checks, signed: false };
|
|
1993
|
+
}
|
|
1994
|
+
function signatureChecks(recordDigest, sig, opts = {}) {
|
|
1995
|
+
const checks = [];
|
|
1996
|
+
const kid = signatureKeyId(sig);
|
|
1997
|
+
const validSig = verifySignature(sig) && sig.digest === recordDigest;
|
|
1998
|
+
checks.push({
|
|
1999
|
+
name: "signature",
|
|
2000
|
+
ok: validSig,
|
|
2001
|
+
detail: validSig ? `valid ed25519 signature by key ${kid}` : "signature does not match this record"
|
|
2002
|
+
});
|
|
2003
|
+
if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
2004
|
+
let matches = false;
|
|
2005
|
+
if (opts.expectedPubKeyPem) {
|
|
2006
|
+
try {
|
|
2007
|
+
matches = keyId(opts.expectedPubKeyPem) === kid;
|
|
2008
|
+
} catch {
|
|
2009
|
+
matches = false;
|
|
2010
|
+
}
|
|
2011
|
+
} else if (opts.expectedKeyId) {
|
|
2012
|
+
matches = opts.expectedKeyId.toLowerCase() === kid.toLowerCase();
|
|
2013
|
+
}
|
|
2014
|
+
checks.push({
|
|
2015
|
+
name: "signer",
|
|
2016
|
+
ok: matches,
|
|
2017
|
+
detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
return checks;
|
|
2021
|
+
}
|
|
2022
|
+
async function verifyEvidenceFile(path, opts = {}) {
|
|
2023
|
+
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
2024
|
+
if (parsed?.schema === "veriskit/bundle@1") {
|
|
2025
|
+
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
2026
|
+
return verifyBundle2(parsed, opts);
|
|
2027
|
+
}
|
|
2028
|
+
const result = verifyRecord(parsed);
|
|
2029
|
+
const assertedSigner = Boolean(
|
|
2030
|
+
opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
|
|
2031
|
+
);
|
|
2032
|
+
const sigPath = opts.sigPath ?? `${path}.sig`;
|
|
2033
|
+
if (existsSync5(sigPath)) {
|
|
2034
|
+
const sig = JSON.parse(await readFile3(sigPath, "utf8"));
|
|
2035
|
+
result.checks.push(
|
|
2036
|
+
...signatureChecks(result.record.digest, sig, {
|
|
2037
|
+
expectedKeyId: opts.expectedKeyId,
|
|
2038
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2039
|
+
})
|
|
2040
|
+
);
|
|
2041
|
+
result.signed = true;
|
|
2042
|
+
} else if (assertedSigner) {
|
|
2043
|
+
result.checks.push({
|
|
2044
|
+
name: "signature",
|
|
2045
|
+
ok: false,
|
|
2046
|
+
detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
result.ok = result.checks.every((c) => c.ok);
|
|
2050
|
+
return result;
|
|
2051
|
+
}
|
|
2052
|
+
var init_verify_evidence = __esm({
|
|
2053
|
+
"src/evidence/verify-evidence.ts"() {
|
|
2054
|
+
"use strict";
|
|
2055
|
+
init_record();
|
|
2056
|
+
init_signing();
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
// src/evidence/bundle.ts
|
|
2061
|
+
var bundle_exports = {};
|
|
2062
|
+
__export(bundle_exports, {
|
|
2063
|
+
BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
|
|
2064
|
+
buildBundle: () => buildBundle,
|
|
2065
|
+
verifyBundle: () => verifyBundle
|
|
2066
|
+
});
|
|
2067
|
+
function buildBundle(record, report, logs, signature) {
|
|
2068
|
+
const manifest = {
|
|
2069
|
+
record: record.digest,
|
|
2070
|
+
report: sha256(report),
|
|
2071
|
+
logs: Object.fromEntries(
|
|
2072
|
+
Object.entries(logs).map(([k, v]) => [k, sha256(v)])
|
|
2073
|
+
)
|
|
2074
|
+
};
|
|
2075
|
+
const base = signature ? { schema: BUNDLE_SCHEMA, record, report, logs, manifest, signature } : { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
|
|
2076
|
+
return { ...base, bundleDigest: sha256(canonicalize(base)) };
|
|
2077
|
+
}
|
|
2078
|
+
function verifyBundle(bundle, opts = {}) {
|
|
2079
|
+
const checks = [];
|
|
2080
|
+
const recordResult = verifyRecord(bundle.record);
|
|
2081
|
+
checks.push(...recordResult.checks);
|
|
2082
|
+
const reportDigest = sha256(bundle.report);
|
|
2083
|
+
checks.push({
|
|
2084
|
+
name: "report",
|
|
2085
|
+
ok: reportDigest === bundle.manifest.report,
|
|
2086
|
+
detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
|
|
2087
|
+
});
|
|
2088
|
+
for (const [id, body] of Object.entries(bundle.logs)) {
|
|
2089
|
+
const d = sha256(body);
|
|
2090
|
+
checks.push({
|
|
2091
|
+
name: `log:${id}`,
|
|
2092
|
+
ok: d === bundle.manifest.logs[id],
|
|
2093
|
+
detail: d === bundle.manifest.logs[id] ? "matches manifest" : "mismatch"
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
const { bundleDigest: _omit, ...rest } = bundle;
|
|
2097
|
+
const recomputed = sha256(canonicalize(rest));
|
|
2098
|
+
checks.push({
|
|
2099
|
+
name: "bundle digest",
|
|
2100
|
+
ok: recomputed === bundle.bundleDigest,
|
|
2101
|
+
detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
|
|
2102
|
+
});
|
|
2103
|
+
let signed = false;
|
|
2104
|
+
if (bundle.signature) {
|
|
2105
|
+
signed = true;
|
|
2106
|
+
checks.push(
|
|
2107
|
+
...signatureChecks(bundle.record.digest, bundle.signature, {
|
|
2108
|
+
expectedKeyId: opts.expectedKeyId,
|
|
2109
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2110
|
+
})
|
|
2111
|
+
);
|
|
2112
|
+
} else if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
2113
|
+
checks.push({
|
|
2114
|
+
name: "signature",
|
|
2115
|
+
ok: false,
|
|
2116
|
+
detail: "a signature was required (--pubkey/--key-id) but the bundle is unsigned"
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
return {
|
|
2120
|
+
ok: checks.every((c) => c.ok),
|
|
2121
|
+
kind: "bundle",
|
|
2122
|
+
record: bundle.record,
|
|
2123
|
+
checks,
|
|
2124
|
+
signed
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
var BUNDLE_SCHEMA;
|
|
2128
|
+
var init_bundle = __esm({
|
|
2129
|
+
"src/evidence/bundle.ts"() {
|
|
2130
|
+
"use strict";
|
|
2131
|
+
init_record();
|
|
2132
|
+
init_verify_evidence();
|
|
2133
|
+
BUNDLE_SCHEMA = "veriskit/bundle@1";
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
// src/cli/commands/evidence.ts
|
|
2138
|
+
var evidence_exports = {};
|
|
2139
|
+
__export(evidence_exports, {
|
|
2140
|
+
runEvidenceBundle: () => runEvidenceBundle,
|
|
2141
|
+
runEvidenceKeygen: () => runEvidenceKeygen,
|
|
2142
|
+
runEvidenceShow: () => runEvidenceShow,
|
|
2143
|
+
runEvidenceSign: () => runEvidenceSign,
|
|
2144
|
+
runEvidenceVerify: () => runEvidenceVerify
|
|
2145
|
+
});
|
|
2146
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync } from "fs";
|
|
2147
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
2148
|
+
import { basename as basename3, dirname as dirname2, join as join12 } from "path";
|
|
2149
|
+
import pc5 from "picocolors";
|
|
2150
|
+
async function runEvidenceVerify(path, opts = {}) {
|
|
2151
|
+
let expectedPubKeyPem;
|
|
2152
|
+
if (opts.pubkey) {
|
|
2153
|
+
try {
|
|
2154
|
+
expectedPubKeyPem = readFileSync5(opts.pubkey, "utf8");
|
|
2155
|
+
} catch {
|
|
2156
|
+
process.stderr.write(`veris: cannot read public key at ${opts.pubkey}
|
|
2157
|
+
`);
|
|
2158
|
+
return 1;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
let result;
|
|
2162
|
+
try {
|
|
2163
|
+
result = await verifyEvidenceFile(path, {
|
|
2164
|
+
sigPath: opts.sig,
|
|
2165
|
+
expectedKeyId: opts.keyId,
|
|
2166
|
+
expectedPubKeyPem
|
|
2167
|
+
});
|
|
2168
|
+
} catch (err) {
|
|
2169
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2170
|
+
process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
|
|
2171
|
+
`);
|
|
2172
|
+
return 1;
|
|
2173
|
+
}
|
|
2174
|
+
const plain = isPlain();
|
|
2175
|
+
const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
|
|
2176
|
+
for (const check of result.checks) {
|
|
2177
|
+
process.stdout.write(
|
|
2178
|
+
` ${mark(check.ok)} ${check.name}: ${check.detail}
|
|
2179
|
+
`
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
const g = result.record.git;
|
|
2183
|
+
const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
|
|
2184
|
+
process.stdout.write(
|
|
2185
|
+
`
|
|
2186
|
+
${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
|
|
2187
|
+
`
|
|
2188
|
+
);
|
|
2189
|
+
process.stdout.write(`
|
|
2190
|
+
${HONESTY}
|
|
2191
|
+
`);
|
|
2192
|
+
if (result.signed && !opts.pubkey && !opts.keyId) {
|
|
2193
|
+
process.stdout.write(
|
|
2194
|
+
"\nThis record is signed. Confirm you trust the signing key by comparing its\nkey id above to one you already trust, or re-run with --pubkey / --key-id.\n"
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
return result.ok ? 0 : 1;
|
|
2198
|
+
}
|
|
2199
|
+
async function runEvidenceKeygen(root, opts = {}) {
|
|
2200
|
+
const out = opts.out ?? join12(root, ".veris", "keys", "veriskit-signing.key");
|
|
2201
|
+
const pub = `${out}.pub`;
|
|
2202
|
+
if (existsSync6(out) || existsSync6(pub)) {
|
|
2203
|
+
process.stderr.write(
|
|
2204
|
+
`veris: a key already exists at ${out}. Refusing to overwrite it.
|
|
2205
|
+
`
|
|
2206
|
+
);
|
|
2207
|
+
return 1;
|
|
2208
|
+
}
|
|
2209
|
+
const { mkdir: mkdir2 } = await import("fs/promises");
|
|
2210
|
+
await mkdir2(dirname2(out), { recursive: true });
|
|
2211
|
+
const kp = generateKeyPair();
|
|
2212
|
+
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2213
|
+
chmodSync(out, 384);
|
|
2214
|
+
writeFileSync(pub, kp.publicKeyPem, "utf8");
|
|
2215
|
+
process.stdout.write(
|
|
2216
|
+
[
|
|
2217
|
+
`Wrote signing key ${out} (keep this secret; do not commit it)`,
|
|
2218
|
+
`Wrote public key ${pub}`,
|
|
2219
|
+
`Key id ${kp.keyId}`,
|
|
2220
|
+
""
|
|
2221
|
+
].join("\n")
|
|
2222
|
+
);
|
|
2223
|
+
return 0;
|
|
2224
|
+
}
|
|
2225
|
+
async function runEvidenceSign(evidencePath, opts = {}) {
|
|
2226
|
+
let privateKeyPem;
|
|
2227
|
+
if (process.env.VERISKIT_SIGNING_KEY) {
|
|
2228
|
+
privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
|
|
2229
|
+
} else if (opts.key) {
|
|
2230
|
+
try {
|
|
2231
|
+
privateKeyPem = readFileSync5(opts.key, "utf8");
|
|
2232
|
+
} catch {
|
|
2233
|
+
process.stderr.write(`veris: cannot read signing key at ${opts.key}
|
|
2234
|
+
`);
|
|
2235
|
+
return 1;
|
|
2236
|
+
}
|
|
2237
|
+
} else {
|
|
2238
|
+
process.stderr.write(
|
|
2239
|
+
"veris: no signing key. Pass --key <path> or set VERISKIT_SIGNING_KEY.\n"
|
|
2240
|
+
);
|
|
2241
|
+
return 1;
|
|
2242
|
+
}
|
|
2243
|
+
let record;
|
|
2244
|
+
try {
|
|
2245
|
+
record = JSON.parse(readFileSync5(evidencePath, "utf8"));
|
|
2246
|
+
} catch {
|
|
2247
|
+
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2248
|
+
`);
|
|
2249
|
+
return 1;
|
|
2250
|
+
}
|
|
2251
|
+
const recomputed = computeDigest(
|
|
2252
|
+
record
|
|
2253
|
+
);
|
|
2254
|
+
if (recomputed !== record.digest) {
|
|
2255
|
+
process.stderr.write(
|
|
2256
|
+
"veris: refusing to sign, the record digest does not match its contents.\n"
|
|
2257
|
+
);
|
|
2258
|
+
return 1;
|
|
2259
|
+
}
|
|
2260
|
+
let sig;
|
|
2261
|
+
try {
|
|
2262
|
+
sig = signDigest(record.digest, privateKeyPem);
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2265
|
+
process.stderr.write(`veris: signing failed: ${msg}
|
|
2266
|
+
`);
|
|
2267
|
+
return 1;
|
|
2268
|
+
}
|
|
2269
|
+
const out = opts.out ?? `${evidencePath}.sig`;
|
|
2270
|
+
writeFileSync(out, `${JSON.stringify(sig, null, 2)}
|
|
2271
|
+
`, "utf8");
|
|
2272
|
+
process.stdout.write(
|
|
2273
|
+
`Signed ${evidencePath}
|
|
2274
|
+
Wrote ${out} (key ${sig.keyId})
|
|
2275
|
+
`
|
|
2276
|
+
);
|
|
2277
|
+
return 0;
|
|
2278
|
+
}
|
|
2279
|
+
async function runEvidenceBundle(root, opts = {}) {
|
|
2280
|
+
const runDir = latestRunDir(root);
|
|
2281
|
+
if (!runDir) {
|
|
2282
|
+
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2283
|
+
return 1;
|
|
2284
|
+
}
|
|
2285
|
+
let record;
|
|
2286
|
+
try {
|
|
2287
|
+
record = JSON.parse(
|
|
2288
|
+
readFileSync5(join12(runDir, "evidence.json"), "utf8")
|
|
2289
|
+
);
|
|
2290
|
+
} catch {
|
|
2291
|
+
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
2292
|
+
`);
|
|
2293
|
+
return 1;
|
|
2294
|
+
}
|
|
2295
|
+
let report = "";
|
|
2296
|
+
try {
|
|
2297
|
+
report = readFileSync5(
|
|
2298
|
+
join12(root, ".veris", "reports", `verify-${record.id}.md`),
|
|
2299
|
+
"utf8"
|
|
2300
|
+
);
|
|
2301
|
+
} catch {
|
|
2302
|
+
}
|
|
2303
|
+
const logIds = record.checks.filter((c) => c.logDigest).map((c) => c.id);
|
|
2304
|
+
const logs = await readRunLogs(runDir, logIds);
|
|
2305
|
+
let signature;
|
|
2306
|
+
const sigPath = join12(runDir, "evidence.json.sig");
|
|
2307
|
+
if (existsSync6(sigPath)) {
|
|
2308
|
+
try {
|
|
2309
|
+
signature = JSON.parse(readFileSync5(sigPath, "utf8"));
|
|
2310
|
+
} catch {
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
const bundle = buildBundle(record, report, logs, signature);
|
|
2314
|
+
const outDir = await ensureEvidenceDir(root);
|
|
2315
|
+
const out = opts.out ?? join12(outDir, `${record.id}.bundle.json`);
|
|
2316
|
+
await writeFile4(out, `${JSON.stringify(bundle, null, 2)}
|
|
2317
|
+
`, "utf8");
|
|
2318
|
+
process.stdout.write(`Wrote portable evidence bundle: ${out}
|
|
2319
|
+
`);
|
|
2320
|
+
return 0;
|
|
2321
|
+
}
|
|
2322
|
+
async function runEvidenceShow(root, path) {
|
|
2323
|
+
const target = path ?? (() => {
|
|
2324
|
+
const dir = latestRunDir(root);
|
|
2325
|
+
return dir ? join12(dir, "evidence.json") : null;
|
|
2326
|
+
})();
|
|
2327
|
+
if (!target) {
|
|
2328
|
+
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2329
|
+
return 0;
|
|
2330
|
+
}
|
|
2331
|
+
let record;
|
|
2332
|
+
try {
|
|
2333
|
+
record = JSON.parse(readFileSync5(target, "utf8"));
|
|
2334
|
+
} catch {
|
|
2335
|
+
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2336
|
+
`);
|
|
2337
|
+
return 1;
|
|
2338
|
+
}
|
|
2339
|
+
const g = record.git;
|
|
2340
|
+
process.stdout.write(
|
|
2341
|
+
[
|
|
2342
|
+
`Evidence ${basename3(target)}`,
|
|
2343
|
+
`Verdict ${record.verdict.state}`,
|
|
2344
|
+
`Project ${record.project.name}`,
|
|
2345
|
+
`Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
|
|
2346
|
+
`Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
|
|
2347
|
+
`Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
|
|
2348
|
+
`Digest ${record.digest}`,
|
|
2349
|
+
""
|
|
2350
|
+
].join("\n")
|
|
2351
|
+
);
|
|
2352
|
+
return 0;
|
|
2353
|
+
}
|
|
2354
|
+
var HONESTY;
|
|
2355
|
+
var init_evidence = __esm({
|
|
2356
|
+
"src/cli/commands/evidence.ts"() {
|
|
2357
|
+
"use strict";
|
|
2358
|
+
init_bundle();
|
|
2359
|
+
init_record();
|
|
2360
|
+
init_signing();
|
|
2361
|
+
init_store();
|
|
2362
|
+
init_verify_evidence();
|
|
2363
|
+
init_tty();
|
|
2364
|
+
HONESTY = "An integrity digest confirms the record was not edited or corrupted since it was written.\nIt is not forgery-proof on its own: publish the digest separately (CI log, PR) or sign it (veris evidence sign) to prove authorship.";
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
|
|
1711
2368
|
// src/cli/index.ts
|
|
2369
|
+
init_version();
|
|
1712
2370
|
import { realpathSync } from "fs";
|
|
1713
2371
|
import { argv } from "process";
|
|
1714
2372
|
import { pathToFileURL } from "url";
|
|
1715
2373
|
import cac from "cac";
|
|
1716
|
-
|
|
1717
|
-
// src/version.ts
|
|
1718
|
-
import { readFileSync } from "fs";
|
|
1719
|
-
import { fileURLToPath } from "url";
|
|
1720
|
-
var pkgUrl = new URL("../package.json", import.meta.url);
|
|
1721
|
-
var VERSION = JSON.parse(
|
|
1722
|
-
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
1723
|
-
).version;
|
|
1724
|
-
|
|
1725
|
-
// src/cli/index.ts
|
|
1726
2374
|
function buildCli() {
|
|
1727
2375
|
const cli = cac("veris");
|
|
1728
2376
|
cli.version(VERSION);
|
|
@@ -1771,6 +2419,52 @@ function buildCli() {
|
|
|
1771
2419
|
const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
1772
2420
|
process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
|
|
1773
2421
|
});
|
|
2422
|
+
cli.command(
|
|
2423
|
+
"evidence <action> [file]",
|
|
2424
|
+
"Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
|
|
2425
|
+
).option("--out <file>", "For bundle/keygen/sign: output path").option("--key <file>", "For sign: the private signing key (PEM)").option("--pubkey <file>", "For verify: assert the signer's public key").option("--key-id <id>", "For verify: assert the signer's key id").option("--sig <file>", "For verify: signature path (default <file>.sig)").action(
|
|
2426
|
+
async (action, file, opts) => {
|
|
2427
|
+
const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
2428
|
+
if (action === "verify") {
|
|
2429
|
+
if (!file) {
|
|
2430
|
+
process.stderr.write("veris: evidence verify needs a <file>\n");
|
|
2431
|
+
process.exitCode = 1;
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
process.exitCode = await mod.runEvidenceVerify(file, {
|
|
2435
|
+
pubkey: opts.pubkey,
|
|
2436
|
+
keyId: opts.keyId,
|
|
2437
|
+
sig: opts.sig
|
|
2438
|
+
});
|
|
2439
|
+
} else if (action === "bundle") {
|
|
2440
|
+
process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
|
|
2441
|
+
out: opts.out
|
|
2442
|
+
});
|
|
2443
|
+
} else if (action === "show") {
|
|
2444
|
+
process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
|
|
2445
|
+
} else if (action === "keygen") {
|
|
2446
|
+
process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
|
|
2447
|
+
out: opts.out
|
|
2448
|
+
});
|
|
2449
|
+
} else if (action === "sign") {
|
|
2450
|
+
if (!file) {
|
|
2451
|
+
process.stderr.write("veris: evidence sign needs a <file>\n");
|
|
2452
|
+
process.exitCode = 1;
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
process.exitCode = await mod.runEvidenceSign(file, {
|
|
2456
|
+
key: opts.key,
|
|
2457
|
+
out: opts.out
|
|
2458
|
+
});
|
|
2459
|
+
} else {
|
|
2460
|
+
process.stderr.write(
|
|
2461
|
+
`veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
|
|
2462
|
+
`
|
|
2463
|
+
);
|
|
2464
|
+
process.exitCode = 1;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
);
|
|
1774
2468
|
return { raw: cli, version: VERSION };
|
|
1775
2469
|
}
|
|
1776
2470
|
async function main(argv2) {
|