@trigguard/cli 0.1.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.
Files changed (84) hide show
  1. package/README.md +70 -0
  2. package/data/execution-surfaces.json +28 -0
  3. package/dist/auth.js +20 -0
  4. package/dist/commands/authorize.d.ts +1 -0
  5. package/dist/commands/authorize.js +99 -0
  6. package/dist/commands/chaos.d.ts +1 -0
  7. package/dist/commands/chaos.js +35 -0
  8. package/dist/commands/dev.d.ts +1 -0
  9. package/dist/commands/dev.js +27 -0
  10. package/dist/commands/doctor.d.ts +1 -0
  11. package/dist/commands/doctor.js +50 -0
  12. package/dist/commands/log.d.ts +3 -0
  13. package/dist/commands/log.js +119 -0
  14. package/dist/commands/logMonitor.d.ts +1 -0
  15. package/dist/commands/logMonitor.js +65 -0
  16. package/dist/commands/login-web.d.ts +1 -0
  17. package/dist/commands/login-web.js +80 -0
  18. package/dist/commands/policy-distribution.d.ts +10 -0
  19. package/dist/commands/policy-distribution.js +108 -0
  20. package/dist/commands/policy-runtime.d.ts +4 -0
  21. package/dist/commands/policy-runtime.js +61 -0
  22. package/dist/commands/policy.d.ts +1 -0
  23. package/dist/commands/policy.js +123 -0
  24. package/dist/commands/policyLifecycle.d.ts +13 -0
  25. package/dist/commands/policyLifecycle.js +601 -0
  26. package/dist/commands/receiptFetch.d.ts +1 -0
  27. package/dist/commands/receiptFetch.js +44 -0
  28. package/dist/commands/receiptProof.d.ts +1 -0
  29. package/dist/commands/receiptProof.js +43 -0
  30. package/dist/commands/replay.d.ts +2 -0
  31. package/dist/commands/replay.js +130 -0
  32. package/dist/commands/session.d.ts +6 -0
  33. package/dist/commands/session.js +280 -0
  34. package/dist/commands/simulate.d.ts +5 -0
  35. package/dist/commands/simulate.js +89 -0
  36. package/dist/commands/tg-authorize.d.ts +12 -0
  37. package/dist/commands/tg-authorize.js +191 -0
  38. package/dist/commands/tg-init.d.ts +1 -0
  39. package/dist/commands/tg-init.js +149 -0
  40. package/dist/commands/tg-setup.d.ts +1 -0
  41. package/dist/commands/tg-setup.js +43 -0
  42. package/dist/commands/tg-surfaces.d.ts +7 -0
  43. package/dist/commands/tg-surfaces.js +50 -0
  44. package/dist/commands/tg-verify.d.ts +1 -0
  45. package/dist/commands/tg-verify.js +118 -0
  46. package/dist/commands/transparency.d.ts +2 -0
  47. package/dist/commands/transparency.js +65 -0
  48. package/dist/commands/verify.d.ts +1 -0
  49. package/dist/commands/verify.js +127 -0
  50. package/dist/commands/verifyBundle.d.ts +1 -0
  51. package/dist/commands/verifyBundle.js +109 -0
  52. package/dist/commands/verifyReceiptCmd.d.ts +1 -0
  53. package/dist/commands/verifyReceiptCmd.js +49 -0
  54. package/dist/commands/witness.d.ts +1 -0
  55. package/dist/commands/witness.js +22 -0
  56. package/dist/cp/cliDeviceAuth.d.ts +24 -0
  57. package/dist/cp/cliDeviceAuth.js +68 -0
  58. package/dist/cp/client.d.ts +19 -0
  59. package/dist/cp/client.js +73 -0
  60. package/dist/cp/config.d.ts +8 -0
  61. package/dist/cp/config.js +113 -0
  62. package/dist/cp/credentials.d.ts +9 -0
  63. package/dist/cp/credentials.js +31 -0
  64. package/dist/cp/provisionCliKey.d.ts +12 -0
  65. package/dist/cp/provisionCliKey.js +43 -0
  66. package/dist/cp/types.d.ts +37 -0
  67. package/dist/cp/types.js +1 -0
  68. package/dist/stdin.js +9 -0
  69. package/dist/tg/args.d.ts +3 -0
  70. package/dist/tg/args.js +28 -0
  71. package/dist/tg/authorize-format.d.ts +21 -0
  72. package/dist/tg/authorize-format.js +87 -0
  73. package/dist/tg/errors.d.ts +6 -0
  74. package/dist/tg/errors.js +53 -0
  75. package/dist/tg/gateway.d.ts +2 -0
  76. package/dist/tg/gateway.js +19 -0
  77. package/dist/tg/help.d.ts +7 -0
  78. package/dist/tg/help.js +164 -0
  79. package/dist/tg/receipt.d.ts +1 -0
  80. package/dist/tg/receipt.js +13 -0
  81. package/dist/tg/shellQuote.d.ts +1 -0
  82. package/dist/tg/shellQuote.js +6 -0
  83. package/dist/tg.js +92 -0
  84. package/package.json +50 -0
