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/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("Veris doctor");
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 { writeFile as writeFile2 } from "fs/promises";
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(`Veris \u2014 ${scoped}`));
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("Veris"));
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
- ` : "Veris already initialized (.veris/config.json exists). Nothing overwritten.\n"
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 = ["runs/", "reports/", "cache/", "graph.json", ""].join("\n");
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("# Veris Verification Report");
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 Veris. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
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 reportRef = await writeReport(root, run.id, renderMarkdown(run));
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 writeMetadata(runDir, run);
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 = readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
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 readdirSync2, statSync } from "fs";
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 = readdirSync2(dir);
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 reportRef = await writeReport(root, run.id, renderMarkdown(run));
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 writeMetadata(runDir, run);
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 readdirSync3,
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 = basename(root);
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 = readdirSync3(dir);
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("Veris \u2014 scan"));
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("Veris \u2014 plan"));
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) {