@codai/axiom-mcp 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli-main.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { createRequire } from "node:module";
2
2
  import * as fs$1 from "node:fs/promises";
3
- import fs, { access, constants, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import fs, { access, chmod, constants, lstat, mkdir, open, opendir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { dirname, join, relative, sep } from "node:path";
6
6
  import { parseArgs, promisify } from "node:util";
7
- import { createHash, randomBytes } from "node:crypto";
8
- import { realpath } from "node:fs";
7
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
8
+ import { createReadStream, realpath as realpath$1 } from "node:fs";
9
9
  import childProcess, { spawn } from "node:child_process";
10
10
  import { cpus, hostname } from "node:os";
11
11
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
12
- import { performance } from "node:perf_hooks";
12
+ import { performance as performance$1 } from "node:perf_hooks";
13
+ import { fileURLToPath } from "node:url";
13
14
  import process$1 from "node:process";
14
15
  var __create = Object.create;
15
16
  var __defProp = Object.defineProperty;
@@ -80,13 +81,13 @@ function canonicalize$1(value) {
80
81
  }
81
82
  return `{${parts.join(",")}}`;
82
83
  }
83
- const HEX64 = /^[0-9a-f]{64}$/;
84
+ const HEX64$1 = /^[0-9a-f]{64}$/;
84
85
  const REF = /^sha256:([0-9a-f]{64})$/;
85
86
  function sha256Hex(data) {
86
87
  return createHash("sha256").update(data).digest("hex");
87
88
  }
88
89
  function digestRef(hex) {
89
- if (!HEX64.test(hex)) throw new TypeError(`invalid sha256 hex: ${hex}`);
90
+ if (!HEX64$1.test(hex)) throw new TypeError(`invalid sha256 hex: ${hex}`);
90
91
  return `sha256:${hex}`;
91
92
  }
92
93
  function parseDigestRef(ref) {
@@ -100,7 +101,200 @@ function canonicalHash(value) {
100
101
  function canonicalDigestRef(value) {
101
102
  return digestRef(canonicalHash(value));
102
103
  }
103
- new TextEncoder();
104
+ const utf8 = new TextEncoder();
105
+ function pae(payloadType, payload) {
106
+ const typeBytes = utf8.encode(payloadType);
107
+ const head = utf8.encode(`DSSEv1 ${typeBytes.length} ${payloadType} ${payload.length} `);
108
+ const out = new Uint8Array(head.length + payload.length);
109
+ out.set(head, 0);
110
+ out.set(payload, head.length);
111
+ return out;
112
+ }
113
+ const AXIOM_MANIFEST_PAYLOAD_TYPE$1 = "application/vnd.axiom.manifest+json";
114
+ const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
115
+ const PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
116
+ function toBuffer(u8) {
117
+ return Buffer.from(u8.buffer, u8.byteOffset, u8.byteLength);
118
+ }
119
+ function rawPublicKeyOf(key) {
120
+ const spki = key.export({
121
+ type: "spki",
122
+ format: "der"
123
+ });
124
+ return Buffer.from(spki.subarray(spki.length - 32));
125
+ }
126
+ function keyidFor(key) {
127
+ let raw;
128
+ if (typeof key === "string") raw = Buffer.from(key, "base64");
129
+ else if (key instanceof Uint8Array) raw = toBuffer(key);
130
+ else raw = rawPublicKeyOf(key.type === "private" ? createPublicKey(key) : key);
131
+ if (raw.length !== 32) throw new Error(`ed25519 public key must be 32 raw bytes`);
132
+ return createHash("sha256").update(raw).digest("hex");
133
+ }
134
+ function publicKeyBase64(key) {
135
+ return rawPublicKeyOf(key.type === "private" ? createPublicKey(key) : key).toString("base64");
136
+ }
137
+ function privateKeyFrom(material) {
138
+ const text = material.trim();
139
+ if (text.includes("-----BEGIN")) {
140
+ const key = createPrivateKey(text);
141
+ assertEd25519(key);
142
+ return key;
143
+ }
144
+ let der;
145
+ try {
146
+ der = Buffer.from(text, "base64");
147
+ } catch {
148
+ throw new Error("signing key is not valid base64");
149
+ }
150
+ if (der.length === 0) throw new Error("signing key is empty");
151
+ if (der.length === 32) return createPrivateKey({
152
+ key: Buffer.concat([PKCS8_PREFIX, der]),
153
+ format: "der",
154
+ type: "pkcs8"
155
+ });
156
+ const key = createPrivateKey({
157
+ key: der,
158
+ format: "der",
159
+ type: "pkcs8"
160
+ });
161
+ assertEd25519(key);
162
+ return key;
163
+ }
164
+ function publicKeyFrom(material) {
165
+ const text = material.trim();
166
+ if (text.includes("-----BEGIN")) {
167
+ const key = createPublicKey(text);
168
+ assertEd25519(key);
169
+ return key;
170
+ }
171
+ const der = Buffer.from(text, "base64");
172
+ if (der.length === 32) return createPublicKey({
173
+ key: Buffer.concat([SPKI_PREFIX, der]),
174
+ format: "der",
175
+ type: "spki"
176
+ });
177
+ const key = createPublicKey({
178
+ key: der,
179
+ format: "der",
180
+ type: "spki"
181
+ });
182
+ assertEd25519(key);
183
+ return key;
184
+ }
185
+ function assertEd25519(key) {
186
+ if (key.asymmetricKeyType !== "ed25519") throw new Error(`expected an ed25519 key, got ${String(key.asymmetricKeyType)}`);
187
+ }
188
+ function generateKeyPair() {
189
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
190
+ return {
191
+ keyid: keyidFor(publicKey),
192
+ privateKey,
193
+ publicKey,
194
+ privateKeyBase64: Buffer.from(privateKey.export({
195
+ type: "pkcs8",
196
+ format: "der"
197
+ })).toString("base64"),
198
+ publicKeyBase64: publicKeyBase64(publicKey)
199
+ };
200
+ }
201
+ function signEnvelope(body, privateKey, keyid, payloadType = AXIOM_MANIFEST_PAYLOAD_TYPE$1) {
202
+ assertEd25519(privateKey);
203
+ const payloadBytes = new TextEncoder().encode(canonicalize$1(body));
204
+ const message = toBuffer(pae(payloadType, payloadBytes));
205
+ const sig = sign(null, message, privateKey);
206
+ return {
207
+ payloadType,
208
+ payload: toBuffer(payloadBytes).toString("base64"),
209
+ signatures: [{
210
+ keyid: keyid ?? keyidFor(privateKey),
211
+ sig: sig.toString("base64")
212
+ }]
213
+ };
214
+ }
215
+ function eqKeyid(a, b) {
216
+ if (a.length !== b.length) return false;
217
+ return timingSafeEqual(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
218
+ }
219
+ function verifyEnvelope(env, trustedKeys, payloadType = AXIOM_MANIFEST_PAYLOAD_TYPE$1) {
220
+ if (env.payloadType !== payloadType) return {
221
+ ok: false,
222
+ keyids: [],
223
+ reason: "BAD_PAYLOAD_TYPE"
224
+ };
225
+ if (!Array.isArray(env.signatures) || env.signatures.length === 0) return {
226
+ ok: false,
227
+ keyids: [],
228
+ reason: "NO_SIGNATURES"
229
+ };
230
+ const payloadBytes = Buffer.from(env.payload, "base64");
231
+ const text = payloadBytes.toString("utf8");
232
+ let parsed;
233
+ try {
234
+ parsed = JSON.parse(text);
235
+ } catch {
236
+ return {
237
+ ok: false,
238
+ keyids: [],
239
+ reason: "BAD_PAYLOAD"
240
+ };
241
+ }
242
+ let canon;
243
+ try {
244
+ canon = canonicalize$1(parsed);
245
+ } catch {
246
+ return {
247
+ ok: false,
248
+ keyids: [],
249
+ reason: "NOT_CANONICAL"
250
+ };
251
+ }
252
+ if (canon !== text) return {
253
+ ok: false,
254
+ keyids: [],
255
+ reason: "NOT_CANONICAL"
256
+ };
257
+ const message = toBuffer(pae(env.payloadType, payloadBytes));
258
+ const keys = [];
259
+ for (const t of trustedKeys) {
260
+ if (t.alg !== "ed25519") continue;
261
+ try {
262
+ keys.push({
263
+ keyid: t.keyid,
264
+ key: publicKeyFrom(t.publicKey)
265
+ });
266
+ } catch {}
267
+ }
268
+ if (keys.length === 0) return {
269
+ ok: false,
270
+ keyids: [],
271
+ reason: "UNKNOWN_KEY"
272
+ };
273
+ const verified = /* @__PURE__ */ new Set();
274
+ let sawKnownKeyid = false;
275
+ for (const s of env.signatures) {
276
+ if (s.keyid !== void 0 && keys.some((k) => eqKeyid(k.keyid, s.keyid))) sawKnownKeyid = true;
277
+ let sig;
278
+ try {
279
+ sig = Buffer.from(s.sig, "base64");
280
+ } catch {
281
+ continue;
282
+ }
283
+ for (const k of keys) try {
284
+ if (verify(null, message, k.key, sig)) verified.add(k.keyid);
285
+ } catch {}
286
+ }
287
+ if (verified.size === 0) return {
288
+ ok: false,
289
+ keyids: [],
290
+ reason: sawKnownKeyid ? "BAD_SIGNATURE" : "UNKNOWN_KEY"
291
+ };
292
+ return {
293
+ ok: true,
294
+ keyids: [...verified].sort(),
295
+ payload: text
296
+ };
297
+ }
104
298
  const IN_TOTO_STATEMENT_V1 = "https://in-toto.io/Statement/v1";
105
299
  const SLSA_PROVENANCE_V1 = "https://slsa.dev/provenance/v1";
106
300
  const AXIOM_BUILD_TYPE = "https://axiom.dev/build/plan@v2";
@@ -6025,6 +6219,12 @@ const ERROR_CODES = [
6025
6219
  "ERR_PROVIDER_FAILED",
6026
6220
  "ERR_GUARD_TIMEOUT",
6027
6221
  "ERR_GUARD_OUTPUT",
6222
+ "ERR_SIGNATURE_MISSING",
6223
+ "ERR_SIGNATURE_INVALID",
6224
+ "ERR_ROLLBACK",
6225
+ "ERR_EMITTER_UNKNOWN",
6226
+ "ERR_TEMPLATE_UNKNOWN",
6227
+ "ERR_TEMPLATE_PARAMS",
6028
6228
  "ERR_GIT_NOT_FOUND",
6029
6229
  "ERR_GIT_NOT_REPO",
6030
6230
  "ERR_GIT_DIRTY",
@@ -6032,6 +6232,9 @@ const ERROR_CODES = [
6032
6232
  "ERR_GIT_BRANCH_INVALID",
6033
6233
  "ERR_GIT_FAILED",
6034
6234
  "ERR_REF_OFFLINE",
6235
+ "ERR_NET_DISABLED",
6236
+ "ERR_NET_DENIED",
6237
+ "ERR_NET_FAILED",
6035
6238
  "ERR_NOT_CANONICAL",
6036
6239
  "ERR_UNSUPPORTED_OP",
6037
6240
  "ERR_INTERNAL"
@@ -6208,6 +6411,7 @@ const PlanSchema = object$1({
6208
6411
  capabilities: array(CapabilitySchema).default([]),
6209
6412
  artifacts: array(PlanArtifactSchema).min(1).max(2e3),
6210
6413
  checks: array(CheckRefSchema).default([]),
6414
+ counter: int().nonnegative().optional(),
6211
6415
  metadata: record(string(), json$1()).default({})
6212
6416
  }).strict();
6213
6417
  const ErrorCodeSchema = _enum(ERROR_CODES);
@@ -6335,7 +6539,8 @@ const ManifestBodySchema = object$1({
6335
6539
  planDigest: DigestRefSchema,
6336
6540
  artifacts: array(ManifestArtifactSchema).min(1).max(2e3),
6337
6541
  checks: array(CheckRefSchema),
6338
- toolchain: ToolchainSchema
6542
+ toolchain: ToolchainSchema,
6543
+ counter: int().nonnegative().optional()
6339
6544
  }).strict().superRefine((m, ctx) => {
6340
6545
  if (!isSortedUnique(m.artifacts.map((a) => a.path))) ctx.addIssue({
6341
6546
  code: "custom",
@@ -6376,11 +6581,37 @@ const DsseEnvelopeSchema = object$1({
6376
6581
  sig: base64()
6377
6582
  }).strict())
6378
6583
  }).strict();
6584
+ const ManifestSignatureSchema = object$1({
6585
+ payloadType: literal("application/vnd.axiom.manifest+json"),
6586
+ payload: base64(),
6587
+ signatures: array(object$1({
6588
+ keyid: string().optional(),
6589
+ sig: base64()
6590
+ }).strict()).min(1)
6591
+ }).strict();
6592
+ const TrustedKeySchema = object$1({
6593
+ keyid: string().regex(/^[0-9a-f]{64}$/),
6594
+ alg: literal("ed25519"),
6595
+ publicKey: base64(),
6596
+ name: string().min(1).max(200).optional(),
6597
+ notBefore: int().nonnegative().optional()
6598
+ }).strict();
6599
+ const TrustStoreSchema = object$1({
6600
+ version: literal(1),
6601
+ keys: array(TrustedKeySchema),
6602
+ minCounter: int().nonnegative().optional()
6603
+ }).strict();
6604
+ const TrustStateSchema = object$1({
6605
+ version: literal(1),
6606
+ lastCounter: int().nonnegative(),
6607
+ manifestDigest: DigestRefSchema.optional()
6608
+ }).strict();
6379
6609
  const ManifestBundleSchema = object$1({
6380
6610
  manifest: ManifestBodySchema,
6381
6611
  manifestDigest: DigestRefSchema,
6382
6612
  attestation: InTotoStatementLooseSchema.optional(),
6383
6613
  envelope: DsseEnvelopeSchema.optional(),
6614
+ signatures: array(ManifestSignatureSchema).optional(),
6384
6615
  blobs: record(DigestRefSchema, BlobSchema).default({})
6385
6616
  }).strict().superRefine((b, ctx) => {
6386
6617
  let total = 0;
@@ -6415,6 +6646,28 @@ const ProfileSchema = object$1({
6415
6646
  limits: ProfileLimitsSchema.default({}),
6416
6647
  facts: ProfileFactsSchema.prefault({})
6417
6648
  }).strict();
6649
+ const RepoSnapshotEntryKindSchema = _enum(["file", "symlink"]);
6650
+ const RepoSnapshotBodySchema = object$1({
6651
+ files: array(object$1({
6652
+ path: RelPathSchema,
6653
+ bytes: int().nonnegative(),
6654
+ sha256: Sha256HexSchema.optional(),
6655
+ mode: ArtifactModeSchema,
6656
+ kind: RepoSnapshotEntryKindSchema
6657
+ }).strict()),
6658
+ truncated: boolean(),
6659
+ counts: object$1({
6660
+ files: int().nonnegative(),
6661
+ bytes: int().nonnegative()
6662
+ }).strict()
6663
+ }).strict();
6664
+ const RepoSnapshotSchema = object$1({
6665
+ apiVersion: ApiVersionSchema,
6666
+ kind: literal("RepoSnapshot"),
6667
+ root: object$1({ kind: literal("relative") }).strict(),
6668
+ snapshotDigest: DigestRefSchema,
6669
+ body: RepoSnapshotBodySchema
6670
+ }).strict();
6418
6671
  var Diff = class {
6419
6672
  diff(oldStr, newStr, options = {}) {
6420
6673
  let callback;
@@ -7198,7 +7451,7 @@ async function resolveContent(bundle, artifact, casDir) {
7198
7451
  });
7199
7452
  return bytes;
7200
7453
  }
7201
- const realpathNative$2 = promisify(realpath.native);
7454
+ const realpathNative$2 = promisify(realpath$1.native);
7202
7455
  function validateArtifactPaths(artifacts) {
7203
7456
  const seen = /* @__PURE__ */ new Map();
7204
7457
  for (const a of artifacts) {
@@ -9990,7 +10243,7 @@ function deriveManifestFacts(bundle) {
9990
10243
  const ext = extOf(a.path);
9991
10244
  byExt[ext] = (byExt[ext] ?? 0) + 1;
9992
10245
  }
9993
- const signed = bundle.envelope !== void 0 && bundle.envelope.signatures.length > 0;
10246
+ const signed = bundle.signatures !== void 0 && bundle.signatures.length > 0 || bundle.envelope !== void 0 && bundle.envelope.signatures.length > 0;
9994
10247
  return {
9995
10248
  artifactCount: bundle.manifest.artifacts.length,
9996
10249
  totalBytes,
@@ -10164,6 +10417,292 @@ function parseJsonObject(bytes) {
10164
10417
  return;
10165
10418
  }
10166
10419
  }
10420
+ const EXPRESSION_MAX_CHARS = 4096;
10421
+ const CEL_LIMITS = {
10422
+ maxDepth: 24,
10423
+ maxAstNodes: 2e3,
10424
+ maxListElements: 256,
10425
+ maxMapEntries: 256,
10426
+ maxCallArguments: 8
10427
+ };
10428
+ const CEL_ALLOWED_FUNCTIONS = /* @__PURE__ */ new Set([
10429
+ "has",
10430
+ "all",
10431
+ "exists",
10432
+ "exists_one",
10433
+ "map",
10434
+ "filter",
10435
+ "size",
10436
+ "contains",
10437
+ "startsWith",
10438
+ "endsWith",
10439
+ "matches",
10440
+ "lowerAscii",
10441
+ "upperAscii",
10442
+ "trim",
10443
+ "split",
10444
+ "join",
10445
+ "indexOf",
10446
+ "lastIndexOf",
10447
+ "substring",
10448
+ "string",
10449
+ "int",
10450
+ "uint",
10451
+ "double",
10452
+ "bool",
10453
+ "bytes",
10454
+ "dyn",
10455
+ "type"
10456
+ ]);
10457
+ const CelParams = object$1({
10458
+ expression: string().min(1).max(EXPRESSION_MAX_CHARS),
10459
+ message: string().max(2e3).optional(),
10460
+ severity: _enum([
10461
+ "error",
10462
+ "warn",
10463
+ "info"
10464
+ ]).optional()
10465
+ }).strict();
10466
+ const PREDICATE$1 = "expr.cel";
10467
+ const UNSAFE_REGEX = /\(\?<?[=!]|\\[1-9]|\\k</;
10468
+ let envPromise;
10469
+ function celEnvironment() {
10470
+ if (envPromise === void 0) envPromise = import("./lib-CqHwM4m_.js").then((m) => {
10471
+ let env = new m.Environment({
10472
+ limits: CEL_LIMITS,
10473
+ unlistedVariablesAreDyn: false
10474
+ });
10475
+ env = env.registerVariable("manifest", "map");
10476
+ env = env.registerVariable("artifacts", "list");
10477
+ env = env.registerVariable("content", "map");
10478
+ env = env.registerVariable("repo", "map");
10479
+ return env;
10480
+ });
10481
+ return envPromise;
10482
+ }
10483
+ function isNode(v) {
10484
+ return typeof v === "object" && v !== null && "op" in v && "args" in v;
10485
+ }
10486
+ function children(n) {
10487
+ const a = n.args;
10488
+ switch (n.op) {
10489
+ case "value":
10490
+ case "id": return [];
10491
+ case ".":
10492
+ case ".?": return Array.isArray(a) && isNode(a[0]) ? [a[0]] : [];
10493
+ case "call": return Array.isArray(a) && Array.isArray(a[1]) ? a[1].filter(isNode) : [];
10494
+ case "rcall": {
10495
+ if (!Array.isArray(a)) return [];
10496
+ const out = [];
10497
+ if (isNode(a[1])) out.push(a[1]);
10498
+ if (Array.isArray(a[2])) out.push(...a[2].filter(isNode));
10499
+ return out;
10500
+ }
10501
+ case "map": return Array.isArray(a) ? a.flatMap((e) => Array.isArray(e) ? e.filter(isNode) : isNode(e) ? [e] : []) : [];
10502
+ default:
10503
+ if (isNode(a)) return [a];
10504
+ return Array.isArray(a) ? a.filter(isNode) : [];
10505
+ }
10506
+ }
10507
+ function analyzeAst(root) {
10508
+ const out = {
10509
+ deniedCalls: [],
10510
+ unsafeRegex: [],
10511
+ variables: /* @__PURE__ */ new Set()
10512
+ };
10513
+ const stack = [root];
10514
+ while (stack.length > 0) {
10515
+ const n = stack.pop();
10516
+ if (n === void 0) break;
10517
+ if (n.op === "id" && typeof n.args === "string") out.variables.add(n.args);
10518
+ if (n.op === "call" || n.op === "rcall") {
10519
+ const args = n.args;
10520
+ const name = typeof args[0] === "string" ? args[0] : "";
10521
+ if (!CEL_ALLOWED_FUNCTIONS.has(name)) out.deniedCalls.push(name);
10522
+ if (name === "matches") {
10523
+ const params = n.op === "rcall" ? args[2] : args[1].slice(1);
10524
+ const pat = Array.isArray(params) ? params[0] : void 0;
10525
+ if (!isNode(pat) || pat.op !== "value" || typeof pat.args !== "string") out.unsafeRegex.push("<non-literal pattern>");
10526
+ else if (UNSAFE_REGEX.test(pat.args)) out.unsafeRegex.push(pat.args);
10527
+ }
10528
+ }
10529
+ const kids = children(n);
10530
+ for (let i = kids.length - 1; i >= 0; i--) {
10531
+ const k = kids[i];
10532
+ if (k !== void 0) stack.push(k);
10533
+ }
10534
+ }
10535
+ return out;
10536
+ }
10537
+ function toCelValue(v) {
10538
+ if (typeof v === "number") return Number.isInteger(v) ? BigInt(v) : v;
10539
+ if (Array.isArray(v)) return v.map(toCelValue);
10540
+ if (v instanceof Uint8Array) return v;
10541
+ if (typeof v === "object" && v !== null) {
10542
+ const out = {};
10543
+ for (const [k, val] of Object.entries(v)) out[k] = toCelValue(val);
10544
+ return out;
10545
+ }
10546
+ return v;
10547
+ }
10548
+ async function buildContentMap(ctx) {
10549
+ const out = {};
10550
+ const decoder = new TextDecoder("utf-8", { fatal: true });
10551
+ for (const a of ctx.manifest.artifacts) {
10552
+ if (a.op === "delete" || a.digest === void 0) continue;
10553
+ const bytes = await ctx.facts.content(a.path);
10554
+ if (bytes === void 0) continue;
10555
+ const entry = {
10556
+ bytes: BigInt(bytes.length),
10557
+ sha256: a.digest.sha256
10558
+ };
10559
+ if (bytes.length <= 262144 && isUtf8(bytes)) entry.text = decoder.decode(bytes);
10560
+ out[a.path] = entry;
10561
+ }
10562
+ return out;
10563
+ }
10564
+ async function buildRepoMap(ctx) {
10565
+ const repo = ctx.facts.repo;
10566
+ if (repo === void 0) return void 0;
10567
+ const exists = {};
10568
+ for (const a of ctx.manifest.artifacts) exists[a.path] = await repo.exists(a.path);
10569
+ const out = { exists };
10570
+ if (repo.packageJson !== void 0) out.packageJson = toCelValue(repo.packageJson);
10571
+ if (repo.gitHead !== void 0) out.gitHead = repo.gitHead;
10572
+ if (repo.gitDirty !== void 0) out.gitDirty = repo.gitDirty;
10573
+ return out;
10574
+ }
10575
+ function manifestActivation(manifest) {
10576
+ const m = toCelValue(manifest);
10577
+ return {
10578
+ manifest: m,
10579
+ artifacts: m.artifacts
10580
+ };
10581
+ }
10582
+ function errorMessage$1(e) {
10583
+ if (e instanceof Error) {
10584
+ const code = e.code;
10585
+ const first = e.message.split("\n")[0] ?? e.message;
10586
+ return typeof code === "string" ? `${code}: ${first}` : first;
10587
+ }
10588
+ return String(e);
10589
+ }
10590
+ async function evaluateCel(expression, activation, budgetMs = 100) {
10591
+ if (expression.length > 4096) return {
10592
+ kind: "error",
10593
+ code: "ERR_PREDICATE_PARAMS",
10594
+ message: `expression exceeds ${EXPRESSION_MAX_CHARS} characters`
10595
+ };
10596
+ const env = await celEnvironment();
10597
+ let compiled;
10598
+ try {
10599
+ compiled = env.parse(expression);
10600
+ } catch (e) {
10601
+ return {
10602
+ kind: "error",
10603
+ code: "ERR_PREDICATE_PARAMS",
10604
+ message: `parse: ${errorMessage$1(e)}`
10605
+ };
10606
+ }
10607
+ const analysis = analyzeAst(compiled.ast);
10608
+ if (analysis.deniedCalls.length > 0) return {
10609
+ kind: "error",
10610
+ code: "ERR_PREDICATE_PARAMS",
10611
+ message: `function not allowed: ${[...new Set(analysis.deniedCalls)].join(", ")}`
10612
+ };
10613
+ if (analysis.unsafeRegex.length > 0) return {
10614
+ kind: "error",
10615
+ code: "ERR_PREDICATE_PARAMS",
10616
+ message: `matches() pattern must be a literal RE2-safe regex: ${analysis.unsafeRegex.join(", ")}`
10617
+ };
10618
+ if (analysis.variables.has("repo") && activation.repo === void 0) return {
10619
+ kind: "error",
10620
+ code: "ERR_PROVIDER_FAILED",
10621
+ message: "expression references `repo` but repo facts are unavailable (no authorised root)"
10622
+ };
10623
+ const context = {
10624
+ manifest: activation.manifest,
10625
+ artifacts: activation.artifacts,
10626
+ content: activation.content
10627
+ };
10628
+ if (activation.repo !== void 0) context.repo = activation.repo;
10629
+ const t0 = performance.now();
10630
+ let value;
10631
+ try {
10632
+ value = compiled(context);
10633
+ } catch (e) {
10634
+ return {
10635
+ kind: "error",
10636
+ code: "ERR_PROVIDER_FAILED",
10637
+ message: `eval: ${errorMessage$1(e)}`
10638
+ };
10639
+ }
10640
+ const ms = performance.now() - t0;
10641
+ if (ms > budgetMs) return {
10642
+ kind: "error",
10643
+ code: "ERR_PROVIDER_FAILED",
10644
+ message: `evaluation took ${Math.round(ms)} ms (budget ${budgetMs} ms)`
10645
+ };
10646
+ if (typeof value !== "boolean") return {
10647
+ kind: "error",
10648
+ code: "ERR_PROVIDER_FAILED",
10649
+ message: `expression must evaluate to bool, got ${celTypeName(value)}`
10650
+ };
10651
+ return {
10652
+ kind: "value",
10653
+ value,
10654
+ ms
10655
+ };
10656
+ }
10657
+ function celTypeName(v) {
10658
+ if (v === null) return "null";
10659
+ if (typeof v === "bigint") return "int";
10660
+ if (typeof v === "number") return "double";
10661
+ if (Array.isArray(v)) return "list";
10662
+ if (v instanceof Uint8Array) return "bytes";
10663
+ return typeof v;
10664
+ }
10665
+ function providerError$2(code, message, expression) {
10666
+ return finding({
10667
+ id: PREDICATE$1,
10668
+ predicate: PREDICATE$1,
10669
+ message,
10670
+ facts: {
10671
+ code,
10672
+ __provider: true,
10673
+ expression
10674
+ }
10675
+ });
10676
+ }
10677
+ const exprCel = definePredicate({
10678
+ id: PREDICATE$1,
10679
+ params: CelParams,
10680
+ requires: ["manifest", "content"],
10681
+ async run(ctx, { expression, message, severity }) {
10682
+ const { manifest, artifacts } = manifestActivation(ctx.manifest);
10683
+ const mentions = (name) => new RegExp(`\\b${name}\\b`).test(expression);
10684
+ const activation = {
10685
+ manifest,
10686
+ artifacts,
10687
+ content: mentions("content") ? await buildContentMap(ctx) : {}
10688
+ };
10689
+ if (mentions("repo")) {
10690
+ const repo = await buildRepoMap(ctx);
10691
+ if (repo !== void 0) activation.repo = repo;
10692
+ }
10693
+ const r = await evaluateCel(expression, activation);
10694
+ if (r.kind === "error") return [providerError$2(r.code, r.message, expression)];
10695
+ if (r.value) return [];
10696
+ const f = finding({
10697
+ id: PREDICATE$1,
10698
+ predicate: PREDICATE$1,
10699
+ message: message ?? `expression evaluated to false: ${expression}`,
10700
+ facts: { expression }
10701
+ });
10702
+ if (severity !== void 0) f.severity = severity;
10703
+ return [f];
10704
+ }
10705
+ });
10167
10706
  const SECRET_PATTERNS = [
10168
10707
  {
10169
10708
  name: "cnp",
@@ -10390,7 +10929,7 @@ const GuardOutputSchema = object$1({
10390
10929
  const PREDICATE = "guard.external";
10391
10930
  const OUTPUT_MAX = 8388608;
10392
10931
  const IS_WIN32$1 = process.platform === "win32";
10393
- const realpathNative$1 = promisify(realpath.native);
10932
+ const realpathNative$1 = promisify(realpath$1.native);
10394
10933
  const ENV_WHITELIST = [
10395
10934
  "PATH",
10396
10935
  "HOME",
@@ -10423,7 +10962,7 @@ function isInside(parent, child) {
10423
10962
  const c = norm$1(child);
10424
10963
  return c !== p && c.startsWith(p + path.sep);
10425
10964
  }
10426
- function providerError(code, message, extra = {}) {
10965
+ function providerError$1(code, message, extra = {}) {
10427
10966
  return finding({
10428
10967
  id: PREDICATE,
10429
10968
  predicate: PREDICATE,
@@ -10449,22 +10988,22 @@ function guardEnv(extra, digest, root, base = process.env) {
10449
10988
  }
10450
10989
  async function resolveGuardCommand(command, args, root, allowlist) {
10451
10990
  const segments = command.split(/[\\/]+/);
10452
- if (segments.includes("..")) return providerError("ERR_PREDICATE_PARAMS", `guard command must not contain "..": ${command}`, { command });
10991
+ if (segments.includes("..")) return providerError$1("ERR_PREDICATE_PARAMS", `guard command must not contain "..": ${command}`, { command });
10453
10992
  let real;
10454
10993
  if (path.isAbsolute(command)) {
10455
10994
  real = await realpathSafe(command);
10456
- if (real === void 0) return providerError("ERR_PREDICATE_PARAMS", `guard command not found: ${command}`, { command });
10995
+ if (real === void 0) return providerError$1("ERR_PREDICATE_PARAMS", `guard command not found: ${command}`, { command });
10457
10996
  const allowed = /* @__PURE__ */ new Set();
10458
10997
  for (const a of allowlist) {
10459
10998
  const r = await realpathSafe(a);
10460
10999
  if (r !== void 0) allowed.add(norm$1(r));
10461
11000
  }
10462
- if (!allowed.has(norm$1(real))) return providerError("ERR_PREDICATE_PARAMS", `absolute guard command is not in --guard-allowlist: ${command}`, { command });
11001
+ if (!allowed.has(norm$1(real))) return providerError$1("ERR_PREDICATE_PARAMS", `absolute guard command is not in --guard-allowlist: ${command}`, { command });
10463
11002
  } else {
10464
11003
  const scriptsDir = path.join(root, "scripts");
10465
11004
  real = await realpathSafe(segments[0] === "scripts" ? path.resolve(root, command) : path.resolve(scriptsDir, command));
10466
11005
  const scriptsReal = await realpathSafe(scriptsDir);
10467
- if (real === void 0 || scriptsReal === void 0 || !isInside(scriptsReal, real)) return providerError("ERR_PREDICATE_PARAMS", `relative guard command must resolve inside <root>/scripts/: ${command}`, { command });
11006
+ if (real === void 0 || scriptsReal === void 0 || !isInside(scriptsReal, real)) return providerError$1("ERR_PREDICATE_PARAMS", `relative guard command must resolve inside <root>/scripts/: ${command}`, { command });
10468
11007
  }
10469
11008
  const ext = path.extname(real).toLowerCase();
10470
11009
  if (ext === ".mjs" || ext === ".js" || ext === ".cjs") return {
@@ -10482,7 +11021,7 @@ async function resolveGuardCommand(command, args, root, allowlist) {
10482
11021
  ...args
10483
11022
  ]
10484
11023
  };
10485
- if (!path.isAbsolute(command)) return providerError("ERR_PREDICATE_PARAMS", `relative guard command must be .mjs/.js/.cjs/.ps1: ${command}`, { command });
11024
+ if (!path.isAbsolute(command)) return providerError$1("ERR_PREDICATE_PARAMS", `relative guard command must be .mjs/.js/.cjs/.ps1: ${command}`, { command });
10486
11025
  return {
10487
11026
  file: real,
10488
11027
  argv: [...args]
@@ -10620,25 +11159,25 @@ function mapOutput(out, command) {
10620
11159
  }
10621
11160
  async function runGuard(ctx, params) {
10622
11161
  const guard = ctx.facts.guard;
10623
- if (guard === void 0 || !guard.enabled || !ctx.facts.profile.allowGuards) return [providerError("ERR_UNSUPPORTED_OP", "external guards disabled (needs profile facts.allowGuards and --allow-guards)", { command: params.command })];
11162
+ if (guard === void 0 || !guard.enabled || !ctx.facts.profile.allowGuards) return [providerError$1("ERR_UNSUPPORTED_OP", "external guards disabled (needs profile facts.allowGuards and --allow-guards)", { command: params.command })];
10624
11163
  const resolved = await resolveGuardCommand(params.command, params.args, guard.root, guard.allowlist);
10625
11164
  if ("id" in resolved) return [resolved];
10626
11165
  let cwd = guard.root;
10627
11166
  if (params.cwd === "staging") {
10628
- if (guard.stagingDir === void 0) return [providerError("ERR_PREDICATE_PARAMS", "cwd \"staging\" requested but no staging dir", { command: params.command })];
11167
+ if (guard.stagingDir === void 0) return [providerError$1("ERR_PREDICATE_PARAMS", "cwd \"staging\" requested but no staging dir", { command: params.command })];
10629
11168
  cwd = guard.stagingDir;
10630
11169
  }
10631
11170
  const input = params.stdin === "none" ? void 0 : canonicalize$1(params.stdin === "bundle" ? ctx.bundle : ctx.manifest);
10632
11171
  const env = guardEnv(params.env, ctx.bundle.manifestDigest, guard.root);
10633
11172
  const r = await runChild(resolved, cwd, env, input, params.timeoutMs);
10634
- if (r.timedOut) return [providerError("ERR_GUARD_TIMEOUT", `guard timed out after ${params.timeoutMs} ms`, {
11173
+ if (r.timedOut) return [providerError$1("ERR_GUARD_TIMEOUT", `guard timed out after ${params.timeoutMs} ms`, {
10635
11174
  command: params.command,
10636
11175
  timeoutMs: params.timeoutMs,
10637
11176
  stderr: r.stderr
10638
11177
  })];
10639
- if (r.spawnError !== void 0) return [providerError("ERR_GUARD_OUTPUT", `guard could not be spawned: ${r.spawnError}`, { command: params.command })];
11178
+ if (r.spawnError !== void 0) return [providerError$1("ERR_GUARD_OUTPUT", `guard could not be spawned: ${r.spawnError}`, { command: params.command })];
10640
11179
  const out = parseJsonOutput(r.stdout) ?? (params.legacyText ? parseLegacyText(r.stdout) : void 0);
10641
- if (out === void 0) return [providerError("ERR_GUARD_OUTPUT", `guard stdout is not a GuardOutput JSON object (exit ${r.code ?? "null"})`, {
11180
+ if (out === void 0) return [providerError$1("ERR_GUARD_OUTPUT", `guard stdout is not a GuardOutput JSON object (exit ${r.code ?? "null"})`, {
10642
11181
  command: params.command,
10643
11182
  exitCode: r.code,
10644
11183
  stdout: r.stdout.slice(-4096),
@@ -10690,20 +11229,6 @@ const manifestMaxTotalBytes = definePredicate({
10690
11229
  })];
10691
11230
  }
10692
11231
  });
10693
- const manifestRequireSigned = definePredicate({
10694
- id: "manifest.requireSigned",
10695
- params: Empty,
10696
- requires: ["manifest"],
10697
- async run(ctx) {
10698
- if (ctx.facts.manifest.signed) return [];
10699
- return [finding({
10700
- id: "manifest.requireSigned",
10701
- predicate: "manifest.requireSigned",
10702
- message: "bundle has no DSSE signature",
10703
- facts: { signed: false }
10704
- })];
10705
- }
10706
- });
10707
11232
  const manifestNoDeletes = definePredicate({
10708
11233
  id: "manifest.noDeletes",
10709
11234
  params: Empty,
@@ -10718,59 +11243,283 @@ const manifestNoDeletes = definePredicate({
10718
11243
  }
10719
11244
  });
10720
11245
  const Globs = object$1({ globs: array(string().min(1)).min(1) }).strict();
10721
- const BUILTIN_PREDICATES = [
10722
- definePredicate({
10723
- id: "path.allow",
10724
- params: Globs,
10725
- requires: ["manifest"],
10726
- async run(ctx, { globs }) {
10727
- const allowed = globMatcher(globs, false);
10728
- return ctx.facts.manifest.paths.filter((p) => !allowed(p)).map((p) => finding({
10729
- id: "path.allow",
10730
- predicate: "path.allow",
10731
- path: p,
10732
- message: `path is outside the allowed globs`,
10733
- facts: { globs }
11246
+ const pathAllow = definePredicate({
11247
+ id: "path.allow",
11248
+ params: Globs,
11249
+ requires: ["manifest"],
11250
+ async run(ctx, { globs }) {
11251
+ const allowed = globMatcher(globs, false);
11252
+ return ctx.facts.manifest.paths.filter((p) => !allowed(p)).map((p) => finding({
11253
+ id: "path.allow",
11254
+ predicate: "path.allow",
11255
+ path: p,
11256
+ message: `path is outside the allowed globs`,
11257
+ facts: { globs }
11258
+ }));
11259
+ }
11260
+ });
11261
+ const pathDeny = definePredicate({
11262
+ id: "path.deny",
11263
+ params: Globs,
11264
+ requires: ["manifest"],
11265
+ async run(ctx, { globs }) {
11266
+ const denied = globMatcher(globs, false);
11267
+ return ctx.facts.manifest.paths.filter(denied).map((p) => finding({
11268
+ id: "path.deny",
11269
+ predicate: "path.deny",
11270
+ path: p,
11271
+ message: `path matches a denied glob`,
11272
+ facts: { globs }
11273
+ }));
11274
+ }
11275
+ });
11276
+ const pathReservedNames = definePredicate({
11277
+ id: "path.reservedNames",
11278
+ params: object$1({}).strict(),
11279
+ requires: ["manifest"],
11280
+ async run(ctx) {
11281
+ const out = [];
11282
+ for (const p of ctx.facts.manifest.paths) {
11283
+ const issues = relPathIssues(p);
11284
+ if (issues.length === 0) continue;
11285
+ out.push(finding({
11286
+ id: "path.reservedNames",
11287
+ predicate: "path.reservedNames",
11288
+ message: `path violates relative-POSIX rules: ${issues.join(", ")}`,
11289
+ facts: {
11290
+ path: p,
11291
+ issues
11292
+ }
10734
11293
  }));
10735
11294
  }
10736
- }),
10737
- definePredicate({
10738
- id: "path.deny",
10739
- params: Globs,
10740
- requires: ["manifest"],
10741
- async run(ctx, { globs }) {
10742
- const denied = globMatcher(globs, false);
10743
- return ctx.facts.manifest.paths.filter(denied).map((p) => finding({
10744
- id: "path.deny",
10745
- predicate: "path.deny",
10746
- path: p,
10747
- message: `path matches a denied glob`,
10748
- facts: { globs }
11295
+ return out;
11296
+ }
11297
+ });
11298
+ const repoNoOverwriteOf = definePredicate({
11299
+ id: "repo.noOverwriteOf",
11300
+ params: object$1({ globs: array(string().min(1)).min(1) }).strict(),
11301
+ requires: ["manifest", "repo"],
11302
+ async run(ctx, { globs }) {
11303
+ const repo = ctx.facts.repo;
11304
+ if (repo === void 0) return [];
11305
+ const protectedPath = globMatcher(globs, false);
11306
+ const out = [];
11307
+ for (const a of ctx.manifest.artifacts) {
11308
+ if (a.op === "create" || !protectedPath(a.path)) continue;
11309
+ if (!await repo.exists(a.path)) continue;
11310
+ out.push(finding({
11311
+ id: "repo.noOverwriteOf",
11312
+ predicate: "repo.noOverwriteOf",
11313
+ path: a.path,
11314
+ message: `${a.op} of protected existing file`,
11315
+ facts: {
11316
+ op: a.op,
11317
+ globs
11318
+ }
10749
11319
  }));
10750
11320
  }
10751
- }),
10752
- definePredicate({
10753
- id: "path.reservedNames",
10754
- params: object$1({}).strict(),
10755
- requires: ["manifest"],
10756
- async run(ctx) {
10757
- const out = [];
10758
- for (const p of ctx.facts.manifest.paths) {
10759
- const issues = relPathIssues(p);
10760
- if (issues.length === 0) continue;
11321
+ return out;
11322
+ }
11323
+ });
11324
+ const repoRequireCompanion = definePredicate({
11325
+ id: "repo.requireCompanion",
11326
+ params: object$1({ rules: array(object$1({
11327
+ when: string().min(1),
11328
+ expect: array(object$1({
11329
+ name: string().min(1),
11330
+ match: string().min(1)
11331
+ }).strict()).min(1)
11332
+ }).strict()).min(1) }).strict(),
11333
+ requires: ["manifest", "repo"],
11334
+ async run(ctx, { rules }) {
11335
+ const paths = ctx.facts.manifest.paths;
11336
+ const out = [];
11337
+ for (const rule of rules) {
11338
+ const when = globMatcher([rule.when], false);
11339
+ const triggers = paths.filter(when);
11340
+ if (triggers.length === 0) continue;
11341
+ for (const exp of rule.expect) {
11342
+ const match = globMatcher([exp.match], false);
11343
+ if (paths.some(match)) continue;
11344
+ if ((ctx.facts.repo ? await ctx.facts.repo.glob(exp.match) : []).length > 0) continue;
10761
11345
  out.push(finding({
10762
- id: "path.reservedNames",
10763
- predicate: "path.reservedNames",
10764
- message: `path violates relative-POSIX rules: ${issues.join(", ")}`,
11346
+ id: `repo.requireCompanion.${exp.name}`,
11347
+ predicate: "repo.requireCompanion",
11348
+ message: `"${rule.when}" changed but no companion matches "${exp.match}" (${exp.name})`,
10765
11349
  facts: {
10766
- path: p,
10767
- issues
11350
+ when: rule.when,
11351
+ expect: exp.match,
11352
+ name: exp.name,
11353
+ triggers
10768
11354
  }
10769
11355
  }));
10770
11356
  }
10771
- return out;
10772
11357
  }
10773
- }),
11358
+ return out;
11359
+ }
11360
+ });
11361
+ const PREDICATE_ID = "manifest.requireSigned";
11362
+ const TRUST_FILE_DEFAULT = ".axiom/trust/keys.json";
11363
+ const TRUST_STATE_FILE = ".axiom/trust/state.json";
11364
+ const TRUST_FILE_MAX = 262144;
11365
+ const RequireSignedParams = object$1({
11366
+ minSignatures: int().min(1).max(16).default(1),
11367
+ antiRollback: boolean().default(false),
11368
+ trustFile: string().min(1).default(TRUST_FILE_DEFAULT)
11369
+ }).strict();
11370
+ function providerError(code, message, extra = {}) {
11371
+ return finding({
11372
+ id: PREDICATE_ID,
11373
+ predicate: PREDICATE_ID,
11374
+ message,
11375
+ facts: {
11376
+ code,
11377
+ __provider: true,
11378
+ ...extra
11379
+ }
11380
+ });
11381
+ }
11382
+ function fail(id, message, facts = {}) {
11383
+ return finding({
11384
+ id,
11385
+ predicate: PREDICATE_ID,
11386
+ message,
11387
+ facts
11388
+ });
11389
+ }
11390
+ async function readJsonFile(ctx, rel) {
11391
+ const repo = ctx.facts.repo;
11392
+ if (repo === void 0) return { error: "no root" };
11393
+ let bytes;
11394
+ try {
11395
+ bytes = await repo.read(rel, TRUST_FILE_MAX);
11396
+ } catch (e) {
11397
+ return { error: e instanceof Error ? e.message : String(e) };
11398
+ }
11399
+ if (bytes === void 0) return { missing: true };
11400
+ try {
11401
+ return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) };
11402
+ } catch (e) {
11403
+ return { error: e instanceof Error ? e.message : String(e) };
11404
+ }
11405
+ }
11406
+ async function loadTrustStore$1(ctx, trustFile) {
11407
+ if (ctx.facts.repo === void 0) return { finding: providerError("ERR_PROVIDER_FAILED", "manifest.requireSigned needs a repo root to read the trust store") };
11408
+ const r = await readJsonFile(ctx, trustFile);
11409
+ if ("missing" in r) return { finding: providerError("ERR_NOT_FOUND", `trust store ${trustFile} not found`, { trustFile }) };
11410
+ if ("error" in r) return { finding: providerError("ERR_PROVIDER_FAILED", `trust store ${trustFile}: ${r.error}`, { trustFile }) };
11411
+ const parsed = TrustStoreSchema.safeParse(r.value);
11412
+ if (!parsed.success) return { finding: providerError("ERR_PROVIDER_FAILED", `trust store ${trustFile} is invalid`, {
11413
+ trustFile,
11414
+ issues: parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".")}: ${i.message}`)
11415
+ }) };
11416
+ return { store: parsed.data };
11417
+ }
11418
+ async function loadTrustState$1(ctx) {
11419
+ const r = await readJsonFile(ctx, TRUST_STATE_FILE);
11420
+ if ("missing" in r) return { state: void 0 };
11421
+ if ("error" in r) return { finding: providerError("ERR_PROVIDER_FAILED", `trust state ${TRUST_STATE_FILE}: ${r.error}`) };
11422
+ const parsed = TrustStateSchema.safeParse(r.value);
11423
+ if (!parsed.success) return { finding: providerError("ERR_JOURNAL_CORRUPT", `trust state ${TRUST_STATE_FILE} is invalid`) };
11424
+ return { state: parsed.data };
11425
+ }
11426
+ function verifyBundleSignatures(bundle, store, counter) {
11427
+ const findings = [];
11428
+ const envs = bundle.signatures ?? [];
11429
+ if (envs.length === 0) return {
11430
+ keyids: [],
11431
+ findings: [fail("signature.missing", "bundle carries no signatures")]
11432
+ };
11433
+ const keyids = /* @__PURE__ */ new Set();
11434
+ const eligible = store.keys.filter((k) => k.notBefore === void 0 || counter !== void 0 && counter >= k.notBefore);
11435
+ for (const [i, env] of envs.entries()) {
11436
+ const payloadDigest = `sha256:${sha256Hex(Buffer.from(env.payload, "base64"))}`;
11437
+ if (payloadDigest !== bundle.manifestDigest) {
11438
+ findings.push(fail("signature.bad", `signatures[${i}] signs a different manifest`, {
11439
+ reason: "PAYLOAD_MISMATCH",
11440
+ payloadDigest
11441
+ }));
11442
+ continue;
11443
+ }
11444
+ const r = verifyEnvelope(env, eligible);
11445
+ if (r.ok) {
11446
+ for (const k of r.keyids) keyids.add(k);
11447
+ continue;
11448
+ }
11449
+ const detail = {
11450
+ reason: r.reason,
11451
+ index: i
11452
+ };
11453
+ switch (r.reason) {
11454
+ case "NOT_CANONICAL":
11455
+ findings.push(fail("signature.notCanonical", `signatures[${i}] payload is not JCS-canonical`, detail));
11456
+ break;
11457
+ case "UNKNOWN_KEY":
11458
+ findings.push(fail("signature.unknownKey", `signatures[${i}] is not from a trusted key`, {
11459
+ ...detail,
11460
+ hinted: env.signatures.map((s) => s.keyid ?? null)
11461
+ }));
11462
+ break;
11463
+ default: findings.push(fail("signature.bad", `signatures[${i}] failed verification`, detail));
11464
+ }
11465
+ }
11466
+ return {
11467
+ keyids: [...keyids].sort(),
11468
+ findings
11469
+ };
11470
+ }
11471
+ const manifestRequireSigned = definePredicate({
11472
+ id: PREDICATE_ID,
11473
+ params: RequireSignedParams,
11474
+ requires: ["manifest"],
11475
+ async run(ctx, params) {
11476
+ const loaded = await loadTrustStore$1(ctx, params.trustFile);
11477
+ if ("finding" in loaded) return [loaded.finding];
11478
+ const store = loaded.store;
11479
+ const counter = ctx.manifest.counter;
11480
+ const { keyids, findings } = verifyBundleSignatures(ctx.bundle, store, counter);
11481
+ const out = [];
11482
+ if (keyids.length < params.minSignatures) {
11483
+ out.push(...findings);
11484
+ if (findings.length === 0 || keyids.length > 0) out.push(fail("signature.missing", `need ${params.minSignatures} trusted signature(s), have ${keyids.length}`, {
11485
+ have: keyids,
11486
+ need: params.minSignatures
11487
+ }));
11488
+ return dedupe(out);
11489
+ }
11490
+ if (params.antiRollback) {
11491
+ if (counter === void 0) return [fail("signature.rollback", "antiRollback requires manifest.counter", { reason: "NO_COUNTER" })];
11492
+ const st = await loadTrustState$1(ctx);
11493
+ if ("finding" in st) return [st.finding];
11494
+ const last = st.state?.lastCounter;
11495
+ const floor = store.minCounter;
11496
+ if (last !== void 0 && counter <= last) return [fail("signature.rollback", `counter ${counter} is not greater than last ${last}`, {
11497
+ reason: "ROLLBACK",
11498
+ counter,
11499
+ lastCounter: last
11500
+ })];
11501
+ if (floor !== void 0 && counter < floor) return [fail("signature.rollback", `counter ${counter} is below trust minCounter ${floor}`, {
11502
+ reason: "BELOW_MIN",
11503
+ counter,
11504
+ minCounter: floor
11505
+ })];
11506
+ }
11507
+ return [];
11508
+ }
11509
+ });
11510
+ function dedupe(findings) {
11511
+ const seen = /* @__PURE__ */ new Set();
11512
+ return findings.filter((f) => {
11513
+ const k = `${f.id}|${f.message}`;
11514
+ if (seen.has(k)) return false;
11515
+ seen.add(k);
11516
+ return true;
11517
+ });
11518
+ }
11519
+ const BUILTIN_PREDICATES = [
11520
+ pathAllow,
11521
+ pathDeny,
11522
+ pathReservedNames,
10774
11523
  contentNoSecrets,
10775
11524
  contentMaxBytes,
10776
11525
  contentEncodingUtf8,
@@ -10780,70 +11529,10 @@ const BUILTIN_PREDICATES = [
10780
11529
  manifestNoDeletes,
10781
11530
  depsMax,
10782
11531
  depsDeny,
10783
- definePredicate({
10784
- id: "repo.noOverwriteOf",
10785
- params: object$1({ globs: array(string().min(1)).min(1) }).strict(),
10786
- requires: ["manifest", "repo"],
10787
- async run(ctx, { globs }) {
10788
- const repo = ctx.facts.repo;
10789
- if (repo === void 0) return [];
10790
- const protectedPath = globMatcher(globs, false);
10791
- const out = [];
10792
- for (const a of ctx.manifest.artifacts) {
10793
- if (a.op === "create" || !protectedPath(a.path)) continue;
10794
- if (!await repo.exists(a.path)) continue;
10795
- out.push(finding({
10796
- id: "repo.noOverwriteOf",
10797
- predicate: "repo.noOverwriteOf",
10798
- path: a.path,
10799
- message: `${a.op} of protected existing file`,
10800
- facts: {
10801
- op: a.op,
10802
- globs
10803
- }
10804
- }));
10805
- }
10806
- return out;
10807
- }
10808
- }),
10809
- definePredicate({
10810
- id: "repo.requireCompanion",
10811
- params: object$1({ rules: array(object$1({
10812
- when: string().min(1),
10813
- expect: array(object$1({
10814
- name: string().min(1),
10815
- match: string().min(1)
10816
- }).strict()).min(1)
10817
- }).strict()).min(1) }).strict(),
10818
- requires: ["manifest", "repo"],
10819
- async run(ctx, { rules }) {
10820
- const paths = ctx.facts.manifest.paths;
10821
- const out = [];
10822
- for (const rule of rules) {
10823
- const when = globMatcher([rule.when], false);
10824
- const triggers = paths.filter(when);
10825
- if (triggers.length === 0) continue;
10826
- for (const exp of rule.expect) {
10827
- const match = globMatcher([exp.match], false);
10828
- if (paths.some(match)) continue;
10829
- if ((ctx.facts.repo ? await ctx.facts.repo.glob(exp.match) : []).length > 0) continue;
10830
- out.push(finding({
10831
- id: `repo.requireCompanion.${exp.name}`,
10832
- predicate: "repo.requireCompanion",
10833
- message: `"${rule.when}" changed but no companion matches "${exp.match}" (${exp.name})`,
10834
- facts: {
10835
- when: rule.when,
10836
- expect: exp.match,
10837
- name: exp.name,
10838
- triggers
10839
- }
10840
- }));
10841
- }
10842
- }
10843
- return out;
10844
- }
10845
- }),
10846
- guardExternal
11532
+ repoNoOverwriteOf,
11533
+ repoRequireCompanion,
11534
+ guardExternal,
11535
+ exprCel
10847
11536
  ];
10848
11537
  const MiB = 1048576;
10849
11538
  const BUILTIN_PROFILE_INPUTS = {
@@ -11091,7 +11780,7 @@ function errorMessage(e) {
11091
11780
  return e instanceof Error ? e.message : String(e);
11092
11781
  }
11093
11782
  function ms(start) {
11094
- return Math.max(0, Math.round(performance.now() - start));
11783
+ return Math.max(0, Math.round(performance$1.now() - start));
11095
11784
  }
11096
11785
  const GUARD_POOL_SIZE = Math.max(1, Math.min(4, cpus().length));
11097
11786
  async function runPool(items, size, fn) {
@@ -11101,21 +11790,21 @@ async function runPool(items, size, fn) {
11101
11790
  }));
11102
11791
  }
11103
11792
  async function runChecks(opts) {
11104
- const t0 = performance.now();
11793
+ const t0 = performance$1.now();
11105
11794
  const { bundle, profile } = opts;
11106
11795
  const registry = opts.registry ?? builtinRegistry();
11107
11796
  const checks = mergeChecks(profile.checks, opts.checks ?? []);
11108
11797
  const providers = [];
11109
11798
  const findings = [];
11110
11799
  let providerFailed = false;
11111
- const tm = performance.now();
11800
+ const tm = performance$1.now();
11112
11801
  const manifestFacts = deriveManifestFacts(bundle);
11113
11802
  providers.push({
11114
11803
  name: "manifest",
11115
11804
  status: "ok",
11116
11805
  ms: ms(tm)
11117
11806
  });
11118
- const tc = performance.now();
11807
+ const tc = performance$1.now();
11119
11808
  const content = contentReader(bundle, opts.casDir);
11120
11809
  providers.push({
11121
11810
  name: "content",
@@ -11131,7 +11820,7 @@ async function runChecks(opts) {
11131
11820
  profile: profile.facts
11132
11821
  }
11133
11822
  };
11134
- const tr = performance.now();
11823
+ const tr = performance$1.now();
11135
11824
  if (opts.root !== void 0 && profile.facts.allowRepo) try {
11136
11825
  ctx.facts.repo = await createRepoFacts(opts.root);
11137
11826
  providers.push({
@@ -11233,7 +11922,7 @@ async function runChecks(opts) {
11233
11922
  }
11234
11923
  await execute(check, predicate, params.params);
11235
11924
  }
11236
- const tg = performance.now();
11925
+ const tg = performance$1.now();
11237
11926
  if (guardChecks.length > 0 || guardFailed) {
11238
11927
  await runPool(guardChecks, GUARD_POOL_SIZE, (g) => execute(g.check, g.predicate, g.params));
11239
11928
  providers.push({
@@ -11341,11 +12030,222 @@ async function casGet(root, hex) {
11341
12030
  throw err;
11342
12031
  }
11343
12032
  }
12033
+ function redactUri(uri) {
12034
+ try {
12035
+ const u = new URL(uri);
12036
+ return u.protocol === "file:" ? `file://${u.pathname}` : `${u.origin}${u.pathname}`;
12037
+ } catch {
12038
+ return "<invalid uri>";
12039
+ }
12040
+ }
12041
+ function hostAllowed(host, allowlist) {
12042
+ if (allowlist === void 0) return true;
12043
+ const h = host.toLowerCase();
12044
+ for (const raw of allowlist) {
12045
+ const p = raw.trim().toLowerCase();
12046
+ if (p === "") continue;
12047
+ if (p.startsWith("*.")) {
12048
+ if (h.endsWith(p.slice(1)) && h.length > p.length - 1) return true;
12049
+ } else if (h === p) return true;
12050
+ }
12051
+ return false;
12052
+ }
12053
+ function netError(code, message, src, opts, extra = {}) {
12054
+ let host;
12055
+ try {
12056
+ host = new URL(src.uri).host;
12057
+ } catch {
12058
+ host = void 0;
12059
+ }
12060
+ const details = {
12061
+ uri: redactUri(src.uri),
12062
+ digest: src.digest,
12063
+ ...extra
12064
+ };
12065
+ if (host !== void 0) details.host = host;
12066
+ const o = { details };
12067
+ if (opts.path !== void 0) o.path = opts.path;
12068
+ return new AxiomError(code, message, o);
12069
+ }
12070
+ async function openSink(root, hex) {
12071
+ const target = casPath(root, hex);
12072
+ await mkdir(dirname(target), { recursive: true });
12073
+ const tmp = `${target}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
12074
+ return {
12075
+ tmp,
12076
+ fh: await open(tmp, "wx"),
12077
+ hash: createHash("sha256"),
12078
+ bytes: 0
12079
+ };
12080
+ }
12081
+ async function readFileStream(uri, sink, maxBytes, src, opts) {
12082
+ const p = fileURLToPath(uri);
12083
+ const st = await stat(p).catch(() => void 0);
12084
+ if (st === void 0 || !st.isFile()) throw netError("ERR_NET_FAILED", "file ref does not exist or is not a file", src, opts);
12085
+ if (st.size > maxBytes) throw new AxiomError("ERR_BLOB_TOO_LARGE", `ref is ${st.size} bytes`, { details: {
12086
+ bytes: st.size,
12087
+ max: maxBytes,
12088
+ uri: redactUri(src.uri)
12089
+ } });
12090
+ for await (const chunk of createReadStream(p)) {
12091
+ const c = chunk;
12092
+ sink.bytes += c.length;
12093
+ if (sink.bytes > maxBytes) throw new AxiomError("ERR_BLOB_TOO_LARGE", "ref exceeds maxBytes", { details: {
12094
+ max: maxBytes,
12095
+ uri: redactUri(src.uri)
12096
+ } });
12097
+ sink.hash.update(c);
12098
+ await sink.fh.write(c);
12099
+ }
12100
+ }
12101
+ async function readHttpsStream(uri, sink, maxBytes, timeoutMs, src, opts) {
12102
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
12103
+ const ac = new AbortController();
12104
+ const timer = setTimeout(() => ac.abort(/* @__PURE__ */ new Error("timeout")), timeoutMs);
12105
+ try {
12106
+ let res;
12107
+ try {
12108
+ res = await fetchImpl(uri, {
12109
+ method: "GET",
12110
+ redirect: "error",
12111
+ signal: ac.signal,
12112
+ headers: { accept: "*/*" }
12113
+ });
12114
+ } catch (err) {
12115
+ const timedOut = ac.signal.aborted;
12116
+ throw netError("ERR_NET_FAILED", timedOut ? `ref fetch timed out after ${timeoutMs} ms` : "ref fetch failed", src, opts, { reason: timedOut ? "timeout" : err.message });
12117
+ }
12118
+ if (!res.ok) throw netError("ERR_NET_FAILED", `ref fetch returned HTTP ${res.status}`, src, opts, { status: res.status });
12119
+ const declared = Number(res.headers.get("content-length") ?? "");
12120
+ if (Number.isFinite(declared) && declared > maxBytes) throw new AxiomError("ERR_BLOB_TOO_LARGE", `ref declares ${declared} bytes`, { details: {
12121
+ bytes: declared,
12122
+ max: maxBytes,
12123
+ uri: redactUri(src.uri)
12124
+ } });
12125
+ if (res.body === null) return;
12126
+ const reader = res.body.getReader();
12127
+ for (;;) {
12128
+ let step;
12129
+ try {
12130
+ step = await reader.read();
12131
+ } catch (err) {
12132
+ throw netError("ERR_NET_FAILED", ac.signal.aborted ? `ref fetch timed out after ${timeoutMs} ms` : "ref body read failed", src, opts, { reason: ac.signal.aborted ? "timeout" : err.message });
12133
+ }
12134
+ if (step.done) break;
12135
+ sink.bytes += step.value.length;
12136
+ if (sink.bytes > maxBytes) {
12137
+ ac.abort(/* @__PURE__ */ new Error("maxBytes"));
12138
+ await reader.cancel().catch(() => void 0);
12139
+ throw new AxiomError("ERR_BLOB_TOO_LARGE", "ref exceeds maxBytes", { details: {
12140
+ max: maxBytes,
12141
+ uri: redactUri(src.uri)
12142
+ } });
12143
+ }
12144
+ sink.hash.update(step.value);
12145
+ await sink.fh.write(step.value);
12146
+ }
12147
+ } finally {
12148
+ clearTimeout(timer);
12149
+ }
12150
+ }
12151
+ async function resolveRef(src, opts) {
12152
+ const hex = parseDigestRef(src.digest);
12153
+ const cached = await casGet(opts.root, hex);
12154
+ if (cached !== void 0) return cached;
12155
+ if (!opts.allowNet) throw netError("ERR_NET_DISABLED", "ref is not in the CAS and network access is disabled (pass --allow-net)", src, opts);
12156
+ let uri;
12157
+ try {
12158
+ uri = new URL(src.uri);
12159
+ } catch {
12160
+ throw netError("ERR_NET_DENIED", "ref uri is not a valid URL", src, opts);
12161
+ }
12162
+ if (uri.protocol === "file:") {
12163
+ if (opts.allowFile !== true) throw netError("ERR_NET_DENIED", "file: refs need allowFile", src, opts, { protocol: "file:" });
12164
+ } else if (uri.protocol !== "https:") throw netError("ERR_NET_DENIED", "only https: refs are fetched", src, opts, { protocol: uri.protocol });
12165
+ else if (uri.username !== "" || uri.password !== "") throw netError("ERR_NET_DENIED", "credentials in a ref uri are not allowed", src, opts);
12166
+ else if (!hostAllowed(uri.hostname, opts.allowlist)) throw netError("ERR_NET_DENIED", "ref host is not in the allowlist", src, opts, { allowlist: opts.allowlist ?? [] });
12167
+ const maxBytes = opts.maxBytes ?? 33554432;
12168
+ const timeoutMs = opts.timeoutMs ?? 3e4;
12169
+ const sink = await openSink(opts.root, hex);
12170
+ let ok = false;
12171
+ try {
12172
+ if (uri.protocol === "file:") await readFileStream(uri, sink, maxBytes, src, opts);
12173
+ else await readHttpsStream(uri, sink, maxBytes, timeoutMs, src, opts);
12174
+ const actual = sink.hash.digest("hex");
12175
+ if (actual !== hex) throw new AxiomError("ERR_DIGEST_MISMATCH", "fetched ref does not match its pinned digest", { details: {
12176
+ expected: src.digest,
12177
+ actual: `sha256:${actual}`,
12178
+ bytes: sink.bytes,
12179
+ uri: redactUri(src.uri)
12180
+ } });
12181
+ await sink.fh.sync();
12182
+ await sink.fh.close();
12183
+ ok = true;
12184
+ try {
12185
+ await rename(sink.tmp, casPath(opts.root, hex));
12186
+ } catch (err) {
12187
+ await rm(sink.tmp, { force: true });
12188
+ if (!await casHas(opts.root, hex)) throw err;
12189
+ }
12190
+ } finally {
12191
+ if (!ok) {
12192
+ await sink.fh.close().catch(() => void 0);
12193
+ await rm(sink.tmp, { force: true });
12194
+ }
12195
+ }
12196
+ const bytes = await casGet(opts.root, hex);
12197
+ if (bytes === void 0) throw new AxiomError("ERR_BLOB_MISSING", "ref was stored but cannot be read back", { details: { digest: src.digest } });
12198
+ return bytes;
12199
+ }
11344
12200
  const INLINE_BASE64_DECODED_MAX = 196608;
11345
12201
  const AXIOM_VERSION = "2.0.0";
11346
12202
  function invalidPlan(err) {
11347
12203
  return new AxiomError("ERR_INVALID_PLAN", "plan does not match PlanSchema", { details: { issues: err.issues } });
11348
12204
  }
12205
+ function renderTemplate(a, src, opts) {
12206
+ const emitter = opts.emitters?.get(src.emitter);
12207
+ if (emitter === void 0) throw new AxiomError("ERR_EMITTER_UNKNOWN", `no emitter registered as "${src.emitter}"`, {
12208
+ path: a.path,
12209
+ details: {
12210
+ emitter: src.emitter,
12211
+ available: opts.emitters?.list() ?? []
12212
+ }
12213
+ });
12214
+ const def = Object.hasOwn(emitter.templates, src.template) ? emitter.templates[src.template] : void 0;
12215
+ if (def === void 0) throw new AxiomError("ERR_TEMPLATE_UNKNOWN", `emitter "${src.emitter}" has no template "${src.template}"`, {
12216
+ path: a.path,
12217
+ details: {
12218
+ emitter: src.emitter,
12219
+ template: src.template,
12220
+ available: Object.keys(emitter.templates).sort()
12221
+ }
12222
+ });
12223
+ const parsed = def.params.safeParse(src.params);
12224
+ if (!parsed.success) throw new AxiomError("ERR_TEMPLATE_PARAMS", "template params fail the template schema", {
12225
+ path: a.path,
12226
+ details: {
12227
+ emitter: src.emitter,
12228
+ template: src.template,
12229
+ issues: parsed.error.issues
12230
+ }
12231
+ });
12232
+ const rendered = def.render(parsed.data);
12233
+ const bytes = typeof rendered === "string" ? utf8Bytes(rendered) : rendered;
12234
+ if (bytes.length > 262144) throw new AxiomError("ERR_BLOB_TOO_LARGE", `template rendered ${bytes.length} bytes`, {
12235
+ path: a.path,
12236
+ details: {
12237
+ bytes: bytes.length,
12238
+ max: INLINE_CONTENT_MAX
12239
+ }
12240
+ });
12241
+ return {
12242
+ bytes,
12243
+ emitter: {
12244
+ id: emitter.id,
12245
+ version: emitter.version
12246
+ }
12247
+ };
12248
+ }
11349
12249
  async function resolveSource(a, opts) {
11350
12250
  const src = a.source;
11351
12251
  if (src === void 0) throw new AxiomError("ERR_INVALID_PLAN", "source is required unless op is delete", { path: a.path });
@@ -11360,7 +12260,7 @@ async function resolveSource(a, opts) {
11360
12260
  max: INLINE_CONTENT_MAX
11361
12261
  }
11362
12262
  });
11363
- return bytes;
12263
+ return { bytes };
11364
12264
  }
11365
12265
  if (!isBase64(src.content)) throw new AxiomError("ERR_INVALID_PLAN", "inline content is not valid base64", { path: a.path });
11366
12266
  const bytes = decodeBlob({
@@ -11374,7 +12274,7 @@ async function resolveSource(a, opts) {
11374
12274
  max: INLINE_BASE64_DECODED_MAX
11375
12275
  }
11376
12276
  });
11377
- return bytes;
12277
+ return { bytes };
11378
12278
  }
11379
12279
  case "cas": {
11380
12280
  const hex = parseDigestRef(src.digest);
@@ -11395,22 +12295,19 @@ async function resolveSource(a, opts) {
11395
12295
  actual: `sha256:${actual}`
11396
12296
  }
11397
12297
  });
11398
- return bytes;
12298
+ return { bytes };
11399
12299
  }
11400
- case "ref": throw new AxiomError("ERR_REF_OFFLINE", "ref sources are not fetched in v2.0", {
11401
- path: a.path,
11402
- details: {
11403
- uri: src.uri,
11404
- digest: src.digest
11405
- }
11406
- });
11407
- case "template": throw new AxiomError("ERR_UNSUPPORTED_OP", "template sources ship in v2.1", {
11408
- path: a.path,
11409
- details: {
11410
- emitter: src.emitter,
11411
- template: src.template
11412
- }
11413
- });
12300
+ case "ref":
12301
+ if (opts.root === void 0) throw new AxiomError("ERR_REF_OFFLINE", "ref source needs a root (its CAS)", {
12302
+ path: a.path,
12303
+ details: { digest: src.digest }
12304
+ });
12305
+ return { bytes: await resolveRef(src, {
12306
+ ...opts.net ?? { allowNet: false },
12307
+ root: opts.root,
12308
+ path: a.path
12309
+ }) };
12310
+ case "template": return renderTemplate(a, src, opts);
11414
12311
  }
11415
12312
  }
11416
12313
  async function resolveArtifact(a, opts) {
@@ -11422,9 +12319,9 @@ async function resolveArtifact(a, opts) {
11422
12319
  },
11423
12320
  bytes: void 0
11424
12321
  };
11425
- const bytes = await resolveSource(a, opts);
12322
+ const { bytes, emitter } = await resolveSource(a, opts);
11426
12323
  const origin = a.source?.type ?? "inline";
11427
- return {
12324
+ const out = {
11428
12325
  artifact: {
11429
12326
  path: a.path,
11430
12327
  op: a.op,
@@ -11435,6 +12332,8 @@ async function resolveArtifact(a, opts) {
11435
12332
  },
11436
12333
  bytes
11437
12334
  };
12335
+ if (emitter !== void 0) out.emitter = emitter;
12336
+ return out;
11438
12337
  }
11439
12338
  function digestOnlyPlan(plan, digests) {
11440
12339
  const artifacts = [...plan.artifacts].sort((x, y) => compareUtf8(x.path, y.path)).map((a) => {
@@ -11443,10 +12342,19 @@ function digestOnlyPlan(plan, digests) {
11443
12342
  mode: a.mode,
11444
12343
  op: a.op
11445
12344
  };
11446
- if (a.source !== void 0) out.source = {
11447
- type: a.source.type,
11448
- digest: `sha256:${digests.get(a.path) ?? ""}`
11449
- };
12345
+ if (a.source !== void 0) {
12346
+ const digest = `sha256:${digests.get(a.path) ?? ""}`;
12347
+ out.source = a.source.type === "template" ? {
12348
+ type: "template",
12349
+ emitter: a.source.emitter,
12350
+ template: a.source.template,
12351
+ params: a.source.params,
12352
+ digest
12353
+ } : {
12354
+ type: a.source.type,
12355
+ digest
12356
+ };
12357
+ }
11450
12358
  return out;
11451
12359
  });
11452
12360
  return {
@@ -11454,7 +12362,7 @@ function digestOnlyPlan(plan, digests) {
11454
12362
  artifacts
11455
12363
  };
11456
12364
  }
11457
- function splitToolchain(input) {
12365
+ function splitToolchain(input, used) {
11458
12366
  const emitters = {};
11459
12367
  let axiom = AXIOM_VERSION;
11460
12368
  for (const key of Object.keys(input ?? {}).sort()) {
@@ -11463,6 +12371,7 @@ function splitToolchain(input) {
11463
12371
  if (key === "axiom") axiom = v;
11464
12372
  else emitters[key] = v;
11465
12373
  }
12374
+ for (const id of [...used.keys()].sort()) emitters[id] = used.get(id);
11466
12375
  return {
11467
12376
  axiom,
11468
12377
  emitters
@@ -11492,9 +12401,13 @@ async function compilePlan(planInput, opts = {}) {
11492
12401
  for (const a of plan.artifacts) resolved.push(await resolveArtifact(a, opts));
11493
12402
  resolved.sort((x, y) => compareUtf8(x.artifact.path, y.artifact.path));
11494
12403
  const digests = /* @__PURE__ */ new Map();
11495
- for (const r of resolved) if (r.artifact.digest !== void 0) digests.set(r.artifact.path, r.artifact.digest.sha256);
12404
+ const usedEmitters = /* @__PURE__ */ new Map();
12405
+ for (const r of resolved) {
12406
+ if (r.artifact.digest !== void 0) digests.set(r.artifact.path, r.artifact.digest.sha256);
12407
+ if (r.emitter !== void 0) usedEmitters.set(r.emitter.id, r.emitter.version);
12408
+ }
11496
12409
  const planDigest = canonicalDigestRef(digestOnlyPlan(plan, digests));
11497
- const toolchain = splitToolchain(opts.toolchain);
12410
+ const toolchain = splitToolchain(opts.toolchain, usedEmitters);
11498
12411
  const body = {
11499
12412
  apiVersion: plan.apiVersion,
11500
12413
  kind: "Manifest",
@@ -11505,12 +12418,14 @@ async function compilePlan(planInput, opts = {}) {
11505
12418
  checks: [...plan.checks].sort((x, y) => compareUtf8(x.id, y.id)),
11506
12419
  toolchain
11507
12420
  };
12421
+ if (plan.counter !== void 0) body.counter = plan.counter;
11508
12422
  const manifestDigest = canonicalDigestRef(body);
11509
12423
  const blobs = {};
11510
12424
  const useCas = opts.store === "cas" && opts.root !== void 0;
11511
12425
  let total = 0;
11512
12426
  for (const r of resolved) {
11513
12427
  if (r.bytes === void 0 || r.artifact.digest === void 0) continue;
12428
+ if (r.artifact.origin === "ref") continue;
11514
12429
  if (useCas) {
11515
12430
  await casPut(opts.root, r.bytes);
11516
12431
  continue;
@@ -11587,6 +12502,17 @@ function diffManifests(a, b) {
11587
12502
  changed
11588
12503
  };
11589
12504
  }
12505
+ function createEmitterRegistry(emitters) {
12506
+ const byId = /* @__PURE__ */ new Map();
12507
+ for (const e of emitters) {
12508
+ if (byId.has(e.id)) throw new Error(`duplicate emitter id: ${e.id}`);
12509
+ byId.set(e.id, e);
12510
+ }
12511
+ return {
12512
+ get: (id) => byId.get(id),
12513
+ list: () => [...byId.keys()].sort()
12514
+ };
12515
+ }
11590
12516
  function issueCode(params) {
11591
12517
  const code = params?.code;
11592
12518
  return isErrorCode(code) ? code : "ERR_INVALID_MANIFEST";
@@ -12523,6 +13449,476 @@ var StdioServerTransport = class {
12523
13449
  });
12524
13450
  }
12525
13451
  };
13452
+ function defineTemplate(description, params, render) {
13453
+ return {
13454
+ description,
13455
+ params,
13456
+ render
13457
+ };
13458
+ }
13459
+ function lines(...parts) {
13460
+ const flat = [];
13461
+ for (const p of parts) if (typeof p === "string") flat.push(p);
13462
+ else flat.push(...p);
13463
+ return `${flat.join("\n").replace(/\s+$/, "")}\n`;
13464
+ }
13465
+ const Ident = string().regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/, "must be a JavaScript identifier").max(64);
13466
+ const HttpMethod = _enum([
13467
+ "GET",
13468
+ "POST",
13469
+ "PUT",
13470
+ "DELETE"
13471
+ ]);
13472
+ const Segment = string().regex(/^[a-z0-9][a-z0-9-]*(\/(\[[a-z][a-zA-Z0-9]*\]|[a-z0-9][a-z0-9-]*))*$/, "invalid segment").max(128);
13473
+ const SORT_METHODS = (ms) => {
13474
+ const order = [
13475
+ "GET",
13476
+ "POST",
13477
+ "PUT",
13478
+ "DELETE"
13479
+ ];
13480
+ return [...new Set(ms)].sort((a, b) => order.indexOf(a) - order.indexOf(b));
13481
+ };
13482
+ const routeHandler = defineTemplate("Next.js 16 App Router route handler (`app/api/<segment>/route.ts`) — one exported async function per method, `NextRequest` in, `Response.json` out; optional Zod body validation on the non-GET methods.", object$1({
13483
+ segment: Segment,
13484
+ methods: array(HttpMethod).min(1).max(4),
13485
+ zodSchemaName: Ident.optional()
13486
+ }).strict(), ({ segment, methods, zodSchemaName }) => {
13487
+ const schema = zodSchemaName;
13488
+ return lines([
13489
+ "import type { NextRequest } from \"next/server\";",
13490
+ ...schema === void 0 ? [] : [`import { ${schema} } from "./schema";`],
13491
+ "",
13492
+ `// ${segment} — generated by @codai/axiom-emitters-web (web@2.0.0).`,
13493
+ ""
13494
+ ], SORT_METHODS(methods).map((m) => {
13495
+ if (m === "GET") return lines([
13496
+ "export async function GET(request: NextRequest): Promise<Response> {",
13497
+ " const { searchParams } = new URL(request.url);",
13498
+ " return Response.json({ ok: true, query: Object.fromEntries(searchParams) });",
13499
+ "}"
13500
+ ]).trimEnd();
13501
+ if (m === "DELETE") return lines([
13502
+ "export async function DELETE(_request: NextRequest): Promise<Response> {",
13503
+ " return new Response(null, { status: 204 });",
13504
+ "}"
13505
+ ]).trimEnd();
13506
+ const validate = schema === void 0 ? [" const data = (await request.json()) as unknown;"] : [
13507
+ " const parsed = " + schema + ".safeParse(await request.json());",
13508
+ " if (!parsed.success) {",
13509
+ " return Response.json({ error: parsed.error.issues }, { status: 400 });",
13510
+ " }",
13511
+ " const data = parsed.data;"
13512
+ ];
13513
+ return lines([
13514
+ `export async function ${m}(request: NextRequest): Promise<Response> {`,
13515
+ ...validate,
13516
+ ` return Response.json({ ok: true, data }, { status: ${m === "POST" ? 201 : 200} });`,
13517
+ "}"
13518
+ ]).trimEnd();
13519
+ }).join("\n\n"));
13520
+ });
13521
+ const ZOD_FIELD = {
13522
+ string: "z.string().min(1)",
13523
+ number: "z.coerce.number()",
13524
+ boolean: "z.coerce.boolean()",
13525
+ email: "z.email()"
13526
+ };
13527
+ const serverAction = defineTemplate("Next.js 16 Server Action (\"use server\") validating FormData with Zod v4 and returning a `useActionState`-compatible `{ ok, errors, values }` state.", object$1({
13528
+ name: Ident,
13529
+ fields: array(object$1({
13530
+ name: Ident,
13531
+ zod: _enum([
13532
+ "string",
13533
+ "number",
13534
+ "boolean",
13535
+ "email"
13536
+ ])
13537
+ }).strict()).min(1).max(50)
13538
+ }).strict(), ({ name, fields }) => {
13539
+ const State = `${name.charAt(0).toUpperCase()}${name.slice(1)}State`;
13540
+ return lines([
13541
+ "\"use server\";",
13542
+ "",
13543
+ "import { z } from \"zod\";",
13544
+ "",
13545
+ `// ${name} — generated by @codai/axiom-emitters-web (web@2.0.0).`,
13546
+ "",
13547
+ `const ${name}Schema = z.object({`,
13548
+ ...fields.map((f) => ` ${f.name}: ${ZOD_FIELD[f.zod]},`),
13549
+ "});",
13550
+ "",
13551
+ `export interface ${State} {`,
13552
+ " ok: boolean;",
13553
+ " errors: Record<string, string[]>;",
13554
+ ` values: Partial<z.output<typeof ${name}Schema>>;`,
13555
+ "}",
13556
+ "",
13557
+ `export const initial${State}: ${State} = { ok: false, errors: {}, values: {} };`,
13558
+ "",
13559
+ `export async function ${name}(`,
13560
+ ` _prev: ${State},`,
13561
+ " formData: FormData,",
13562
+ `): Promise<${State}> {`,
13563
+ ` const parsed = ${name}Schema.safeParse({`,
13564
+ ...fields.map((f) => ` ${f.name}: formData.get("${f.name}"),`),
13565
+ " });",
13566
+ " if (!parsed.success) {",
13567
+ " const flat = z.flattenError(parsed.error);",
13568
+ " return { ok: false, errors: flat.fieldErrors as Record<string, string[]>, values: {} };",
13569
+ " }",
13570
+ " return { ok: true, errors: {}, values: parsed.data };",
13571
+ "}"
13572
+ ]);
13573
+ });
13574
+ const honoRoute = defineTemplate("Hono 4 route module exporting a `Hono` instance with one handler per method; mount it with `app.route(\"/\", <name>)`.", object$1({
13575
+ name: Ident,
13576
+ path: string().regex(/^\/[A-Za-z0-9/:_-]*$/, "path must start with /").max(128),
13577
+ methods: array(HttpMethod).min(1).max(4)
13578
+ }).strict(), ({ name, path, methods }) => lines([
13579
+ "import { Hono } from \"hono\";",
13580
+ "",
13581
+ `// ${name} — generated by @codai/axiom-emitters-web (web@2.0.0).`,
13582
+ "",
13583
+ `export const ${name} = new Hono();`,
13584
+ "",
13585
+ ...SORT_METHODS(methods).flatMap((m) => [
13586
+ `${name}.${m.toLowerCase()}("${path}", (c) => {`,
13587
+ m === "DELETE" ? " return c.body(null, 204);" : ` return c.json({ ok: true, method: "${m}" }${m === "POST" ? ", 201" : ""});`,
13588
+ "});",
13589
+ ""
13590
+ ]),
13591
+ `export default ${name};`
13592
+ ]));
13593
+ const DRIZZLE_COLUMN = {
13594
+ text: (n) => `text("${n}")`,
13595
+ integer: (n) => `integer("${n}")`,
13596
+ boolean: (n) => `boolean("${n}")`,
13597
+ timestamp: (n) => `timestamp("${n}", { withTimezone: true })`,
13598
+ uuid: (n) => `uuid("${n}")`
13599
+ };
13600
+ const drizzleTable = defineTemplate("Drizzle `pgTable` with the repo conventions: `id` identity column always generated, `withTimezone: true` on every timestamp, and inferred insert/select types.", object$1({
13601
+ name: string().regex(/^[a-z][a-z0-9_]*$/, "snake_case table name").max(63),
13602
+ columns: array(object$1({
13603
+ name: string().regex(/^[a-z][a-z0-9_]*$/, "snake_case column name").max(63),
13604
+ type: _enum([
13605
+ "text",
13606
+ "integer",
13607
+ "boolean",
13608
+ "timestamp",
13609
+ "uuid"
13610
+ ]),
13611
+ primaryKey: boolean().default(false),
13612
+ notNull: boolean().default(false)
13613
+ }).strict()).min(1).max(100)
13614
+ }).strict(), ({ name, columns }) => {
13615
+ const kinds = [...new Set(columns.map((c) => c.type))].sort();
13616
+ const toCamel = (s) => s.replace(/_([a-z0-9])/g, (_m, c) => c.toUpperCase());
13617
+ const camel = toCamel(name);
13618
+ const Type = `${camel.charAt(0).toUpperCase()}${camel.slice(1)}`;
13619
+ const hasExplicitPk = columns.some((c) => c.primaryKey);
13620
+ return lines([
13621
+ `import { ${["pgTable", ...kinds].sort().join(", ")} } from "drizzle-orm/pg-core";`,
13622
+ "",
13623
+ `// ${name} — generated by @codai/axiom-emitters-web (web@2.0.0).`,
13624
+ "",
13625
+ `export const ${camel} = pgTable("${name}", {`,
13626
+ ...hasExplicitPk ? [] : [" id: integer(\"id\").generatedAlwaysAsIdentity().primaryKey(),"],
13627
+ ...columns.map((c) => {
13628
+ const suffix = [c.primaryKey ? ".primaryKey()" : "", c.notNull && !c.primaryKey ? ".notNull()" : ""].join("");
13629
+ return ` ${toCamel(c.name)}: ${DRIZZLE_COLUMN[c.type](c.name)}${suffix},`;
13630
+ }),
13631
+ "});",
13632
+ "",
13633
+ `export type ${Type} = typeof ${camel}.$inferSelect;`,
13634
+ `export type New${Type} = typeof ${camel}.$inferInsert;`
13635
+ ]);
13636
+ });
13637
+ const biomeConfig = defineTemplate("Biome 2.5 config matching this repo's own style (2-space, width 100, double quotes, LF, `noExplicitAny` / `noConsole` / `noNonNullAssertion` as errors).", object$1({}).strict(), () => lines([
13638
+ "{",
13639
+ " \"$schema\": \"https://biomejs.dev/schemas/2.5.14/schema.json\",",
13640
+ " \"vcs\": { \"enabled\": true, \"clientKind\": \"git\", \"useIgnoreFile\": true },",
13641
+ " \"files\": { \"includes\": [\"**\", \"!**/dist\", \"!**/.next\"] },",
13642
+ " \"formatter\": {",
13643
+ " \"enabled\": true,",
13644
+ " \"indentStyle\": \"space\",",
13645
+ " \"indentWidth\": 2,",
13646
+ " \"lineWidth\": 100,",
13647
+ " \"lineEnding\": \"lf\"",
13648
+ " },",
13649
+ " \"javascript\": {",
13650
+ " \"formatter\": { \"quoteStyle\": \"double\", \"semicolons\": \"always\", \"trailingCommas\": \"all\" }",
13651
+ " },",
13652
+ " \"linter\": {",
13653
+ " \"enabled\": true,",
13654
+ " \"rules\": {",
13655
+ " \"preset\": \"recommended\",",
13656
+ " \"style\": {",
13657
+ " \"noNonNullAssertion\": \"error\",",
13658
+ " \"useImportType\": \"error\",",
13659
+ " \"useNodejsImportProtocol\": \"error\"",
13660
+ " },",
13661
+ " \"suspicious\": {",
13662
+ " \"noExplicitAny\": \"error\",",
13663
+ " \"noConsole\": { \"level\": \"error\", \"options\": { \"allow\": [\"error\", \"warn\"] } }",
13664
+ " },",
13665
+ " \"correctness\": { \"noUnusedImports\": \"error\", \"noUnusedVariables\": \"error\" }",
13666
+ " }",
13667
+ " }",
13668
+ "}"
13669
+ ]));
13670
+ const tailwindGlobals = defineTemplate("Tailwind v4 CSS-first entry (`@import \"tailwindcss\"` + an empty `@theme` block and light/dark tokens). No `tailwind.config.js`.", object$1({}).strict(), () => lines([
13671
+ "@import \"tailwindcss\";",
13672
+ "",
13673
+ "@theme {",
13674
+ " --color-background: oklch(1 0 0);",
13675
+ " --color-foreground: oklch(0.145 0 0);",
13676
+ " --radius: 0.625rem;",
13677
+ "}",
13678
+ "",
13679
+ "@layer base {",
13680
+ " :root {",
13681
+ " color-scheme: light dark;",
13682
+ " }",
13683
+ "",
13684
+ " body {",
13685
+ " background-color: var(--color-background);",
13686
+ " color: var(--color-foreground);",
13687
+ " }",
13688
+ "}"
13689
+ ]));
13690
+ const EMITTERS = createEmitterRegistry([{
13691
+ id: "web",
13692
+ version: "2.0.0",
13693
+ templates: {
13694
+ "biome.config": biomeConfig,
13695
+ "drizzle.table": drizzleTable,
13696
+ "hono.route": honoRoute,
13697
+ "next.route-handler": routeHandler,
13698
+ "next.server-action": serverAction,
13699
+ "readme.section": defineTemplate("A single `## <title>` markdown section with the given body — the smallest useful template, meant for composing docs from a plan.", object$1({
13700
+ title: string().min(1).max(120),
13701
+ body: string().max(8e3)
13702
+ }).strict(), ({ title, body }) => lines([
13703
+ `## ${title}`,
13704
+ "",
13705
+ body.replace(/\r\n/g, "\n").trimEnd()
13706
+ ])),
13707
+ "tailwind.globals": tailwindGlobals
13708
+ }
13709
+ }]);
13710
+ function emitterCatalogue(registry = EMITTERS) {
13711
+ const rows = [];
13712
+ for (const id of registry.list()) {
13713
+ const e = registry.get(id);
13714
+ if (e === void 0) continue;
13715
+ for (const template of Object.keys(e.templates).sort()) rows.push({
13716
+ emitter: e.id,
13717
+ version: e.version,
13718
+ template,
13719
+ description: e.templates[template]?.description ?? ""
13720
+ });
13721
+ }
13722
+ return rows;
13723
+ }
13724
+ function manifestsDir(root) {
13725
+ return path.join(root, ".axiom", "manifests");
13726
+ }
13727
+ function reportsDir(root) {
13728
+ return path.join(root, ".axiom", "reports");
13729
+ }
13730
+ function hexOf(ref) {
13731
+ return ref.slice(7);
13732
+ }
13733
+ function toDigestRef(shaOrRef) {
13734
+ const ref = shaOrRef.startsWith("sha256:") ? shaOrRef : `sha256:${shaOrRef}`;
13735
+ const parsed = DigestRefSchema.safeParse(ref);
13736
+ if (!parsed.success) throw new AxiomError("ERR_NOT_FOUND", `not a sha256 digest: ${shaOrRef}`);
13737
+ return parsed.data;
13738
+ }
13739
+ async function writeJsonAtomic$1(file, value) {
13740
+ await mkdir(path.dirname(file), { recursive: true });
13741
+ const tmp = `${file}.tmp-${process.pid}`;
13742
+ await writeFile(tmp, JSON.stringify(value), "utf8");
13743
+ await rename(tmp, file);
13744
+ }
13745
+ async function readJsonOrUndefined$1(file) {
13746
+ try {
13747
+ return JSON.parse(await readFile(file, "utf8"));
13748
+ } catch (err) {
13749
+ if (err.code === "ENOENT") return void 0;
13750
+ throw err;
13751
+ }
13752
+ }
13753
+ async function saveManifest(root, bundle) {
13754
+ const file = path.join(manifestsDir(root), `${hexOf(bundle.manifestDigest)}.json`);
13755
+ await writeJsonAtomic$1(file, bundle);
13756
+ return file;
13757
+ }
13758
+ async function saveReport(root, report) {
13759
+ const file = path.join(reportsDir(root), `${hexOf(report.manifestDigest)}.json`);
13760
+ await writeJsonAtomic$1(file, report);
13761
+ return file;
13762
+ }
13763
+ async function loadManifest(roots, ref) {
13764
+ for (const root of roots) {
13765
+ const raw = await readJsonOrUndefined$1(path.join(manifestsDir(root), `${hexOf(ref)}.json`));
13766
+ if (raw !== void 0) return ManifestBundleSchema.parse(raw);
13767
+ }
13768
+ }
13769
+ async function loadReport(roots, ref) {
13770
+ for (const root of roots) {
13771
+ const raw = await readJsonOrUndefined$1(path.join(reportsDir(root), `${hexOf(ref)}.json`));
13772
+ if (raw !== void 0) return CheckReportSchema.parse(raw);
13773
+ }
13774
+ }
13775
+ async function loadApplied(roots, ref) {
13776
+ for (const root of roots) {
13777
+ const raw = await readJsonOrUndefined$1(appliedPath(root, ref));
13778
+ if (raw !== void 0) return ApplyResultSchema.parse(raw);
13779
+ }
13780
+ }
13781
+ async function listStored(roots, sub) {
13782
+ const out = [];
13783
+ for (const root of roots) {
13784
+ let names;
13785
+ try {
13786
+ names = await readdir(path.join(root, ".axiom", sub));
13787
+ } catch {
13788
+ continue;
13789
+ }
13790
+ for (const n of names) {
13791
+ const m = /^([0-9a-f]{64})\.json$/.exec(n);
13792
+ if (m?.[1] !== void 0) out.push({
13793
+ root,
13794
+ sha: m[1]
13795
+ });
13796
+ }
13797
+ }
13798
+ return out;
13799
+ }
13800
+ const HEX64 = /^[0-9a-f]{64}$/;
13801
+ function casRootDir(root) {
13802
+ return path.join(root, ".axiom", "cas", "sha256");
13803
+ }
13804
+ async function readdirOrEmpty(dir) {
13805
+ try {
13806
+ return await readdir(dir);
13807
+ } catch (err) {
13808
+ if (err.code === "ENOENT") return [];
13809
+ throw err;
13810
+ }
13811
+ }
13812
+ async function loadManifestArtifactDigests(root, hex) {
13813
+ const file = path.join(manifestsDir(root), `${hex}.json`);
13814
+ let raw;
13815
+ try {
13816
+ raw = await readFile(file, "utf8");
13817
+ } catch (err) {
13818
+ if (err.code === "ENOENT") throw new AxiomError("ERR_BLOB_MISSING", "a journal names a manifest that is not stored", { details: {
13819
+ manifestDigest: `sha256:${hex}`,
13820
+ file
13821
+ } });
13822
+ throw err;
13823
+ }
13824
+ const parsed = ManifestBundleSchema.safeParse(JSON.parse(raw));
13825
+ if (!parsed.success) throw new AxiomError("ERR_INVALID_MANIFEST", "stored manifest fails ManifestBundleSchema", { details: { file } });
13826
+ const out = /* @__PURE__ */ new Set();
13827
+ for (const a of parsed.data.manifest.artifacts) if (a.digest !== void 0) out.add(a.digest.sha256);
13828
+ return out;
13829
+ }
13830
+ async function liveManifestHexes(root, keep) {
13831
+ const hexes = /* @__PURE__ */ new Set();
13832
+ const { journals } = await listJournals(root);
13833
+ for (const j of journals) hexes.add(j.manifestDigest.slice(7));
13834
+ if (keep === "journal") {
13835
+ for (const n of await readdirOrEmpty(path.join(root, ".axiom", "applied"))) {
13836
+ const m = /^([0-9a-f]{64})\.json$/.exec(n);
13837
+ if (m?.[1] !== void 0) hexes.add(m[1]);
13838
+ }
13839
+ return hexes;
13840
+ }
13841
+ for (const n of await readdirOrEmpty(manifestsDir(root))) {
13842
+ const m = /^([0-9a-f]{64})\.json$/.exec(n);
13843
+ if (m?.[1] !== void 0) hexes.add(m[1]);
13844
+ }
13845
+ return hexes;
13846
+ }
13847
+ async function collectGarbage(root, opts = {}) {
13848
+ const keep = opts.keep ?? "all-manifests";
13849
+ const dryRun = opts.dryRun === true;
13850
+ const now = opts.now ?? Date.now;
13851
+ const lock = await acquireLock(root, "gc", opts.lockTimeoutMs ?? 1e3);
13852
+ try {
13853
+ const live = /* @__PURE__ */ new Set();
13854
+ for (const hex of await liveManifestHexes(root, keep)) for (const d of await loadManifestArtifactDigests(root, hex)) live.add(d);
13855
+ const casDir = casRootDir(root);
13856
+ const removed = [];
13857
+ const present = /* @__PURE__ */ new Set();
13858
+ let scanned = 0;
13859
+ let skippedYoung = 0;
13860
+ let freedBytes = 0;
13861
+ const cutoff = opts.olderThanMs === void 0 ? void 0 : now() - opts.olderThanMs;
13862
+ for (const shard of (await readdirOrEmpty(casDir)).sort()) {
13863
+ const shardDir = path.join(casDir, shard);
13864
+ for (const name of (await readdirOrEmpty(shardDir)).sort()) {
13865
+ const file = path.join(shardDir, name);
13866
+ const st = await stat(file).catch(() => void 0);
13867
+ if (st === void 0 || !st.isFile()) continue;
13868
+ if (!HEX64.test(name)) {
13869
+ if (name.endsWith(".tmp") && (cutoff === void 0 || st.mtimeMs < cutoff)) {
13870
+ if (!dryRun) await rm(file, { force: true });
13871
+ removed.push({
13872
+ sha: name,
13873
+ bytes: st.size
13874
+ });
13875
+ freedBytes += st.size;
13876
+ }
13877
+ continue;
13878
+ }
13879
+ scanned++;
13880
+ present.add(name);
13881
+ if (live.has(name)) continue;
13882
+ if (cutoff !== void 0 && st.mtimeMs >= cutoff) {
13883
+ skippedYoung++;
13884
+ continue;
13885
+ }
13886
+ if (!dryRun) await rm(file, { force: true });
13887
+ removed.push({
13888
+ sha: name,
13889
+ bytes: st.size
13890
+ });
13891
+ freedBytes += st.size;
13892
+ }
13893
+ }
13894
+ let missing = 0;
13895
+ for (const d of live) if (!present.has(d)) missing++;
13896
+ return {
13897
+ root,
13898
+ keep,
13899
+ dryRun,
13900
+ scanned,
13901
+ live: live.size,
13902
+ missing,
13903
+ removed,
13904
+ skippedYoung,
13905
+ freedBytes
13906
+ };
13907
+ } finally {
13908
+ await lock.release();
13909
+ }
13910
+ }
13911
+ function parseDuration(text) {
13912
+ const m = /^(\d+)(ms|s|m|h|d)?$/.exec(text.trim());
13913
+ if (m === null || m[1] === void 0) return void 0;
13914
+ return Number(m[1]) * ({
13915
+ ms: 1,
13916
+ s: 1e3,
13917
+ m: 6e4,
13918
+ h: 36e5,
13919
+ d: 864e5
13920
+ }[m[2] ?? "ms"] ?? 1);
13921
+ }
12526
13922
  const SCHEMA_KINDS = [
12527
13923
  "Plan",
12528
13924
  "Manifest",
@@ -12530,7 +13926,8 @@ const SCHEMA_KINDS = [
12530
13926
  "CheckReport",
12531
13927
  "ApplyResult",
12532
13928
  "Profile",
12533
- "Journal"
13929
+ "Journal",
13930
+ "RepoSnapshot"
12534
13931
  ];
12535
13932
  const BY_KIND = {
12536
13933
  Plan: PlanSchema,
@@ -12539,22 +13936,180 @@ const BY_KIND = {
12539
13936
  CheckReport: CheckReportSchema,
12540
13937
  ApplyResult: ApplyResultSchema,
12541
13938
  Profile: ProfileSchema,
12542
- Journal: JournalSchema
13939
+ Journal: JournalSchema,
13940
+ RepoSnapshot: RepoSnapshotSchema
12543
13941
  };
12544
13942
  function isSchemaKind(v) {
12545
13943
  return typeof v === "string" && SCHEMA_KINDS.includes(v);
12546
13944
  }
12547
- function jsonSchemaFor(kind) {
12548
- const json = toJSONSchema(BY_KIND[kind], {
12549
- target: "draft-2020-12",
12550
- io: "input",
12551
- unrepresentable: "any"
12552
- });
13945
+ function jsonSchemaFor(kind) {
13946
+ const json = toJSONSchema(BY_KIND[kind], {
13947
+ target: "draft-2020-12",
13948
+ io: "input",
13949
+ unrepresentable: "any"
13950
+ });
13951
+ return {
13952
+ $id: `https://axiom.dev/schemas/v2/${kind}.schema.json`,
13953
+ title: kind,
13954
+ ...json
13955
+ };
13956
+ }
13957
+ const SIGNING_KEY_ENV = "AXIOM_SIGNING_KEY";
13958
+ function trustFilePath(root, rel = TRUST_FILE_DEFAULT) {
13959
+ return path.join(root, ...rel.split("/"));
13960
+ }
13961
+ function trustStatePath(root) {
13962
+ return path.join(root, ...TRUST_STATE_FILE.split("/"));
13963
+ }
13964
+ async function writeJsonAtomic(file, value, mode) {
13965
+ await mkdir(path.dirname(file), { recursive: true });
13966
+ const tmp = `${file}.tmp-${process.pid}`;
13967
+ await writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, {
13968
+ encoding: "utf8",
13969
+ mode
13970
+ });
13971
+ if (mode !== void 0) await chmod(tmp, mode).catch(() => void 0);
13972
+ await rename(tmp, file);
13973
+ }
13974
+ async function readJsonOrUndefined(file) {
13975
+ try {
13976
+ return JSON.parse(await readFile(file, "utf8"));
13977
+ } catch (err) {
13978
+ if (err.code === "ENOENT") return void 0;
13979
+ throw err;
13980
+ }
13981
+ }
13982
+ async function keygen(outDir, name) {
13983
+ const kp = generateKeyPair();
13984
+ const file = path.join(outDir, `axiom-signing-${kp.keyid.slice(0, 16)}.key`);
13985
+ await mkdir(outDir, { recursive: true });
13986
+ try {
13987
+ await writeFile(file, `${kp.privateKeyBase64}\n`, {
13988
+ encoding: "utf8",
13989
+ mode: 384,
13990
+ flag: "wx"
13991
+ });
13992
+ } catch (err) {
13993
+ if (err.code === "EEXIST") throw new AxiomError("ERR_EXISTS", `refusing to overwrite ${file}`, { path: file });
13994
+ throw err;
13995
+ }
13996
+ await chmod(file, 384).catch(() => void 0);
13997
+ const publicEntry = {
13998
+ keyid: kp.keyid,
13999
+ alg: "ed25519",
14000
+ publicKey: kp.publicKeyBase64
14001
+ };
14002
+ if (name !== void 0) publicEntry.name = name;
14003
+ return {
14004
+ publicEntry,
14005
+ privateKeyFile: file
14006
+ };
14007
+ }
14008
+ async function loadSigningKey(src) {
14009
+ let material;
14010
+ if (src.keyFile !== void 0) material = await readFile(path.resolve(src.keyFile), "utf8");
14011
+ else material = (src.env ?? process.env)[SIGNING_KEY_ENV];
14012
+ if (material === void 0 || material.trim().length === 0) throw new AxiomError("ERR_NOT_FOUND", `no signing key: set ${SIGNING_KEY_ENV} (base64 PKCS#8 or raw seed) or pass --key-file`);
14013
+ try {
14014
+ return privateKeyFrom(material);
14015
+ } catch (err) {
14016
+ throw new AxiomError("ERR_SIGNATURE_INVALID", "signing key material is not a usable ed25519 key", { cause: err });
14017
+ }
14018
+ }
14019
+ async function signBundle(bundle, src) {
14020
+ const key = await loadSigningKey(src);
14021
+ const keyid = keyidFor(key);
14022
+ const env = signEnvelope(bundle.manifest, key, keyid);
14023
+ const others = (bundle.signatures ?? []).filter((s) => !s.signatures.some((x) => x.keyid === keyid));
14024
+ return {
14025
+ bundle: {
14026
+ ...bundle,
14027
+ signatures: [...others, env]
14028
+ },
14029
+ keyid
14030
+ };
14031
+ }
14032
+ async function verifyBundleAgainstRoot(root, bundle, rel = TRUST_FILE_DEFAULT) {
14033
+ const store = await loadTrustStore(root, rel);
14034
+ if (store === void 0) return void 0;
14035
+ const v = verifyBundleSignatures(bundle, store, bundle.manifest.counter);
12553
14036
  return {
12554
- $id: `https://axiom.dev/schemas/v2/${kind}.schema.json`,
12555
- title: kind,
12556
- ...json
14037
+ trustFile: rel,
14038
+ keyids: v.keyids,
14039
+ findings: v.findings.map((f) => ({
14040
+ id: f.id,
14041
+ message: f.message
14042
+ })),
14043
+ ok: v.keyids.length > 0 && v.findings.length === 0
14044
+ };
14045
+ }
14046
+ async function loadTrustStore(root, rel = TRUST_FILE_DEFAULT) {
14047
+ const raw = await readJsonOrUndefined(trustFilePath(root, rel));
14048
+ if (raw === void 0) return void 0;
14049
+ const parsed = TrustStoreSchema.safeParse(raw);
14050
+ if (!parsed.success) throw new AxiomError("ERR_INVALID_PROFILE", `trust store ${rel} is invalid`, { details: { issues: parsed.error.issues.slice(0, 10).map((i) => i.message) } });
14051
+ return parsed.data;
14052
+ }
14053
+ function parsePublicEntry(raw) {
14054
+ const candidate = typeof raw === "object" && raw !== null && "publicEntry" in raw ? raw.publicEntry : raw;
14055
+ const parsed = TrustedKeySchema.safeParse(candidate);
14056
+ if (!parsed.success) throw new AxiomError("ERR_INVALID_PROFILE", "not a trusted-key entry", { details: { issues: parsed.error.issues.slice(0, 10).map((i) => i.message) } });
14057
+ const derived = keyidFor(publicKeyBase64(publicKeyFrom(parsed.data.publicKey)));
14058
+ if (derived !== parsed.data.keyid) throw new AxiomError("ERR_SIGNATURE_INVALID", "keyid does not match publicKey", { details: {
14059
+ keyid: parsed.data.keyid,
14060
+ derived
14061
+ } });
14062
+ return parsed.data;
14063
+ }
14064
+ async function trustAdd(root, entry, rel = TRUST_FILE_DEFAULT) {
14065
+ const store = await loadTrustStore(root, rel) ?? {
14066
+ version: 1,
14067
+ keys: []
14068
+ };
14069
+ const keys = store.keys.filter((k) => k.keyid !== entry.keyid);
14070
+ keys.push(entry);
14071
+ keys.sort((a, b) => a.keyid < b.keyid ? -1 : a.keyid > b.keyid ? 1 : 0);
14072
+ const next = {
14073
+ ...store,
14074
+ keys
14075
+ };
14076
+ await writeJsonAtomic(trustFilePath(root, rel), next);
14077
+ return next;
14078
+ }
14079
+ async function trustRemove(root, keyid, rel = TRUST_FILE_DEFAULT) {
14080
+ const store = await loadTrustStore(root, rel);
14081
+ if (store === void 0) throw new AxiomError("ERR_NOT_FOUND", `trust store ${rel} not found`);
14082
+ const keys = store.keys.filter((k) => k.keyid !== keyid);
14083
+ if (keys.length === store.keys.length) throw new AxiomError("ERR_NOT_FOUND", `keyid ${keyid} not in trust store`);
14084
+ const next = {
14085
+ ...store,
14086
+ keys
12557
14087
  };
14088
+ await writeJsonAtomic(trustFilePath(root, rel), next);
14089
+ return next;
14090
+ }
14091
+ async function loadTrustState(root) {
14092
+ const raw = await readJsonOrUndefined(trustStatePath(root));
14093
+ if (raw === void 0) return void 0;
14094
+ const parsed = TrustStateSchema.safeParse(raw);
14095
+ if (!parsed.success) throw new AxiomError("ERR_JOURNAL_CORRUPT", `${TRUST_STATE_FILE} is invalid`);
14096
+ return parsed.data;
14097
+ }
14098
+ async function advanceTrustState(root, bundle) {
14099
+ const counter = bundle.manifest.counter;
14100
+ if (counter === void 0) return void 0;
14101
+ const cur = await loadTrustState(root);
14102
+ if (cur !== void 0 && cur.lastCounter >= counter) return cur;
14103
+ const next = {
14104
+ version: 1,
14105
+ lastCounter: counter,
14106
+ manifestDigest: bundle.manifestDigest
14107
+ };
14108
+ await writeJsonAtomic(trustStatePath(root), next);
14109
+ return next;
14110
+ }
14111
+ function profileWantsAntiRollback(checks) {
14112
+ return checks.some((c) => c.predicate === "manifest.requireSigned" && typeof c.params === "object" && c.params !== null && c.params.antiRollback === true);
12558
14113
  }
12559
14114
  const LOG_LEVELS = [
12560
14115
  "error",
@@ -12593,7 +14148,7 @@ function createLogger(opts = {}) {
12593
14148
  };
12594
14149
  }
12595
14150
  const silentLogger = createLogger({ write: () => {} });
12596
- const realpathNative = promisify(realpath.native);
14151
+ const realpathNative = promisify(realpath$1.native);
12597
14152
  const IS_WIN32 = process.platform === "win32";
12598
14153
  async function realDir(p, code) {
12599
14154
  let real;
@@ -25387,80 +26942,205 @@ const EMPTY_COMPLETION_RESULT = { completion: {
25387
26942
  values: [],
25388
26943
  hasMore: false
25389
26944
  } };
25390
- function manifestsDir(root) {
25391
- return path.join(root, ".axiom", "manifests");
25392
- }
25393
- function reportsDir(root) {
25394
- return path.join(root, ".axiom", "reports");
25395
- }
25396
- function hexOf(ref) {
25397
- return ref.slice(7);
26945
+ const SNAPSHOT_MAX_FILES_DEFAULT = 2e4;
26946
+ const SNAPSHOT_MAX_FILES_CAP = 5e4;
26947
+ const SNAPSHOT_MAX_BYTES_DEFAULT = 67108864;
26948
+ const ALWAYS_SKIP = /* @__PURE__ */ new Set([".git", ".axiom"]);
26949
+ function globToRegExp(glob) {
26950
+ let re = "^";
26951
+ for (let i = 0; i < glob.length; i++) {
26952
+ const c = glob[i];
26953
+ if (c === "*") {
26954
+ if (glob[i + 1] === "*") {
26955
+ i++;
26956
+ if (glob[i + 1] === "/") {
26957
+ i++;
26958
+ re += "(?:.*/)?";
26959
+ } else re += ".*";
26960
+ } else re += "[^/]*";
26961
+ } else if (c === "?") re += "[^/]";
26962
+ else re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
26963
+ }
26964
+ return new RegExp(`${re}$`);
25398
26965
  }
25399
- function toDigestRef(shaOrRef) {
25400
- const ref = shaOrRef.startsWith("sha256:") ? shaOrRef : `sha256:${shaOrRef}`;
25401
- const parsed = DigestRefSchema.safeParse(ref);
25402
- if (!parsed.success) throw new AxiomError("ERR_NOT_FOUND", `not a sha256 digest: ${shaOrRef}`);
25403
- return parsed.data;
26966
+ function matcherOf(globs, whenEmpty) {
26967
+ if (globs === void 0 || globs.length === 0) return () => whenEmpty;
26968
+ const res = globs.map(globToRegExp);
26969
+ return (rel) => res.some((r) => r.test(rel));
25404
26970
  }
25405
- async function writeJsonAtomic(file, value) {
25406
- await mkdir(path.dirname(file), { recursive: true });
25407
- const tmp = `${file}.tmp-${process.pid}`;
25408
- await writeFile(tmp, JSON.stringify(value), "utf8");
25409
- await rename(tmp, file);
26971
+ function validateGlob(g, label) {
26972
+ const probe = g.replace(/\*+/g, "x").replace(/\?/g, "x").replace(/\/+$/, "");
26973
+ if (probe.length === 0 || !isValidRelPath(probe)) throw new AxiomError("ERR_CONTAINMENT", `${label} glob must be a contained relative path: ${g}`, { details: { glob: g } });
25410
26974
  }
25411
- async function readJsonOrUndefined(file) {
26975
+ async function gitignoreMatcher(root) {
26976
+ let text;
25412
26977
  try {
25413
- return JSON.parse(await readFile(file, "utf8"));
25414
- } catch (err) {
25415
- if (err.code === "ENOENT") return void 0;
25416
- throw err;
26978
+ text = await readFile(path.join(root, ".gitignore"), "utf8");
26979
+ } catch {
26980
+ return () => false;
25417
26981
  }
26982
+ const globs = [];
26983
+ for (const raw of text.split(/\r?\n/)) {
26984
+ const l = raw.trim();
26985
+ if (l.length === 0 || l.startsWith("#") || l.startsWith("!")) continue;
26986
+ let pat = l.startsWith("/") ? l.slice(1) : l.includes("/") ? l : `**/${l}`;
26987
+ if (pat.endsWith("/")) pat = pat.slice(0, -1);
26988
+ globs.push(pat, `${pat}/**`);
26989
+ }
26990
+ return matcherOf(globs, false);
25418
26991
  }
25419
- async function saveManifest(root, bundle) {
25420
- const file = path.join(manifestsDir(root), `${hexOf(bundle.manifestDigest)}.json`);
25421
- await writeJsonAtomic(file, bundle);
25422
- return file;
26992
+ function sha256File(abs) {
26993
+ return new Promise((resolve, reject) => {
26994
+ const h = createHash("sha256");
26995
+ createReadStream(abs).on("data", (chunk) => h.update(chunk)).on("error", reject).on("end", () => resolve(h.digest("hex")));
26996
+ });
25423
26997
  }
25424
- async function saveReport(root, report) {
25425
- const file = path.join(reportsDir(root), `${hexOf(report.manifestDigest)}.json`);
25426
- await writeJsonAtomic(file, report);
25427
- return file;
26998
+ function modeOf(mode) {
26999
+ return process.platform !== "win32" && (mode & 64) !== 0 ? "0755" : "0644";
27000
+ }
27001
+ async function snapshotRoot(root, opts = {}) {
27002
+ if (opts.followSymlinks === true) throw new AxiomError("ERR_UNSUPPORTED_OP", "followSymlinks is not supported (symlinks are recorded, never followed)");
27003
+ const rootReal = await realpath(root);
27004
+ const maxFiles = Math.min(opts.maxFiles ?? 2e4, SNAPSHOT_MAX_FILES_CAP);
27005
+ const maxBytes = opts.maxBytes ?? 67108864;
27006
+ if (maxFiles < 1 || maxBytes < 0) throw new AxiomError("ERR_INVALID_PLAN", "maxFiles must be ≥ 1 and maxBytes ≥ 0", { details: {
27007
+ maxFiles,
27008
+ maxBytes
27009
+ } });
27010
+ for (const g of opts.include ?? []) validateGlob(g, "include");
27011
+ for (const g of opts.exclude ?? []) validateGlob(g, "exclude");
27012
+ const include = matcherOf(opts.include, true);
27013
+ const exclude = matcherOf(opts.exclude, false);
27014
+ const ignored = opts.respectGitignore === false ? () => false : await gitignoreMatcher(rootReal);
27015
+ const withDigest = opts.withContentDigest !== false;
27016
+ const files = [];
27017
+ let bytes = 0;
27018
+ let truncated = false;
27019
+ const visit = async (dirAbs, dirRel) => {
27020
+ let entries;
27021
+ try {
27022
+ const dir = await opendir(dirAbs);
27023
+ entries = [];
27024
+ for await (const e of dir) entries.push(e);
27025
+ } catch {
27026
+ return;
27027
+ }
27028
+ const sortKey = (d) => d.isDirectory() ? `${d.name}/` : d.name;
27029
+ entries.sort((a, b) => compareUtf8(sortKey(a), sortKey(b)));
27030
+ for (const e of entries) {
27031
+ if (truncated) return;
27032
+ const rel = dirRel === "" ? e.name : `${dirRel}/${e.name}`;
27033
+ if (!isValidRelPath(rel)) continue;
27034
+ if (e.isDirectory()) {
27035
+ if (ALWAYS_SKIP.has(e.name) || ignored(rel) || exclude(rel)) continue;
27036
+ await visit(path.join(dirAbs, e.name), rel);
27037
+ continue;
27038
+ }
27039
+ if (ignored(rel) || !include(rel) || exclude(rel)) continue;
27040
+ const abs = path.join(dirAbs, e.name);
27041
+ let entry;
27042
+ if (e.isSymbolicLink()) entry = await symlinkEntry(rootReal, abs, rel, withDigest);
27043
+ else if (e.isFile()) entry = await fileEntry(abs, rel, withDigest);
27044
+ if (entry === void 0) continue;
27045
+ if (files.length >= maxFiles || bytes + entry.bytes > maxBytes) {
27046
+ truncated = true;
27047
+ return;
27048
+ }
27049
+ files.push(entry);
27050
+ bytes += entry.bytes;
27051
+ }
27052
+ };
27053
+ await visit(rootReal, "");
27054
+ files.sort((a, b) => compareUtf8(a.path, b.path));
27055
+ const body = {
27056
+ files,
27057
+ truncated,
27058
+ counts: {
27059
+ files: files.length,
27060
+ bytes
27061
+ }
27062
+ };
27063
+ return {
27064
+ apiVersion: "axiom.dev/v2",
27065
+ kind: "RepoSnapshot",
27066
+ root: { kind: "relative" },
27067
+ snapshotDigest: canonicalDigestRef(body),
27068
+ body
27069
+ };
25428
27070
  }
25429
- async function loadManifest(roots, ref) {
25430
- for (const root of roots) {
25431
- const raw = await readJsonOrUndefined(path.join(manifestsDir(root), `${hexOf(ref)}.json`));
25432
- if (raw !== void 0) return ManifestBundleSchema.parse(raw);
27071
+ async function fileEntry(abs, rel, withDigest) {
27072
+ let st;
27073
+ try {
27074
+ st = await lstat(abs);
27075
+ } catch {
27076
+ return;
25433
27077
  }
25434
- }
25435
- async function loadReport(roots, ref) {
25436
- for (const root of roots) {
25437
- const raw = await readJsonOrUndefined(path.join(reportsDir(root), `${hexOf(ref)}.json`));
25438
- if (raw !== void 0) return CheckReportSchema.parse(raw);
27078
+ if (!st.isFile()) return void 0;
27079
+ const entry = {
27080
+ path: rel,
27081
+ bytes: st.size,
27082
+ mode: modeOf(st.mode),
27083
+ kind: "file"
27084
+ };
27085
+ if (withDigest) try {
27086
+ entry.sha256 = await sha256File(abs);
27087
+ } catch {
27088
+ return;
25439
27089
  }
27090
+ return entry;
25440
27091
  }
25441
- async function loadApplied(roots, ref) {
25442
- for (const root of roots) {
25443
- const raw = await readJsonOrUndefined(appliedPath(root, ref));
25444
- if (raw !== void 0) return ApplyResultSchema.parse(raw);
27092
+ async function symlinkEntry(rootReal, abs, rel, withDigest) {
27093
+ const entry = {
27094
+ path: rel,
27095
+ bytes: 0,
27096
+ mode: "0644",
27097
+ kind: "symlink"
27098
+ };
27099
+ let target;
27100
+ try {
27101
+ target = await realpath(abs);
27102
+ } catch {
27103
+ return entry;
27104
+ }
27105
+ if (!isSameOrInside(rootReal, target)) return entry;
27106
+ let st;
27107
+ try {
27108
+ st = await lstat(target);
27109
+ } catch {
27110
+ return entry;
27111
+ }
27112
+ if (!st.isFile()) return entry;
27113
+ entry.bytes = st.size;
27114
+ entry.mode = modeOf(st.mode);
27115
+ if (withDigest) try {
27116
+ entry.sha256 = await sha256File(target);
27117
+ } catch {
27118
+ entry.bytes = 0;
25445
27119
  }
27120
+ return entry;
25446
27121
  }
25447
- async function listStored(roots, sub) {
25448
- const out = [];
25449
- for (const root of roots) {
25450
- let names;
25451
- try {
25452
- names = await readdir(path.join(root, ".axiom", sub));
25453
- } catch {
25454
- continue;
25455
- }
25456
- for (const n of names) {
25457
- const m = /^([0-9a-f]{64})\.json$/.exec(n);
25458
- if (m?.[1] !== void 0) out.push({
25459
- root,
25460
- sha: m[1]
25461
- });
25462
- }
27122
+ function diffSnapshots(a, b) {
27123
+ const key = (e) => e.sha256 ?? `bytes:${e.bytes}:${e.kind}`;
27124
+ const ma = new Map(a.body.files.map((e) => [e.path, e]));
27125
+ const mb = new Map(b.body.files.map((e) => [e.path, e]));
27126
+ const out = {
27127
+ added: [],
27128
+ removed: [],
27129
+ changed: []
27130
+ };
27131
+ for (const [p, eb] of mb) {
27132
+ const ea = ma.get(p);
27133
+ if (ea === void 0) out.added.push(p);
27134
+ else if (key(ea) !== key(eb) || ea.mode !== eb.mode || ea.kind !== eb.kind) out.changed.push({
27135
+ path: p,
27136
+ from: ea.sha256 ?? null,
27137
+ to: eb.sha256 ?? null
27138
+ });
25463
27139
  }
27140
+ for (const p of ma.keys()) if (!mb.has(p)) out.removed.push(p);
27141
+ out.added.sort(compareUtf8);
27142
+ out.removed.sort(compareUtf8);
27143
+ out.changed.sort((x, y) => compareUtf8(x.path, y.path));
25464
27144
  return out;
25465
27145
  }
25466
27146
  const BUNDLE_BYTES_MAX = 4194304;
@@ -25567,7 +27247,16 @@ const ManifestVerifyOutput = object$1({
25567
27247
  code: ErrorCodeSchema,
25568
27248
  message: string(),
25569
27249
  path: string().optional()
25570
- }))
27250
+ })),
27251
+ signatures: object$1({
27252
+ trustFile: string(),
27253
+ keyids: array(string()),
27254
+ findings: array(object$1({
27255
+ id: string(),
27256
+ message: string()
27257
+ })),
27258
+ ok: boolean()
27259
+ }).optional()
25571
27260
  });
25572
27261
  const RollbackOutput = object$1({
25573
27262
  manifestDigest: DigestRefSchema,
@@ -25631,7 +27320,10 @@ const TOOL_DEFS = [
25631
27320
  })
25632
27321
  };
25633
27322
  try {
25634
- const { bundle } = await compilePlan(parsed.data, { store: "inline" });
27323
+ const { bundle } = await compilePlan(parsed.data, {
27324
+ store: "inline",
27325
+ emitters: EMITTERS
27326
+ });
25635
27327
  return {
25636
27328
  ok: true,
25637
27329
  planDigest: bundle.manifest.planDigest,
@@ -25654,7 +27346,7 @@ const TOOL_DEFS = [
25654
27346
  defineTool({
25655
27347
  name: "axiom_plan_compile",
25656
27348
  title: "Compile a Plan into a ManifestBundle",
25657
- description: "Compile a Plan into a content-addressed ManifestBundle (sorted artifacts, sha256 digests, in-toto planDigest). `store: cas` writes blobs under <root>/.axiom/cas instead of inlining them. When a root is given the bundle is stored under <root>/.axiom/manifests/<hex>.json so later tools can reference it by digest.",
27349
+ description: "Compile a Plan into a content-addressed ManifestBundle (sorted artifacts, sha256 digests, in-toto planDigest). `store: cas` writes blobs under <root>/.axiom/cas instead of inlining them. When a root is given the bundle is stored under <root>/.axiom/manifests/<hex>.json so later tools can reference it by digest. `template` sources are rendered by the built-in `web` emitter (see `axiom emitters`); its version is recorded in toolchain.emitters.",
25658
27350
  inputSchema: {
25659
27351
  plan: LooseObject.describe("Plan document"),
25660
27352
  store: _enum(["inline", "cas"]).optional().describe("Blob transport; default inline"),
@@ -25665,7 +27357,10 @@ const TOOL_DEFS = [
25665
27357
  async handler(ctx, { plan, store, root }) {
25666
27358
  guardPayloadSize("plan", plan);
25667
27359
  const rootReal = root !== void 0 || store === "cas" ? (await resolveRoot(ctx.policy, root)).rootReal : void 0;
25668
- const opts = { store: store ?? "inline" };
27360
+ const opts = {
27361
+ store: store ?? "inline",
27362
+ emitters: EMITTERS
27363
+ };
25669
27364
  if (rootReal !== void 0) opts.root = rootReal;
25670
27365
  const { bundle } = await compilePlan(plan, opts);
25671
27366
  if (rootReal !== void 0) {
@@ -25690,11 +27385,14 @@ const TOOL_DEFS = [
25690
27385
  defineTool({
25691
27386
  name: "axiom_manifest_verify",
25692
27387
  title: "Verify a ManifestBundle",
25693
- description: "Structural and content-address verification: schema, recomputed manifestDigest, every inline blob hashes to its key, attestation subject matches. Never writes.",
25694
- inputSchema: { bundle: LooseObject.describe("ManifestBundle") },
27388
+ description: "Structural and content-address verification: schema, recomputed manifestDigest, every inline blob hashes to its key, attestation subject matches. When a root with .axiom/trust/keys.json is available, detached DSSE signatures are verified and the trusted keyids are reported under `signatures`. Never writes.",
27389
+ inputSchema: {
27390
+ bundle: LooseObject.describe("ManifestBundle"),
27391
+ root: RootArg
27392
+ },
25695
27393
  outputSchema: ManifestVerifyOutput,
25696
27394
  annotations: READ,
25697
- async handler(_ctx, { bundle }) {
27395
+ async handler(ctx, { bundle, root }) {
25698
27396
  guardPayloadSize("bundle", bundle);
25699
27397
  const r = verifyBundle(bundle);
25700
27398
  const out = {
@@ -25705,12 +27403,25 @@ const TOOL_DEFS = [
25705
27403
  errors: r.errors
25706
27404
  };
25707
27405
  if (r.manifestDigest !== void 0) out.manifestDigest = r.manifestDigest;
27406
+ if (r.ok) {
27407
+ const rootReal = await optionalRoot(ctx, root);
27408
+ if (rootReal !== void 0) {
27409
+ const sig = await verifyBundleAgainstRoot(rootReal, parseBundle(bundle));
27410
+ if (sig !== void 0) {
27411
+ out.signatures = sig;
27412
+ out.signed = sig.keyids.length > 0;
27413
+ if (!sig.ok) out.ok = false;
27414
+ }
27415
+ }
27416
+ }
25708
27417
  return out;
25709
27418
  },
25710
27419
  summarize: (o) => ({
25711
27420
  ok: o.ok,
25712
27421
  manifestDigest: o.manifestDigest,
25713
27422
  canonical: o.canonical,
27423
+ signed: o.signed,
27424
+ keyids: o.signatures?.keyids,
25714
27425
  missing: o.missing.length,
25715
27426
  errors: o.errors.slice(0, 20)
25716
27427
  })
@@ -25776,6 +27487,7 @@ const TOOL_DEFS = [
25776
27487
  manifestDigest: parsed.manifestDigest
25777
27488
  } });
25778
27489
  const { rootReal } = await resolveRoot(ctx.policy, root);
27490
+ const profileDoc = await profileFor(ctx, parsed, profile, rootReal);
25779
27491
  const result = await apply({
25780
27492
  bundle: parsed,
25781
27493
  root: rootReal,
@@ -25789,6 +27501,7 @@ const TOOL_DEFS = [
25789
27501
  await saveManifest(rootReal, parsed);
25790
27502
  ctx.seenRoots.add(rootReal);
25791
27503
  }
27504
+ if (result.status === "applied" && profileWantsAntiRollback([...profileDoc.checks, ...parsed.manifest.checks])) await advanceTrustState(rootReal, parsed);
25792
27505
  ctx.log.info("apply", {
25793
27506
  manifestDigest: parsed.manifestDigest,
25794
27507
  status: result.status,
@@ -25891,6 +27604,46 @@ const TOOL_DEFS = [
25891
27604
  return { roots };
25892
27605
  },
25893
27606
  summarize: (o) => o
27607
+ }),
27608
+ defineTool({
27609
+ name: "axiom_repo_snapshot",
27610
+ title: "Snapshot a root",
27611
+ description: "Deterministic, content-addressed inventory of a root: every regular file (and symlink) as { path, bytes, sha256, mode, kind }, sorted by code point, with snapshotDigest = sha256(JCS(body)). No timestamps, no absolute paths — the same tree gives the same digest on every machine. Honours the root .gitignore, always skips .git/ and .axiom/, never follows symlinks, never leaves the root. Use it to build Plans against real pre-image digests, or diff two snapshots with `axiom snapshot-diff`.",
27612
+ inputSchema: {
27613
+ root: RootArg,
27614
+ include: array(string().min(1)).optional().describe("Relative globs (*, **, ?) to keep; default everything"),
27615
+ exclude: array(string().min(1)).optional().describe("Relative globs to drop"),
27616
+ maxFiles: int().min(1).max(SNAPSHOT_MAX_FILES_CAP).default(SNAPSHOT_MAX_FILES_DEFAULT).describe(`Stop after this many files (cap ${SNAPSHOT_MAX_FILES_CAP}); sets truncated`),
27617
+ maxBytes: int().nonnegative().default(SNAPSHOT_MAX_BYTES_DEFAULT).describe("Stop once the summed size would exceed this; sets truncated"),
27618
+ followSymlinks: literal(false).default(false).describe("Always false; symlinks are recorded, never followed"),
27619
+ respectGitignore: boolean().default(true),
27620
+ withContentDigest: boolean().default(true).describe("false → sizes only, no sha256")
27621
+ },
27622
+ outputSchema: RepoSnapshotSchema,
27623
+ annotations: READ,
27624
+ async handler(ctx, input) {
27625
+ const { rootReal } = await resolveRoot(ctx.policy, input.root);
27626
+ const opts = {
27627
+ maxFiles: input.maxFiles,
27628
+ maxBytes: input.maxBytes,
27629
+ respectGitignore: input.respectGitignore,
27630
+ withContentDigest: input.withContentDigest
27631
+ };
27632
+ if (input.include !== void 0) opts.include = input.include;
27633
+ if (input.exclude !== void 0) opts.exclude = input.exclude;
27634
+ const snap = await snapshotRoot(rootReal, opts);
27635
+ ctx.log.debug("snapshot", {
27636
+ root: rootReal,
27637
+ files: snap.body.counts.files
27638
+ });
27639
+ return snap;
27640
+ },
27641
+ summarize: (o) => ({
27642
+ snapshotDigest: o.snapshotDigest,
27643
+ counts: o.body.counts,
27644
+ truncated: o.body.truncated,
27645
+ paths: o.body.files.slice(0, 20).map((f) => f.path)
27646
+ })
25894
27647
  })
25895
27648
  ];
25896
27649
  async function resolveBundleOrRef(ctx, v, label) {
@@ -26083,6 +27836,10 @@ function createServer(policy, opts = {}) {
26083
27836
  text: JSON.stringify(jsonSchemaFor(k), null, 2)
26084
27837
  }] };
26085
27838
  });
27839
+ server.registerResource("emitters", "axiom://emitters", {
27840
+ title: "Template emitters available to axiom_plan_compile",
27841
+ mimeType: "application/json"
27842
+ }, async (uri) => json(uri.href, emitterCatalogue()));
26086
27843
  log.info("server created", {
26087
27844
  name: SERVER_NAME,
26088
27845
  version: SERVER_VERSION,
@@ -26097,20 +27854,34 @@ const help = (version) => `axiom ${version} — transactional write gate for AI
26097
27854
 
26098
27855
  Usage:
26099
27856
  axiom mcp [--root <abs>]... [--allow-guards] [--guard-allowlist <abs>]... [--log-level ${LOG_LEVELS.join("|")}]
27857
+ [--http <host:port>] [--http-token-env <NAME>]
26100
27858
  axiom compile <plan.json|plan.axm> [-o <out.json>] [--store inline|cas] [--root <dir>]
26101
- axiom verify <bundle.json>
27859
+ [--allow-net [--net-allow <host>[,host]]] [--allow-file] (ref sources; offline by default)
27860
+ axiom verify <bundle.json> [--root <dir>] (with --root: also verify signatures against .axiom/trust/keys.json)
26102
27861
  axiom check <bundle.json> --root <dir> [--profile <name>] [--json] [--allow-guards] [--guard-allowlist <abs>]...
26103
27862
  axiom apply <bundle.json> --root <dir> [--dry-run] [--profile <name>] [--confirm <digest>]
26104
27863
  [--pr [--branch <name>] [--message <text>]]
26105
27864
  [--allow-guards] [--guard-allowlist <abs>]...
26106
27865
  axiom rollback <digest> --root <dir>
27866
+ axiom gc --root <dir> [--dry-run] [--older-than <n>(ms|s|m|h|d)] [--keep all-manifests|journal] (CAS garbage collection; CLI only)
26107
27867
  axiom diff <a.json> <b.json>
26108
27868
  axiom schema <${SCHEMA_KINDS.join("|")}>
27869
+ axiom emitters [--json] (template emitters available to \`compile\`)
27870
+ axiom keygen [--out <dir>] [--name <label>] (ed25519; private key → file 0600, public entry → stdout)
27871
+ axiom sign <bundle.json> [--key-file <path>] [-o <out.json>] (private key from --key-file or $${SIGNING_KEY_ENV})
27872
+ axiom trust add <pubkey.json> --root <dir> | trust remove <keyid> --root <dir> | trust list --root <dir>
26109
27873
  axiom gate --stdin [--root <dir>] [--profile <file>] [--strict] [--log-level ...] (PreToolUse hook; exit 0 allow / 2 deny)
27874
+ axiom migrate v1 <manifest.json> [-o <plan.json>] [--profile <name>] [--cas <root>] [--content <dir>] [--overwrite]
27875
+ (v1 manifest → v2 Plan; exit 1 = migrated with warnings)
27876
+ axiom snapshot --root <dir> [-o <out.json>] [--include <glob>]... [--exclude <glob>]... [--max-files <n>] [--no-gitignore] [--no-digest]
27877
+ axiom snapshot-diff <a.json> <b.json> (RepoSnapshot files → { added, removed, changed })
26110
27878
  axiom --version | --help
26111
27879
 
26112
27880
  Exit codes: 0 ok · 1 verdict fail / apply failed · 2 usage or error.
26113
27881
  The \`mcp\` verb speaks JSON-RPC on stdout and logs JSON lines on stderr; every other verb prints JSON to stdout.
27882
+ With --http it serves Streamable HTTP at http://<host:port>/mcp instead (port 0 = random; the URL is logged at
27883
+ info level). A non-loopback host requires a bearer token in the env var named by --http-token-env
27884
+ (default AXIOM_HTTP_TOKEN); loopback binds accept an optional token.
26114
27885
  A \`.axm\` plan with errors prints its diagnostics as JSON and exits 2.
26115
27886
  \`guard.external\` checks run only with --allow-guards AND a profile that sets facts.allowGuards; absolute
26116
27887
  commands must additionally appear in --guard-allowlist (relative ones must live under <root>/scripts/).
@@ -26179,6 +27950,8 @@ async function cmdMcp(argv) {
26179
27950
  multiple: true
26180
27951
  },
26181
27952
  "log-level": { type: "string" },
27953
+ http: { type: "string" },
27954
+ "http-token-env": { type: "string" },
26182
27955
  ...GUARD_FLAGS
26183
27956
  });
26184
27957
  const level = values["log-level"] ?? "warn";
@@ -26188,6 +27961,32 @@ async function cmdMcp(argv) {
26188
27961
  if (policy.roots.size === 0) log.warn("no --root given; every root-taking tool will fail with ERR_ROOT_REQUIRED");
26189
27962
  const guards = guardOptions(values);
26190
27963
  if (guards.allowGuards) log.warn("external guards ENABLED (--allow-guards)", { allowlist: guards.guardAllowlist });
27964
+ if (values.http !== void 0) {
27965
+ const { parseHostPort, startHttp, HTTP_TOKEN_ENV_DEFAULT } = await import("./http-lazy.js");
27966
+ const { host, port } = parseHostPort(values.http);
27967
+ const tokenEnv = values["http-token-env"] ?? HTTP_TOKEN_ENV_DEFAULT;
27968
+ const token = process.env[tokenEnv];
27969
+ const httpOpts = {
27970
+ host,
27971
+ port,
27972
+ log
27973
+ };
27974
+ if (token !== void 0 && token.length > 0) httpOpts.token = token;
27975
+ const handle = await startHttp(() => createServer(policy, {
27976
+ log,
27977
+ guards
27978
+ }), httpOpts);
27979
+ await new Promise((resolve) => {
27980
+ const stop = () => {
27981
+ handle.close().then(resolve);
27982
+ };
27983
+ process.once("SIGINT", stop);
27984
+ process.once("SIGTERM", stop);
27985
+ process.stdin.on("end", stop);
27986
+ process.stdin.resume();
27987
+ });
27988
+ return EXIT_OK;
27989
+ }
26191
27990
  const server = createServer(policy, {
26192
27991
  log,
26193
27992
  guards
@@ -26208,15 +28007,31 @@ async function cmdCompile(argv) {
26208
28007
  short: "o"
26209
28008
  },
26210
28009
  store: { type: "string" },
26211
- root: { type: "string" }
28010
+ root: { type: "string" },
28011
+ "allow-net": { type: "boolean" },
28012
+ "net-allow": {
28013
+ type: "string",
28014
+ multiple: true
28015
+ },
28016
+ "allow-file": { type: "boolean" }
26212
28017
  });
26213
28018
  const file = positionals[0];
26214
28019
  if (file === void 0) throw new UsageError("compile: <plan.json|plan.axm> is required");
26215
28020
  const store = values.store ?? "inline";
26216
28021
  if (store !== "inline" && store !== "cas") throw new UsageError("--store must be inline|cas");
26217
- const compileOpts = { store };
28022
+ const allowNet = values["allow-net"] === true;
28023
+ const allowlist = (values["net-allow"] ?? []).flatMap((s) => s.split(",")).filter((s) => s !== "");
28024
+ if (!allowNet && allowlist.length > 0) throw new UsageError("--net-allow requires --allow-net");
28025
+ const net = { allowNet };
28026
+ if (allowlist.length > 0) net.allowlist = allowlist;
28027
+ if (values["allow-file"] === true) net.allowFile = true;
28028
+ const compileOpts = {
28029
+ store,
28030
+ emitters: EMITTERS,
28031
+ net
28032
+ };
26218
28033
  let rootReal;
26219
- if (values.root !== void 0 || store === "cas") {
28034
+ if (values.root !== void 0 || store === "cas" || allowNet || net.allowFile === true) {
26220
28035
  rootReal = await realRootArg(values.root ?? ".");
26221
28036
  compileOpts.root = rootReal;
26222
28037
  }
@@ -26233,12 +28048,30 @@ async function cmdCompile(argv) {
26233
28048
  return EXIT_OK;
26234
28049
  }
26235
28050
  async function cmdVerify(argv) {
26236
- const { positionals } = opts(argv, {});
28051
+ const { values, positionals } = opts(argv, { root: { type: "string" } });
26237
28052
  const file = positionals[0];
26238
28053
  if (file === void 0) throw new UsageError("verify: <bundle.json> is required");
26239
- const r = verifyBundle(await readJson(file));
26240
- out(r);
26241
- return r.ok ? EXIT_OK : EXIT_FAIL;
28054
+ const raw = await readJson(file);
28055
+ const r = verifyBundle(raw);
28056
+ if (values.root === void 0 || !r.ok) {
28057
+ out(r);
28058
+ return r.ok ? EXIT_OK : EXIT_FAIL;
28059
+ }
28060
+ const sig = await verifyBundleAgainstRoot(await realRootArg(values.root), parseBundleFile(raw));
28061
+ if (sig === void 0) {
28062
+ out({
28063
+ ...r,
28064
+ signatures: null,
28065
+ note: "no .axiom/trust/keys.json under root"
28066
+ });
28067
+ return EXIT_OK;
28068
+ }
28069
+ out({
28070
+ ...r,
28071
+ signed: sig.keyids.length > 0,
28072
+ signatures: sig
28073
+ });
28074
+ return sig.ok ? EXIT_OK : EXIT_FAIL;
26242
28075
  }
26243
28076
  async function loadProfileFor(rootReal, name) {
26244
28077
  return loadProfile(name, { searchDirs: [path.join(rootReal, ".axiom", "profiles")] });
@@ -26314,6 +28147,7 @@ async function cmdApply(argv) {
26314
28147
  if (values.message !== void 0) applyOpts.commitMessage = values.message;
26315
28148
  const result = await apply(applyOpts);
26316
28149
  if (result.status === "applied" || result.status === "noop") await saveManifest(rootReal, bundle);
28150
+ if (result.status === "applied" && !dryRun && profileWantsAntiRollback([...profile.checks, ...bundle.manifest.checks])) await advanceTrustState(rootReal, bundle);
26317
28151
  out(result);
26318
28152
  return result.status === "failed" || result.status === "rolled-back" ? EXIT_FAIL : EXIT_OK;
26319
28153
  }
@@ -26324,6 +28158,28 @@ async function cmdRollback(argv) {
26324
28158
  out(await rollback(await realRootArg(values.root), toDigestRef(digest)));
26325
28159
  return EXIT_OK;
26326
28160
  }
28161
+ async function cmdGc(argv) {
28162
+ const { values } = opts(argv, {
28163
+ root: { type: "string" },
28164
+ "dry-run": { type: "boolean" },
28165
+ "older-than": { type: "string" },
28166
+ keep: { type: "string" }
28167
+ });
28168
+ const rootReal = await realRootArg(values.root);
28169
+ const keep = values.keep ?? "all-manifests";
28170
+ if (keep !== "all-manifests" && keep !== "journal") throw new UsageError("--keep must be all-manifests|journal");
28171
+ const gcOpts = {
28172
+ keep,
28173
+ dryRun: values["dry-run"] === true
28174
+ };
28175
+ if (values["older-than"] !== void 0) {
28176
+ const ms = parseDuration(values["older-than"]);
28177
+ if (ms === void 0) throw new UsageError("--older-than must be <n>(ms|s|m|h|d)");
28178
+ gcOpts.olderThanMs = ms;
28179
+ }
28180
+ out(await collectGarbage(rootReal, gcOpts));
28181
+ return EXIT_OK;
28182
+ }
26327
28183
  async function cmdDiff(argv) {
26328
28184
  const { positionals } = opts(argv, {});
26329
28185
  const [a, b] = positionals;
@@ -26339,6 +28195,86 @@ async function cmdSchema(argv) {
26339
28195
  out(jsonSchemaFor(kind));
26340
28196
  return EXIT_OK;
26341
28197
  }
28198
+ async function cmdEmitters(argv) {
28199
+ const { values } = opts(argv, { json: { type: "boolean" } });
28200
+ const rows = emitterCatalogue();
28201
+ if (values.json) {
28202
+ out(rows);
28203
+ return EXIT_OK;
28204
+ }
28205
+ for (const r of rows) console.log(`${r.emitter}@${r.version}: ${r.template} — ${r.description}`);
28206
+ return EXIT_OK;
28207
+ }
28208
+ async function cmdKeygen(argv) {
28209
+ const { values } = opts(argv, {
28210
+ out: { type: "string" },
28211
+ name: { type: "string" }
28212
+ });
28213
+ const r = await keygen(path.resolve(values.out ?? "."), values.name);
28214
+ out({
28215
+ publicEntry: r.publicEntry,
28216
+ privateKeyFile: r.privateKeyFile
28217
+ });
28218
+ return EXIT_OK;
28219
+ }
28220
+ async function cmdSign(argv) {
28221
+ const { values, positionals } = opts(argv, {
28222
+ "key-file": { type: "string" },
28223
+ out: {
28224
+ type: "string",
28225
+ short: "o"
28226
+ }
28227
+ });
28228
+ const file = positionals[0];
28229
+ if (file === void 0) throw new UsageError("sign: <bundle.json> is required");
28230
+ const bundle = parseBundleFile(await readJson(file));
28231
+ const src = {};
28232
+ if (values["key-file"] !== void 0) src.keyFile = values["key-file"];
28233
+ const { bundle: signed, keyid } = await signBundle(bundle, src);
28234
+ const target = values.out ?? file;
28235
+ await writeFile(path.resolve(target), `${JSON.stringify(signed, null, 2)}\n`, "utf8");
28236
+ out({
28237
+ manifestDigest: signed.manifestDigest,
28238
+ keyid,
28239
+ signatures: signed.signatures?.length ?? 0,
28240
+ out: target
28241
+ });
28242
+ return EXIT_OK;
28243
+ }
28244
+ async function cmdTrust(argv) {
28245
+ const [sub, ...rest] = argv;
28246
+ const { values, positionals } = opts(rest, { root: { type: "string" } });
28247
+ const rootReal = await realRootArg(values.root);
28248
+ switch (sub) {
28249
+ case "list":
28250
+ out(await loadTrustStore(rootReal) ?? {
28251
+ version: 1,
28252
+ keys: []
28253
+ });
28254
+ return EXIT_OK;
28255
+ case "add": {
28256
+ const file = positionals[0];
28257
+ if (file === void 0) throw new UsageError("trust add: <pubkey.json> is required");
28258
+ const entry = parsePublicEntry(await readJson(file));
28259
+ const store = await trustAdd(rootReal, entry);
28260
+ out({
28261
+ added: entry.keyid,
28262
+ keys: store.keys.length
28263
+ });
28264
+ return EXIT_OK;
28265
+ }
28266
+ case "remove": {
28267
+ const keyid = positionals[0];
28268
+ if (keyid === void 0) throw new UsageError("trust remove: <keyid> is required");
28269
+ out({
28270
+ removed: keyid,
28271
+ keys: (await trustRemove(rootReal, keyid)).keys.length
28272
+ });
28273
+ return EXIT_OK;
28274
+ }
28275
+ default: throw new UsageError("trust: subcommand must be add|remove|list");
28276
+ }
28277
+ }
26342
28278
  async function cmdGate(argv) {
26343
28279
  const { gateMain } = await import("./gate-lazy.js");
26344
28280
  const r = await gateMain(argv);
@@ -26346,6 +28282,112 @@ async function cmdGate(argv) {
26346
28282
  if (r.stdout !== void 0) console.log(r.stdout);
26347
28283
  return r.exitCode;
26348
28284
  }
28285
+ async function cmdMigrate(argv) {
28286
+ const [from, ...rest] = argv;
28287
+ if (from !== "v1") throw new UsageError("migrate: source format must be v1");
28288
+ const { values, positionals } = opts(rest, {
28289
+ out: {
28290
+ type: "string",
28291
+ short: "o"
28292
+ },
28293
+ profile: { type: "string" },
28294
+ cas: { type: "string" },
28295
+ content: { type: "string" },
28296
+ name: { type: "string" },
28297
+ overwrite: { type: "boolean" }
28298
+ });
28299
+ const file = positionals[0];
28300
+ if (file === void 0) throw new UsageError("migrate v1: <manifest.json> is required");
28301
+ const { migrateV1 } = await import("./migrate-lazy.js");
28302
+ const contentDir = path.resolve(values.content ?? path.dirname(path.resolve(file)));
28303
+ const migrateOpts = {
28304
+ version: MIGRATE_VERSION,
28305
+ resolveContent: async (rel) => {
28306
+ try {
28307
+ return new Uint8Array(await readFile(path.join(contentDir, rel)));
28308
+ } catch (err) {
28309
+ if (err.code === "ENOENT") return void 0;
28310
+ throw err;
28311
+ }
28312
+ }
28313
+ };
28314
+ if (values.profile !== void 0) migrateOpts.profile = values.profile;
28315
+ if (values.name !== void 0) migrateOpts.name = values.name;
28316
+ if (values.overwrite === true) migrateOpts.overwrite = true;
28317
+ if (values.cas !== void 0) migrateOpts.casRoot = await realRootArg(values.cas);
28318
+ const { plan, report } = await migrateV1(await readJson(file), migrateOpts);
28319
+ if (values.out !== void 0) {
28320
+ await writeFile(path.resolve(values.out), `${JSON.stringify(plan, null, 2)}\n`, "utf8");
28321
+ out({
28322
+ ...report,
28323
+ out: values.out
28324
+ });
28325
+ } else out({
28326
+ plan,
28327
+ report
28328
+ });
28329
+ return report.ok ? EXIT_OK : EXIT_FAIL;
28330
+ }
28331
+ let MIGRATE_VERSION = "0.0.0";
28332
+ async function cmdSnapshot(argv) {
28333
+ const { values } = opts(argv, {
28334
+ root: { type: "string" },
28335
+ out: {
28336
+ type: "string",
28337
+ short: "o"
28338
+ },
28339
+ include: {
28340
+ type: "string",
28341
+ multiple: true
28342
+ },
28343
+ exclude: {
28344
+ type: "string",
28345
+ multiple: true
28346
+ },
28347
+ "max-files": { type: "string" },
28348
+ "max-bytes": { type: "string" },
28349
+ "no-gitignore": { type: "boolean" },
28350
+ "no-digest": { type: "boolean" }
28351
+ });
28352
+ const rootReal = await realRootArg(values.root);
28353
+ const snapOpts = {
28354
+ respectGitignore: values["no-gitignore"] !== true,
28355
+ withContentDigest: values["no-digest"] !== true
28356
+ };
28357
+ if (values.include !== void 0) snapOpts.include = values.include;
28358
+ if (values.exclude !== void 0) snapOpts.exclude = values.exclude;
28359
+ for (const [flag, key] of [["max-files", "maxFiles"], ["max-bytes", "maxBytes"]]) {
28360
+ const raw = values[flag];
28361
+ if (raw === void 0) continue;
28362
+ const n = Number(raw);
28363
+ if (!Number.isInteger(n) || n < 0) throw new UsageError(`--${flag} must be a non-negative integer`);
28364
+ snapOpts[key] = n;
28365
+ }
28366
+ const snap = await snapshotRoot(rootReal, snapOpts);
28367
+ if (values.out !== void 0) {
28368
+ await writeFile(path.resolve(values.out), `${JSON.stringify(snap, null, 2)}\n`, "utf8");
28369
+ out({
28370
+ snapshotDigest: snap.snapshotDigest,
28371
+ ...snap.body.counts,
28372
+ truncated: snap.body.truncated,
28373
+ out: values.out
28374
+ });
28375
+ } else out(snap);
28376
+ return EXIT_OK;
28377
+ }
28378
+ function parseSnapshotFile(raw, label) {
28379
+ const parsed = RepoSnapshotSchema.safeParse(raw);
28380
+ if (!parsed.success) throw new AxiomError("ERR_INVALID_MANIFEST", `${label} does not match RepoSnapshotSchema`, { details: { issues: parsed.error.issues.slice(0, 20).map((i) => `${i.path.join(".")}: ${i.message}`) } });
28381
+ return parsed.data;
28382
+ }
28383
+ async function cmdSnapshotDiff(argv) {
28384
+ const { positionals } = opts(argv, {});
28385
+ const [a, b] = positionals;
28386
+ if (a === void 0 || b === void 0) throw new UsageError("snapshot-diff: <a.json> <b.json> are required");
28387
+ const [sa, sb] = await Promise.all([readJson(a), readJson(b)]);
28388
+ out(diffSnapshots(parseSnapshotFile(sa, a), parseSnapshotFile(sb, b)));
28389
+ return EXIT_OK;
28390
+ }
26349
28391
  const VERBS = {
26350
28392
  mcp: cmdMcp,
26351
28393
  compile: cmdCompile,
@@ -26353,12 +28395,21 @@ const VERBS = {
26353
28395
  check: cmdCheck,
26354
28396
  apply: cmdApply,
26355
28397
  rollback: cmdRollback,
28398
+ gc: cmdGc,
26356
28399
  diff: cmdDiff,
26357
28400
  schema: cmdSchema,
26358
- gate: cmdGate
28401
+ emitters: cmdEmitters,
28402
+ keygen: cmdKeygen,
28403
+ sign: cmdSign,
28404
+ trust: cmdTrust,
28405
+ gate: cmdGate,
28406
+ migrate: cmdMigrate,
28407
+ snapshot: cmdSnapshot,
28408
+ "snapshot-diff": cmdSnapshotDiff
26359
28409
  };
26360
28410
  async function main(argv, version) {
26361
28411
  const HELP = help(version);
28412
+ MIGRATE_VERSION = version;
26362
28413
  const [verb, ...rest] = argv;
26363
28414
  if (verb === void 0 || verb === "--help" || verb === "-h" || verb === "help") {
26364
28415
  process.stdout.write(HELP);
@@ -26387,7 +28438,7 @@ async function main(argv, version) {
26387
28438
  console.error(`error: ${err.message}\n\n${HELP}`);
26388
28439
  return EXIT_USAGE;
26389
28440
  }
26390
- if (err instanceof AxiomError) {
28441
+ if (isAxiomError(err)) {
26391
28442
  console.error(JSON.stringify(err.toJSON()));
26392
28443
  return EXIT_USAGE;
26393
28444
  }