@@ -0,0 +1,601 @@
1
+ import { access, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
2
+ import { constants as fsConstants, existsSync } from "node:fs";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { CompilerError, ParseError, assertBundleHashMatches, compilePolicyBundle, evaluatePolicyBundleOnInput, parsePolicy, stringifyCompiledBundle, } from "@trigguard/policy-dsl";
5
+ import { validateBundleVersionContract } from "@trigguard/policy-engine";
6
+ const TG = () => join(process.cwd(), ".trigguard");
7
+ const policiesDir = () => join(TG(), "policies");
8
+ const activePolicyPath = () => join(TG(), "active_policy");
9
+ const candidatePolicyPath = () => join(TG(), "candidate_policy");
10
+ const policyMetricsPath = () => join(TG(), "policy_metrics.json");
11
+ const historyPath = () => join(TG(), "policy_history.json");
12
+ const stackPath = () => join(TG(), "activation_stack.json");
13
+ export function policyUsage() {
14
+ console.error(`Usage:
15
+ trigguard policy lint <file.dsl>
16
+ trigguard policy test <file.dsl>
17
+ trigguard policy build <file.dsl>
18
+ trigguard policy simulate <bundle.json> <inputs.jsonl> [--compare <bundle2.json>]
19
+ trigguard policy publish <bundle.json> (optional TRIGGUARD_POLICY_DISTRIBUTION_URL → POST /policy/publish)
20
+ trigguard policy activate <bundle_hash>
21
+ trigguard policy stack-rollback
22
+ trigguard policy reload <manifest.json> <bundle.json>
23
+ trigguard policy rollback
24
+ trigguard policy history
25
+ trigguard policy active
26
+ trigguard policy stage <bundle.json|bundle_hash>
27
+ trigguard policy promote
28
+ trigguard policy metrics
29
+ trigguard policy sync <manifest.json> <bundle.json>
30
+ trigguard policy state
31
+ trigguard policy verify-manifest <manifest.json>
32
+ trigguard policy validate <bundle.json>
33
+ trigguard policy version bump <bundle.json> <major|minor|patch>
34
+ trigguard policy request-change <bundle.json>
35
+ trigguard policy approve <changeId>
36
+ trigguard policy reject <changeId>
37
+ trigguard policy set-window <start> <end>
38
+ trigguard policy break-glass <reason>
39
+ trigguard policy break-glass-end
40
+ trigguard policy pending`);
41
+ process.exit(1);
42
+ }
43
+ function defaultBundlePath(dslPath) {
44
+ const base = basename(dslPath);
45
+ const dir = dirname(dslPath);
46
+ const outBase = /\.dsl$/i.test(base) ? base.replace(/\.dsl$/i, ".bundle.json") : `${base}.bundle.json`;
47
+ return join(dir, outBase);
48
+ }
49
+ function normalizeBundleHash(input) {
50
+ const s = String(input).trim();
51
+ if (/^sha256:[a-f0-9]{64}$/i.test(s)) {
52
+ return `sha256:${s.slice("sha256:".length).toLowerCase()}`;
53
+ }
54
+ if (/^[a-f0-9]{64}$/i.test(s)) {
55
+ return `sha256:${s.toLowerCase()}`;
56
+ }
57
+ throw new Error(`Invalid bundle hash: ${input}`);
58
+ }
59
+ function policyFileNameFromHash(hash) {
60
+ return `${normalizeBundleHash(hash).replace(/^sha256:/i, "")}.json`;
61
+ }
62
+ async function readJsonFile(path) {
63
+ const raw = await readFile(path, "utf8");
64
+ return JSON.parse(raw);
65
+ }
66
+ async function readHistory() {
67
+ try {
68
+ const h = await readJsonFile(historyPath());
69
+ return Array.isArray(h) ? h : [];
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ }
75
+ async function writeHistory(rows) {
76
+ await mkdir(TG(), { recursive: true });
77
+ await writeFile(historyPath(), `${JSON.stringify(rows, null, 2)}\n`, "utf8");
78
+ }
79
+ async function readStack() {
80
+ try {
81
+ const s = await readJsonFile(stackPath());
82
+ if (Array.isArray(s))
83
+ return s;
84
+ if (s && typeof s === "object" && Array.isArray(s.stack)) {
85
+ return s.stack;
86
+ }
87
+ return [];
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ }
93
+ async function writeStack(stack) {
94
+ await mkdir(TG(), { recursive: true });
95
+ await writeFile(stackPath(), `${JSON.stringify(stack, null, 2)}\n`, "utf8");
96
+ }
97
+ async function readDsl(path) {
98
+ try {
99
+ return await readFile(path, "utf8");
100
+ }
101
+ catch (e) {
102
+ const msg = e instanceof Error ? e.message : String(e);
103
+ console.error(`Failed to read ${path}: ${msg}`);
104
+ process.exit(1);
105
+ return "";
106
+ }
107
+ }
108
+ export async function runPolicyLint(path) {
109
+ const text = await readDsl(path);
110
+ try {
111
+ const rules = parsePolicy(text, { filename: path });
112
+ console.log("Policy valid");
113
+ console.log(`Rules: ${rules.length}`);
114
+ }
115
+ catch (e) {
116
+ if (e instanceof ParseError) {
117
+ console.error(e.format());
118
+ process.exit(1);
119
+ return;
120
+ }
121
+ throw e;
122
+ }
123
+ }
124
+ export async function runPolicyTest(path) {
125
+ const text = await readDsl(path);
126
+ try {
127
+ const rules = parsePolicy(text, { filename: path });
128
+ const bundle = compilePolicyBundle(rules, {});
129
+ console.log("Syntax OK");
130
+ console.log(`Rules: ${rules.length}`);
131
+ console.log(`Bundle hash: ${bundle.bundle_hash}`);
132
+ }
133
+ catch (e) {
134
+ if (e instanceof ParseError) {
135
+ console.error(e.format());
136
+ process.exit(1);
137
+ return;
138
+ }
139
+ if (e instanceof CompilerError) {
140
+ console.error(e.message);
141
+ process.exit(1);
142
+ return;
143
+ }
144
+ throw e;
145
+ }
146
+ }
147
+ export async function runPolicyBuild(path) {
148
+ const text = await readDsl(path);
149
+ try {
150
+ const rules = parsePolicy(text, { filename: path });
151
+ const bundle = compilePolicyBundle(rules, {});
152
+ const outPath = defaultBundlePath(path);
153
+ await writeFile(outPath, `${stringifyCompiledBundle(bundle)}\n`, "utf8");
154
+ console.log(`Wrote ${outPath}`);
155
+ }
156
+ catch (e) {
157
+ if (e instanceof ParseError) {
158
+ console.error(e.format());
159
+ process.exit(1);
160
+ return;
161
+ }
162
+ if (e instanceof CompilerError) {
163
+ console.error(e.message);
164
+ process.exit(1);
165
+ return;
166
+ }
167
+ throw e;
168
+ }
169
+ }
170
+ function parseSimulateArgs(rest) {
171
+ const idx = rest.indexOf("--compare");
172
+ if (idx >= 0) {
173
+ const comparePath = rest[idx + 1];
174
+ if (!comparePath)
175
+ policyUsage();
176
+ const head = rest.slice(0, idx);
177
+ if (head.length < 2)
178
+ policyUsage();
179
+ return { bundlePath: head[0], inputsPath: head[1], comparePath };
180
+ }
181
+ if (rest.length < 2)
182
+ policyUsage();
183
+ return { bundlePath: rest[0], inputsPath: rest[1], comparePath: null };
184
+ }
185
+ function validateBundleShape(b) {
186
+ if (!b || typeof b !== "object" || Array.isArray(b)) {
187
+ throw new CompilerError("bundle must be a JSON object");
188
+ }
189
+ const o = b;
190
+ if (!Array.isArray(o.rules)) {
191
+ throw new CompilerError("bundle.rules must be an array");
192
+ }
193
+ for (const r of o.rules) {
194
+ if (!r || typeof r !== "object")
195
+ throw new CompilerError("Invalid rule entry");
196
+ const rr = r;
197
+ if (typeof rr.id !== "string" || typeof rr.effect !== "string" || rr.condition == null) {
198
+ throw new CompilerError("Each rule needs id, effect, and condition");
199
+ }
200
+ }
201
+ if (typeof o.bundle_hash !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(o.bundle_hash)) {
202
+ throw new CompilerError("bundle.bundle_hash must be sha256:<64-hex>");
203
+ }
204
+ if (typeof o.bundleVersion !== "string" || !/^\d+\.\d+\.\d+$/.test(o.bundleVersion)) {
205
+ throw new CompilerError("bundle.bundleVersion must be semantic version (x.y.z)");
206
+ }
207
+ if (typeof o.policySchema !== "string" || !o.policySchema.trim()) {
208
+ throw new CompilerError("bundle.policySchema is required");
209
+ }
210
+ if (!o.engineCompatibility ||
211
+ typeof o.engineCompatibility.minEngine !== "string" ||
212
+ typeof o.engineCompatibility.maxEngine !== "string") {
213
+ throw new CompilerError("bundle.engineCompatibility.{minEngine,maxEngine} required");
214
+ }
215
+ }
216
+ async function loadBundleForSimulate(path) {
217
+ let raw;
218
+ try {
219
+ raw = await readFile(path, "utf8");
220
+ }
221
+ catch (e) {
222
+ const msg = e instanceof Error ? e.message : String(e);
223
+ console.error(`Failed to read ${path}: ${msg}`);
224
+ process.exit(1);
225
+ throw new Error("unreachable");
226
+ }
227
+ const parsed = JSON.parse(raw);
228
+ validateBundleShape(parsed);
229
+ try {
230
+ assertBundleHashMatches(parsed);
231
+ }
232
+ catch (e) {
233
+ if (e instanceof CompilerError) {
234
+ console.error(e.message);
235
+ process.exit(1);
236
+ throw new Error("unreachable");
237
+ }
238
+ throw e;
239
+ }
240
+ const contract = validateBundleVersionContract(parsed);
241
+ if (!contract.ok) {
242
+ console.error(contract.code);
243
+ console.error(contract.detail);
244
+ process.exit(1);
245
+ }
246
+ return parsed;
247
+ }
248
+ export async function runPolicySimulate(rest) {
249
+ const { bundlePath, inputsPath, comparePath } = parseSimulateArgs(rest);
250
+ let rawInputs;
251
+ try {
252
+ rawInputs = await readFile(inputsPath, "utf8");
253
+ }
254
+ catch (e) {
255
+ const msg = e instanceof Error ? e.message : String(e);
256
+ console.error(`Failed to read ${inputsPath}: ${msg}`);
257
+ process.exit(1);
258
+ return;
259
+ }
260
+ const bundle = await loadBundleForSimulate(bundlePath);
261
+ const bundleB = comparePath ? await loadBundleForSimulate(comparePath) : null;
262
+ const lines = rawInputs.split(/\r?\n/).filter((l) => l.trim().length > 0);
263
+ const summary = { PERMIT: 0, DENY: 0, SILENCE: 0 };
264
+ for (let i = 0; i < lines.length; i += 1) {
265
+ const line = lines[i];
266
+ let row;
267
+ try {
268
+ row = JSON.parse(line);
269
+ }
270
+ catch {
271
+ console.error(`Invalid JSON on line ${i + 1}`);
272
+ process.exit(1);
273
+ return;
274
+ }
275
+ const d = evaluatePolicyBundleOnInput(bundle, row);
276
+ summary[d] += 1;
277
+ console.log(`INPUT ${i + 1} → ${d}`);
278
+ if (bundleB) {
279
+ const d2 = evaluatePolicyBundleOnInput(bundleB, row);
280
+ if (d !== d2) {
281
+ console.log(`Input ${i + 1} decision changed:`);
282
+ console.log(`A → ${d}`);
283
+ console.log(`B → ${d2}`);
284
+ }
285
+ }
286
+ }
287
+ console.log("Summary:");
288
+ console.log(`Total inputs: ${lines.length}`);
289
+ console.log(`PERMIT: ${summary.PERMIT}`);
290
+ console.log(`DENY: ${summary.DENY}`);
291
+ console.log(`SILENCE: ${summary.SILENCE}`);
292
+ }
293
+ export async function runPolicyPublish(bundlePath) {
294
+ let raw;
295
+ try {
296
+ raw = await readFile(bundlePath, "utf8");
297
+ }
298
+ catch (e) {
299
+ const msg = e instanceof Error ? e.message : String(e);
300
+ console.error(`Failed to read ${bundlePath}: ${msg}`);
301
+ process.exit(1);
302
+ return;
303
+ }
304
+ const parsed = JSON.parse(raw);
305
+ validateBundleShape(parsed);
306
+ try {
307
+ assertBundleHashMatches(parsed);
308
+ }
309
+ catch (e) {
310
+ if (e instanceof CompilerError) {
311
+ console.error(e.message);
312
+ process.exit(1);
313
+ return;
314
+ }
315
+ throw e;
316
+ }
317
+ const b = parsed;
318
+ const h = normalizeBundleHash(b.bundle_hash);
319
+ const destDir = policiesDir();
320
+ await mkdir(destDir, { recursive: true });
321
+ const dest = join(destDir, policyFileNameFromHash(h));
322
+ const body = `${JSON.stringify(parsed, null, 2)}\n`;
323
+ await writeFile(dest, body, "utf8");
324
+ const history = await readHistory();
325
+ history.push({ bundle_hash: h, published_at: new Date().toISOString(), activated_at: null });
326
+ await writeHistory(history);
327
+ const distUrl = (process.env.TRIGGUARD_POLICY_DISTRIBUTION_URL || "").trim();
328
+ if (distUrl) {
329
+ const url = `${distUrl.replace(/\/+$/, "")}/policy/publish`;
330
+ const res = await fetch(url, {
331
+ method: "POST",
332
+ headers: { "Content-Type": "application/json" },
333
+ body: body.trimEnd(),
334
+ });
335
+ const text = await res.text();
336
+ if (!res.ok) {
337
+ console.error(`Distribution publish failed (HTTP ${res.status}): ${text}`);
338
+ process.exitCode = 2;
339
+ return;
340
+ }
341
+ try {
342
+ const j = JSON.parse(text);
343
+ if (!j.ok) {
344
+ console.error(`Distribution publish rejected: ${j.error ?? text}`);
345
+ process.exitCode = 2;
346
+ return;
347
+ }
348
+ }
349
+ catch {
350
+ console.error(`Distribution publish: unexpected response: ${text}`);
351
+ process.exitCode = 2;
352
+ return;
353
+ }
354
+ console.log(`Policy published to distribution service: ${url}`);
355
+ }
356
+ console.log("Policy published");
357
+ console.log(`Bundle hash: ${h}`);
358
+ }
359
+ export async function runPolicyActivate(hashArg) {
360
+ const h = normalizeBundleHash(hashArg);
361
+ const dest = join(policiesDir(), policyFileNameFromHash(h));
362
+ try {
363
+ await access(dest, fsConstants.F_OK);
364
+ }
365
+ catch {
366
+ console.error(`Bundle not found for hash (expected file ${dest})`);
367
+ process.exit(1);
368
+ return;
369
+ }
370
+ const activated_at = new Date().toISOString();
371
+ await mkdir(TG(), { recursive: true });
372
+ await writeFile(activePolicyPath(), `${JSON.stringify({ bundle_hash: h, activated_at }, null, 2)}\n`, "utf8");
373
+ const stack = await readStack();
374
+ if (stack.length === 0 || stack[stack.length - 1] !== h) {
375
+ stack.push(h);
376
+ }
377
+ await writeStack(stack);
378
+ const history = await readHistory();
379
+ history.push({ bundle_hash: h, published_at: null, activated_at });
380
+ await writeHistory(history);
381
+ }
382
+ export async function runPolicyStackRollback() {
383
+ const stack = await readStack();
384
+ if (stack.length === 0) {
385
+ console.error("Nothing to rollback (no activations recorded).");
386
+ process.exit(1);
387
+ return;
388
+ }
389
+ stack.pop();
390
+ await writeStack(stack);
391
+ const activated_at = new Date().toISOString();
392
+ if (stack.length === 0) {
393
+ try {
394
+ await unlink(activePolicyPath());
395
+ }
396
+ catch {
397
+ // ignore missing file
398
+ }
399
+ console.log("Rollback successful");
400
+ console.log("Active bundle: (none)");
401
+ return;
402
+ }
403
+ const prev = stack[stack.length - 1];
404
+ await writeFile(activePolicyPath(), `${JSON.stringify({ bundle_hash: prev, activated_at }, null, 2)}\n`, "utf8");
405
+ console.log("Rollback successful");
406
+ console.log(`Active bundle: ${prev}`);
407
+ }
408
+ function emptyMetrics() {
409
+ return {
410
+ evaluations: 0,
411
+ decision_diff: { permit_to_deny: 0, permit_to_silence: 0, deny_to_permit: 0 },
412
+ };
413
+ }
414
+ async function readMetrics() {
415
+ try {
416
+ const parsed = await readJsonFile(policyMetricsPath());
417
+ return {
418
+ evaluations: Number(parsed.evaluations ?? 0),
419
+ decision_diff: {
420
+ permit_to_deny: Number(parsed.decision_diff?.permit_to_deny ?? 0),
421
+ permit_to_silence: Number(parsed.decision_diff?.permit_to_silence ?? 0),
422
+ deny_to_permit: Number(parsed.decision_diff?.deny_to_permit ?? 0),
423
+ },
424
+ };
425
+ }
426
+ catch {
427
+ return emptyMetrics();
428
+ }
429
+ }
430
+ function totalDiff(m) {
431
+ return m.decision_diff.permit_to_deny + m.decision_diff.permit_to_silence + m.decision_diff.deny_to_permit;
432
+ }
433
+ async function resolveBundleHashForStage(arg) {
434
+ const trimmed = String(arg).trim();
435
+ if (!trimmed)
436
+ throw new Error("bundle argument required");
437
+ if (existsSync(trimmed)) {
438
+ const raw = await readFile(trimmed, "utf8");
439
+ const parsed = JSON.parse(raw);
440
+ validateBundleShape(parsed);
441
+ assertBundleHashMatches(parsed);
442
+ return normalizeBundleHash(parsed.bundle_hash);
443
+ }
444
+ return normalizeBundleHash(trimmed);
445
+ }
446
+ export async function runPolicyStage(bundleArg) {
447
+ const h = await resolveBundleHashForStage(bundleArg);
448
+ const bundlePath = join(policiesDir(), policyFileNameFromHash(h));
449
+ try {
450
+ await access(bundlePath, fsConstants.F_OK);
451
+ }
452
+ catch {
453
+ console.error(`Bundle not found for stage (expected file ${bundlePath}). Publish first.`);
454
+ process.exit(1);
455
+ return;
456
+ }
457
+ await mkdir(TG(), { recursive: true });
458
+ await writeFile(candidatePolicyPath(), `${JSON.stringify({ bundle_hash: h, staged_at: new Date().toISOString() }, null, 2)}\n`, "utf8");
459
+ console.log(`Candidate policy staged: ${h}`);
460
+ }
461
+ export async function runPolicyPromote() {
462
+ let candidate;
463
+ try {
464
+ candidate = await readJsonFile(candidatePolicyPath());
465
+ }
466
+ catch {
467
+ console.error("No candidate policy staged. Run: trigguard policy stage <bundle>");
468
+ process.exit(1);
469
+ return;
470
+ }
471
+ if (typeof candidate.bundle_hash !== "string" || !candidate.bundle_hash.trim()) {
472
+ console.error("candidate_policy is invalid (missing bundle_hash)");
473
+ process.exit(1);
474
+ return;
475
+ }
476
+ const h = normalizeBundleHash(candidate.bundle_hash);
477
+ const bundlePath = join(policiesDir(), policyFileNameFromHash(h));
478
+ try {
479
+ await access(bundlePath, fsConstants.F_OK);
480
+ }
481
+ catch {
482
+ console.error(`Candidate bundle file missing: ${bundlePath}`);
483
+ process.exit(1);
484
+ return;
485
+ }
486
+ const thresholdRaw = (process.env.TRIGGUARD_POLICY_DIFF_THRESHOLD || "").trim();
487
+ const threshold = thresholdRaw ? Number(thresholdRaw) : 0.01;
488
+ const metrics = await readMetrics();
489
+ const driftRate = metrics.evaluations > 0 ? totalDiff(metrics) / metrics.evaluations : 0;
490
+ if (Number.isFinite(threshold) && driftRate >= threshold) {
491
+ console.error("Policy candidate diverges from active policy");
492
+ console.error(`diff_rate=${(driftRate * 100).toFixed(2)}% threshold=${(threshold * 100).toFixed(2)}%`);
493
+ process.exit(1);
494
+ return;
495
+ }
496
+ await writeFile(activePolicyPath(), `${JSON.stringify({ bundle_hash: h, activated_at: new Date().toISOString() }, null, 2)}\n`, "utf8");
497
+ try {
498
+ await unlink(candidatePolicyPath());
499
+ }
500
+ catch {
501
+ // ignore
502
+ }
503
+ console.log(`Candidate promoted to active: ${h}`);
504
+ }
505
+ export async function runPolicyMetrics() {
506
+ const metrics = await readMetrics();
507
+ const diff = totalDiff(metrics);
508
+ const rate = metrics.evaluations > 0 ? (diff / metrics.evaluations) * 100 : 0;
509
+ console.log(JSON.stringify({ ...metrics, diff_total: diff, diff_rate_percent: Number(rate.toFixed(4)) }, null, 2));
510
+ }
511
+ export async function runPolicyValidate(bundlePath) {
512
+ let raw;
513
+ try {
514
+ raw = await readFile(bundlePath, "utf8");
515
+ }
516
+ catch (e) {
517
+ const msg = e instanceof Error ? e.message : String(e);
518
+ console.error(`Failed to read ${bundlePath}: ${msg}`);
519
+ process.exitCode = 2;
520
+ return;
521
+ }
522
+ let parsed;
523
+ try {
524
+ parsed = JSON.parse(raw);
525
+ }
526
+ catch {
527
+ console.log("POLICY_BUNDLE_INVALID");
528
+ process.exitCode = 2;
529
+ return;
530
+ }
531
+ try {
532
+ validateBundleShape(parsed);
533
+ }
534
+ catch (e) {
535
+ const msg = e instanceof Error ? e.message : String(e);
536
+ if (msg.includes("bundleVersion") || msg.includes("policySchema") || msg.includes("engineCompatibility")) {
537
+ console.log("MISSING_MANIFEST_FIELDS");
538
+ process.exitCode = 2;
539
+ return;
540
+ }
541
+ if (msg.includes("policySchema")) {
542
+ console.log("INVALID_SCHEMA");
543
+ process.exitCode = 2;
544
+ return;
545
+ }
546
+ console.log("POLICY_BUNDLE_INVALID");
547
+ process.exitCode = 2;
548
+ return;
549
+ }
550
+ try {
551
+ assertBundleHashMatches(parsed);
552
+ }
553
+ catch {
554
+ console.log("POLICY_BUNDLE_INVALID");
555
+ process.exitCode = 2;
556
+ return;
557
+ }
558
+ const contract = validateBundleVersionContract(parsed);
559
+ if (!contract.ok) {
560
+ if (contract.code === "POLICY_ENGINE_VERSION_INCOMPATIBLE")
561
+ console.log("INCOMPATIBLE_ENGINE_VERSION");
562
+ else if (contract.code === "POLICY_SCHEMA_UNSUPPORTED")
563
+ console.log("INVALID_SCHEMA");
564
+ else
565
+ console.log("MISSING_MANIFEST_FIELDS");
566
+ process.exitCode = 2;
567
+ return;
568
+ }
569
+ console.log("VALID");
570
+ }
571
+ function parseSemverString(v) {
572
+ const t = String(v || "").trim();
573
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(t);
574
+ if (!m)
575
+ throw new Error(`invalid semantic version: ${v}`);
576
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
577
+ }
578
+ function bumpSemver(version, kind) {
579
+ const p = parseSemverString(version);
580
+ if (kind === "major")
581
+ return `${p.major + 1}.0.0`;
582
+ if (kind === "minor")
583
+ return `${p.major}.${p.minor + 1}.0`;
584
+ return `${p.major}.${p.minor}.${p.patch + 1}`;
585
+ }
586
+ export async function runPolicyVersionBump(argv) {
587
+ const bundlePath = argv[0];
588
+ const kind = argv[1];
589
+ if (!bundlePath || !kind || !["major", "minor", "patch"].includes(kind)) {
590
+ console.error("Usage: trigguard policy version bump <bundle.json> <major|minor|patch>");
591
+ process.exitCode = 1;
592
+ return;
593
+ }
594
+ const raw = await readFile(bundlePath, "utf8");
595
+ const parsed = JSON.parse(raw);
596
+ validateBundleShape(parsed);
597
+ const next = bumpSemver(parsed.bundleVersion, kind);
598
+ parsed.bundleVersion = next;
599
+ await writeFile(bundlePath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
600
+ console.log(JSON.stringify({ bundleVersion: next, path: bundlePath }, null, 2));
601
+ }
@@ -0,0 +1 @@
1
+ export declare function runReceiptFetch(argv: string[]): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { parseArgs } from "node:util";
2
+ function baseUrlFromArgs(values) {
3
+ const raw = (typeof values["log-url"] === "string" && values["log-url"]) ||
4
+ process.env.TRIGGUARD_RECEIPT_LOG_URL ||
5
+ "http://127.0.0.1:3847";
6
+ return String(raw).replace(/\/$/, "");
7
+ }
8
+ export async function runReceiptFetch(argv) {
9
+ const { values, positionals } = parseArgs({
10
+ args: argv,
11
+ options: {
12
+ "log-url": { type: "string" },
13
+ json: { type: "boolean", default: false },
14
+ },
15
+ allowPositionals: true,
16
+ strict: true,
17
+ });
18
+ const receiptId = positionals[0];
19
+ if (!receiptId) {
20
+ console.error("Usage: trigguard receipt fetch <receipt_id> [--log-url URL] [--json]");
21
+ process.exit(1);
22
+ }
23
+ const base = baseUrlFromArgs(values);
24
+ const res = await fetch(`${base}/v1/receipts/${encodeURIComponent(receiptId)}`, {
25
+ headers: { Accept: "application/json" },
26
+ });
27
+ if (!res.ok) {
28
+ console.error(`receipt fetch failed: HTTP ${res.status}`);
29
+ process.exit(1);
30
+ }
31
+ const row = (await res.json());
32
+ if (values.json) {
33
+ console.log(JSON.stringify(row));
34
+ return;
35
+ }
36
+ console.log(`receipt_id: ${String(row.receipt_id ?? "")}`);
37
+ console.log(`decision: ${String(row.decision ?? "")}`);
38
+ if (row.surface != null)
39
+ console.log(`surface: ${String(row.surface)}`);
40
+ console.log(`timestamp: ${String(row.timestamp ?? "")}`);
41
+ console.log(`receipt_hash: ${String(row.receipt_hash ?? "")}`);
42
+ if (row.merkle_root != null)
43
+ console.log(`merkle_root: ${String(row.merkle_root)}`);
44
+ }
@@ -0,0 +1 @@
1
+ export declare function runReceiptProof(argv: string[]): Promise<void>;
@@ -0,0 +1,43 @@
1
+ import { parseArgs } from "node:util";
2
+ function baseUrlFromArgs(values) {
3
+ const raw = (typeof values["log-url"] === "string" && values["log-url"]) ||
4
+ process.env.TRIGGUARD_RECEIPT_LOG_URL ||
5
+ "http://127.0.0.1:3847";
6
+ return String(raw).replace(/\/$/, "");
7
+ }
8
+ export async function runReceiptProof(argv) {
9
+ const { values, positionals } = parseArgs({
10
+ args: argv,
11
+ options: {
12
+ "log-url": { type: "string" },
13
+ json: { type: "boolean", default: false },
14
+ },
15
+ allowPositionals: true,
16
+ strict: true,
17
+ });
18
+ const receiptId = positionals[0];
19
+ if (!receiptId) {
20
+ console.error("Usage: trigguard receipt proof <receipt_id> [--log-url URL] [--json]");
21
+ process.exit(1);
22
+ }
23
+ const base = baseUrlFromArgs(values);
24
+ const res = await fetch(`${base}/v1/receipts/${encodeURIComponent(receiptId)}/proof`, {
25
+ headers: { Accept: "application/json" },
26
+ });
27
+ if (!res.ok) {
28
+ console.error(`receipt proof failed: HTTP ${res.status}`);
29
+ process.exit(1);
30
+ }
31
+ const proof = (await res.json());
32
+ if (values.json) {
33
+ console.log(JSON.stringify(proof));
34
+ return;
35
+ }
36
+ console.log(`receipt_id: ${String(proof.receipt_id ?? "")}`);
37
+ console.log(`merkle_root: ${String(proof.merkle_root ?? "")}`);
38
+ console.log(`leaf_index: ${String(proof.leaf_index ?? "")}`);
39
+ const path = Array.isArray(proof.inclusion_proof) ? proof.inclusion_proof : [];
40
+ console.log(`inclusion_proof_len: ${path.length}`);
41
+ for (const h of path)
42
+ console.log(` - ${String(h)}`);
43
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runReplay(argv: string[]): Promise<void>;
2
+ export declare function runReplayAudit(): Promise<void>;