@guilz-dev/belay 0.9.4 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +3 -2
  2. package/dist/adapters/claude/runtime-entry.js +5 -18
  3. package/dist/adapters/codex/runtime-entry.js +10 -11
  4. package/dist/adapters/cursor/hook-dispatch-entry.d.ts +3 -0
  5. package/dist/adapters/cursor/hook-dispatch-entry.js +11 -0
  6. package/dist/adapters/cursor/hook-router.d.ts +1 -0
  7. package/dist/adapters/cursor/hook-router.js +39 -9
  8. package/dist/adapters/cursor/hook-routing-health.d.ts +1 -0
  9. package/dist/adapters/cursor/hook-routing-health.js +53 -0
  10. package/dist/adapters/cursor/hooks.d.ts +1 -1
  11. package/dist/adapters/cursor/hooks.js +16 -5
  12. package/dist/adapters/cursor/routing-config-trust.d.ts +1 -0
  13. package/dist/adapters/cursor/routing-config-trust.js +67 -0
  14. package/dist/adapters/cursor/runtime-entry.d.ts +2 -2
  15. package/dist/adapters/cursor/runtime-entry.js +35 -10
  16. package/dist/adapters/shared/gate-runtime.d.ts +0 -2
  17. package/dist/adapters/shared/gate-runtime.js +12 -52
  18. package/dist/bundle/claude-runtime.mjs +1083 -669
  19. package/dist/bundle/codex-runtime.mjs +1092 -698
  20. package/dist/bundle/cursor-dispatcher.mjs +119 -28
  21. package/dist/bundle/cursor-runtime.mjs +1132 -677
  22. package/dist/cli.js +53 -4
  23. package/dist/commands/approval-token.d.ts +10 -0
  24. package/dist/commands/approval-token.js +26 -0
  25. package/dist/commands/audit.d.ts +2 -1
  26. package/dist/commands/audit.js +2 -2
  27. package/dist/commands/config.d.ts +1 -1
  28. package/dist/commands/config.js +28 -2
  29. package/dist/commands/doctor.js +14 -54
  30. package/dist/commands/dogfood-check.d.ts +3 -0
  31. package/dist/commands/dogfood-check.js +116 -0
  32. package/dist/commands/dogfood.d.ts +1 -0
  33. package/dist/commands/dogfood.js +4 -3
  34. package/dist/commands/judge.js +2 -2
  35. package/dist/commands/recovery-checkpoints.js +2 -11
  36. package/dist/config-io.d.ts +1 -0
  37. package/dist/config-io.js +8 -1
  38. package/dist/core/capability/index.d.ts +1 -1
  39. package/dist/core/capability/index.js +1 -1
  40. package/dist/core/capability/policy-engine.d.ts +19 -0
  41. package/dist/core/capability/policy-engine.js +31 -0
  42. package/dist/core/classify-tool.js +372 -148
  43. package/dist/core/config.js +2 -2
  44. package/dist/core/dogfood-environment.d.ts +8 -0
  45. package/dist/core/dogfood-environment.js +55 -0
  46. package/dist/core/effect-ir/shell-lower/decoders/belay.js +12 -0
  47. package/dist/core/egress-approval.js +0 -18
  48. package/dist/core/gate-engine.js +2 -9
  49. package/dist/core/notify.d.ts +8 -2
  50. package/dist/core/notify.js +63 -7
  51. package/dist/core/recovery/operator-guidance.js +4 -4
  52. package/dist/core/repo-config-trust.d.ts +31 -0
  53. package/dist/core/repo-config-trust.js +152 -0
  54. package/dist/corpus/benign-probe-cores.d.ts +1 -1
  55. package/dist/corpus/benign-probe-cores.js +0 -1
  56. package/dist/defaults.js +0 -25
  57. package/dist/installer/scope-config.js +2 -2
  58. package/dist/installer.js +4 -4
  59. package/dist/types.d.ts +19 -0
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/package.json +5 -1
@@ -2155,7 +2155,7 @@ function normalizeConfig(config) {
2155
2155
  policy: {
2156
2156
  unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
2157
2157
  unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
2158
- codexUnmappedTool: v4.policy?.codexUnmappedTool === "allow" ? "allow" : "deny",
2158
+ codexUnmappedTool: "allow",
2159
2159
  fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
2160
2160
  confidenceThresholds: {
2161
2161
  allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
@@ -2457,7 +2457,7 @@ var init_config = __esm({
2457
2457
  DEFAULT_POLICY_V3 = {
2458
2458
  unknownLocalEffect: "allow_flagged",
2459
2459
  unparseableShell: "deny",
2460
- codexUnmappedTool: "deny",
2460
+ codexUnmappedTool: "allow",
2461
2461
  confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
2462
2462
  modelAssist: { ...DEFAULT_MODEL_ASSIST },
2463
2463
  transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
@@ -3510,9 +3510,122 @@ var init_config_layers = __esm({
3510
3510
  }
3511
3511
  });
3512
3512
 
3513
- // src/config-io.ts
3513
+ // src/core/repo-config-trust.ts
3514
3514
  import { existsSync as existsSync3 } from "node:fs";
3515
- import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2, writeFile } from "node:fs/promises";
3515
+ import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2 } from "node:fs/promises";
3516
+ import path12 from "node:path";
3517
+ function isObjectRecord(value) {
3518
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3519
+ }
3520
+ function hasOnlyExpectedKeys(value) {
3521
+ const expected = /* @__PURE__ */ new Set([
3522
+ "schemaVersion",
3523
+ "repoRoot",
3524
+ "adapter",
3525
+ "repoConfigFingerprint",
3526
+ "trustedAt"
3527
+ ]);
3528
+ const keys = Object.keys(value);
3529
+ if (keys.length !== expected.size) {
3530
+ return false;
3531
+ }
3532
+ return keys.every((key) => expected.has(key));
3533
+ }
3534
+ function isAdapterName(value) {
3535
+ return value === "cursor" || value === "claude" || value === "codex";
3536
+ }
3537
+ function isIsoTimestamp(value) {
3538
+ return Number.isFinite(Date.parse(value));
3539
+ }
3540
+ function parseStrictTrustRecord(value) {
3541
+ if (!isObjectRecord(value) || !hasOnlyExpectedKeys(value)) {
3542
+ return null;
3543
+ }
3544
+ if (value.schemaVersion !== 1) {
3545
+ return null;
3546
+ }
3547
+ if (typeof value.repoRoot !== "string" || value.repoRoot.trim().length === 0) {
3548
+ return null;
3549
+ }
3550
+ if (!isAdapterName(value.adapter)) {
3551
+ return null;
3552
+ }
3553
+ if (typeof value.repoConfigFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(value.repoConfigFingerprint)) {
3554
+ return null;
3555
+ }
3556
+ if (typeof value.trustedAt !== "string" || !isIsoTimestamp(value.trustedAt)) {
3557
+ return null;
3558
+ }
3559
+ return {
3560
+ schemaVersion: 1,
3561
+ repoRoot: value.repoRoot,
3562
+ adapter: value.adapter,
3563
+ repoConfigFingerprint: value.repoConfigFingerprint,
3564
+ trustedAt: value.trustedAt
3565
+ };
3566
+ }
3567
+ function repoConfigFingerprint(rawConfig) {
3568
+ return hashValue(canonicalStringify(rawConfig));
3569
+ }
3570
+ function repoConfigTrustPath(repoRoot, adapter) {
3571
+ const canonicalRepoRoot = canonicalPath(repoRoot);
3572
+ const identity = hashValue(`${canonicalRepoRoot}\0${adapter}`);
3573
+ return path12.join(defaultControlPlaneDir(), "config-trust", `${identity}.json`);
3574
+ }
3575
+ async function inspectRepoConfigTrust(repoRoot, adapter, rawConfig) {
3576
+ const canonicalRepoRoot = canonicalPath(repoRoot);
3577
+ const recordPath = repoConfigTrustPath(repoRoot, adapter);
3578
+ if (!existsSync3(recordPath)) {
3579
+ return { trusted: false, recordPath, reason: "missing" };
3580
+ }
3581
+ let parsed;
3582
+ try {
3583
+ parsed = JSON.parse(await readFile2(recordPath, "utf8"));
3584
+ } catch {
3585
+ return { trusted: false, recordPath, reason: "malformed" };
3586
+ }
3587
+ const record = parseStrictTrustRecord(parsed);
3588
+ if (!record) {
3589
+ return { trusted: false, recordPath, reason: "malformed" };
3590
+ }
3591
+ if (record.adapter !== adapter || canonicalPath(record.repoRoot) !== canonicalRepoRoot) {
3592
+ return { trusted: false, recordPath, reason: "identity_mismatch" };
3593
+ }
3594
+ const fingerprint = repoConfigFingerprint(rawConfig);
3595
+ if (record.repoConfigFingerprint !== fingerprint) {
3596
+ return { trusted: false, recordPath, reason: "fingerprint_mismatch" };
3597
+ }
3598
+ return { trusted: true, recordPath, fingerprint };
3599
+ }
3600
+ async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
3601
+ const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
3602
+ if (status.trusted) {
3603
+ return;
3604
+ }
3605
+ throw new RepoConfigTrustError(status);
3606
+ }
3607
+ var TRUST_MESSAGE, RepoConfigTrustError;
3608
+ var init_repo_config_trust = __esm({
3609
+ "src/core/repo-config-trust.ts"() {
3610
+ "use strict";
3611
+ init_config();
3612
+ init_fingerprint2();
3613
+ init_path_utils();
3614
+ TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
3615
+ RepoConfigTrustError = class extends Error {
3616
+ status;
3617
+ constructor(status) {
3618
+ super(TRUST_MESSAGE);
3619
+ this.name = "RepoConfigTrustError";
3620
+ this.status = status;
3621
+ }
3622
+ };
3623
+ }
3624
+ });
3625
+
3626
+ // src/config-io.ts
3627
+ import { existsSync as existsSync4 } from "node:fs";
3628
+ import { chmod as chmod2, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, unlink as unlink3, writeFile } from "node:fs/promises";
3516
3629
  function resolveAdapterName(config) {
3517
3630
  if (config.adapter === "claude") {
3518
3631
  return "claude";
@@ -3523,10 +3636,10 @@ function resolveAdapterName(config) {
3523
3636
  return "cursor";
3524
3637
  }
3525
3638
  function detectAdapterName(repoRoot) {
3526
- if (existsSync3(configPathFor(repoRoot, "claude"))) {
3639
+ if (existsSync4(configPathFor(repoRoot, "claude"))) {
3527
3640
  return "claude";
3528
3641
  }
3529
- if (existsSync3(configPathFor(repoRoot, "codex"))) {
3642
+ if (existsSync4(configPathFor(repoRoot, "codex"))) {
3530
3643
  return "codex";
3531
3644
  }
3532
3645
  return "cursor";
@@ -3541,20 +3654,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
3541
3654
  const layout = getAdapterLayout(adapter);
3542
3655
  const configPath = configPathFor(repoRoot, adapter);
3543
3656
  let repoConfig = {};
3544
- if (existsSync3(configPath)) {
3545
- repoConfig = JSON.parse(await readFile2(configPath, "utf8"));
3657
+ if (existsSync4(configPath)) {
3658
+ repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
3546
3659
  }
3547
3660
  let teamConfig = null;
3548
3661
  const teamPath = teamConfigPath();
3549
- if (existsSync3(teamPath)) {
3550
- teamConfig = JSON.parse(await readFile2(teamPath, "utf8"));
3662
+ if (existsSync4(teamPath)) {
3663
+ teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
3551
3664
  }
3552
3665
  return resolveLayeredConfig({
3553
3666
  repoConfig,
3554
3667
  adapterDefaults: layout.defaultConfig(repoRoot),
3555
3668
  teamConfig,
3556
3669
  teamConfigPath: teamPath,
3557
- repoConfigPath: existsSync3(configPath) ? configPath : void 0
3670
+ repoConfigPath: existsSync4(configPath) ? configPath : void 0
3558
3671
  });
3559
3672
  }
3560
3673
  async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
@@ -3569,6 +3682,7 @@ var init_config_io = __esm({
3569
3682
  init_approval_state_mutation();
3570
3683
  init_config();
3571
3684
  init_config_layers();
3685
+ init_repo_config_trust();
3572
3686
  }
3573
3687
  });
3574
3688
 
@@ -4230,9 +4344,9 @@ init_claude();
4230
4344
  init_approval();
4231
4345
  init_approval_replay();
4232
4346
  import { randomUUID as randomUUID6 } from "node:crypto";
4233
- import { existsSync as existsSync16 } from "node:fs";
4234
- import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
4235
- import path61 from "node:path";
4347
+ import { existsSync as existsSync17 } from "node:fs";
4348
+ import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
4349
+ import path63 from "node:path";
4236
4350
 
4237
4351
  // src/core/approval-service.ts
4238
4352
  init_config_io();
@@ -4242,47 +4356,35 @@ init_approval_replay();
4242
4356
  // src/core/approval-token.ts
4243
4357
  init_config();
4244
4358
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
4245
- import { existsSync as existsSync4 } from "node:fs";
4246
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
4247
- import path12 from "node:path";
4248
- function base64UrlEncode(value) {
4249
- return Buffer.from(value, "utf8").toString("base64url");
4250
- }
4359
+ import { existsSync as existsSync5 } from "node:fs";
4360
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
4361
+ import path13 from "node:path";
4251
4362
  function base64UrlDecode(value) {
4252
4363
  return Buffer.from(value, "base64url").toString("utf8");
4253
4364
  }
4254
4365
  function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
4255
- return path12.join(controlPlaneDir, "approval-signing.key");
4366
+ return path13.join(controlPlaneDir, "approval-signing.key");
4256
4367
  }
4257
4368
  async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
4258
4369
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
4259
- if (existsSync4(keyPath)) {
4260
- return readFile3(keyPath);
4370
+ if (existsSync5(keyPath)) {
4371
+ return readFile4(keyPath);
4261
4372
  }
4262
- await mkdir3(controlPlaneDir, { recursive: true });
4373
+ await mkdir4(controlPlaneDir, { recursive: true });
4263
4374
  const key = randomBytes(32);
4264
4375
  await writeFile2(keyPath, key, { mode: 384 });
4265
4376
  return key;
4266
4377
  }
4267
- function signPayload(payload, key) {
4268
- const body = base64UrlEncode(JSON.stringify(payload));
4269
- const signature = createHmac("sha256", key).update(body).digest("base64url");
4270
- return `${body}.${signature}`;
4271
- }
4272
- async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
4273
- const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
4274
- return signPayload(payload, key);
4275
- }
4276
4378
  async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
4277
4379
  const [body, signature] = token.split(".");
4278
4380
  if (!body || !signature) {
4279
4381
  return null;
4280
4382
  }
4281
4383
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
4282
- if (!existsSync4(keyPath)) {
4384
+ if (!existsSync5(keyPath)) {
4283
4385
  return null;
4284
4386
  }
4285
- const key = await readFile3(keyPath);
4387
+ const key = await readFile4(keyPath);
4286
4388
  const expected = createHmac("sha256", key).update(body).digest("base64url");
4287
4389
  const actualBuffer = Buffer.from(signature);
4288
4390
  const expectedBuffer = Buffer.from(expected);
@@ -4489,11 +4591,11 @@ init_approval_v3();
4489
4591
 
4490
4592
  // src/core/capability/boundary-attestation-sign.ts
4491
4593
  import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
4492
- import { access, readFile as readFile4 } from "node:fs/promises";
4594
+ import { access, readFile as readFile5 } from "node:fs/promises";
4493
4595
  init_fingerprint2();
4494
4596
 
4495
4597
  // src/core/capability/attestation.ts
4496
- import path13 from "node:path";
4598
+ import path14 from "node:path";
4497
4599
  var BOUNDARY_ATTESTATION_VERSION = 1;
4498
4600
  var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
4499
4601
  var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
@@ -4561,7 +4663,7 @@ function validateContainedExecutionAttestation(value) {
4561
4663
  const record = value;
4562
4664
  const dockerSubstrate = record.dockerSubstrate;
4563
4665
  const dockerConfiguration = record.dockerConfiguration;
4564
- if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !path13.isAbsolute(dockerSubstrate.binaryPath) || /[\0\n\r]/.test(dockerSubstrate.binaryPath) || typeof dockerSubstrate.binarySha256 !== "string" || !/^[a-f0-9]{64}$/.test(dockerSubstrate.binarySha256) || typeof dockerSubstrate.endpoint !== "string" || !dockerSubstrate.endpoint.startsWith("unix:///") || !path13.isAbsolute(dockerSubstrate.endpoint.slice("unix://".length)) || /[\0\n\r]/.test(dockerSubstrate.endpoint) || typeof dockerSubstrate.daemonId !== "string" || !dockerSubstrate.daemonId || /[\0\n\r]/.test(dockerSubstrate.daemonId) || !isRecord(dockerConfiguration) || typeof dockerConfiguration.executable !== "string" || !path13.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path13.isAbsolute(dockerConfiguration.host.slice("unix://".length)) || /[\0\n\r]/.test(dockerConfiguration.host) || typeof record.user !== "string" || !/^\d+:\d+$/.test(record.user) || record.entrypoint !== "/bin/sh" || record.capDropAll !== true || record.noNewPrivileges !== true || record.logDriver !== "none" || record.proxyEnvironment !== "neutralized-empty" || typeof record.probedAt !== "string" || typeof record.expiresAt !== "string") {
4666
+ if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !path14.isAbsolute(dockerSubstrate.binaryPath) || /[\0\n\r]/.test(dockerSubstrate.binaryPath) || typeof dockerSubstrate.binarySha256 !== "string" || !/^[a-f0-9]{64}$/.test(dockerSubstrate.binarySha256) || typeof dockerSubstrate.endpoint !== "string" || !dockerSubstrate.endpoint.startsWith("unix:///") || !path14.isAbsolute(dockerSubstrate.endpoint.slice("unix://".length)) || /[\0\n\r]/.test(dockerSubstrate.endpoint) || typeof dockerSubstrate.daemonId !== "string" || !dockerSubstrate.daemonId || /[\0\n\r]/.test(dockerSubstrate.daemonId) || !isRecord(dockerConfiguration) || typeof dockerConfiguration.executable !== "string" || !path14.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path14.isAbsolute(dockerConfiguration.host.slice("unix://".length)) || /[\0\n\r]/.test(dockerConfiguration.host) || typeof record.user !== "string" || !/^\d+:\d+$/.test(record.user) || record.entrypoint !== "/bin/sh" || record.capDropAll !== true || record.noNewPrivileges !== true || record.logDriver !== "none" || record.proxyEnvironment !== "neutralized-empty" || typeof record.probedAt !== "string" || typeof record.expiresAt !== "string") {
4565
4667
  return false;
4566
4668
  }
4567
4669
  const probedAt = Date.parse(record.probedAt);
@@ -4631,7 +4733,7 @@ async function verifySignedBoundaryAttestation(params) {
4631
4733
  return record.attestation;
4632
4734
  }
4633
4735
  async function readSignedAttestationFile(filePath) {
4634
- return JSON.parse(await readFile4(filePath, "utf8"));
4736
+ return JSON.parse(await readFile5(filePath, "utf8"));
4635
4737
  }
4636
4738
 
4637
4739
  // src/core/capability/boundary-egress.ts
@@ -4800,13 +4902,13 @@ async function runWithBoundaryRunnable(target, params) {
4800
4902
  }
4801
4903
 
4802
4904
  // src/core/capability/boundary-session.ts
4803
- import path24 from "node:path";
4905
+ import path25 from "node:path";
4804
4906
 
4805
4907
  // src/services/egress-service.ts
4806
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
4807
- import { mkdir as mkdir4, readFile as readFile5, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
4908
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
4909
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
4808
4910
  import net from "node:net";
4809
- import path14 from "node:path";
4911
+ import path15 from "node:path";
4810
4912
  init_config_io();
4811
4913
  init_config();
4812
4914
 
@@ -4819,16 +4921,16 @@ function egressStatePaths(repoRoot, config) {
4819
4921
  const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
4820
4922
  return {
4821
4923
  stateDir,
4822
- pidPath: path14.join(stateDir, "egress-proxy.pid"),
4823
- statusPath: path14.join(stateDir, "egress-proxy.json")
4924
+ pidPath: path15.join(stateDir, "egress-proxy.pid"),
4925
+ statusPath: path15.join(stateDir, "egress-proxy.json")
4824
4926
  };
4825
4927
  }
4826
4928
  async function readStatusFile(statusPath) {
4827
- if (!existsSync5(statusPath)) {
4929
+ if (!existsSync6(statusPath)) {
4828
4930
  return null;
4829
4931
  }
4830
4932
  try {
4831
- const raw = JSON.parse(await readFile5(statusPath, "utf8"));
4933
+ const raw = JSON.parse(await readFile6(statusPath, "utf8"));
4832
4934
  if (typeof raw.pid !== "number") {
4833
4935
  return null;
4834
4936
  }
@@ -4846,9 +4948,9 @@ async function readStatusFile(statusPath) {
4846
4948
  async function isPortOpen(host, port) {
4847
4949
  return new Promise((resolve) => {
4848
4950
  const socket = net.createConnection({ host, port });
4849
- const finish = (open6) => {
4951
+ const finish = (open7) => {
4850
4952
  socket.destroy();
4851
- resolve(open6);
4953
+ resolve(open7);
4852
4954
  };
4853
4955
  socket.setTimeout(300);
4854
4956
  socket.on("connect", () => finish(true));
@@ -4859,7 +4961,7 @@ async function isPortOpen(host, port) {
4859
4961
  async function resolveLiveEgressStatus(repoRoot, config) {
4860
4962
  const { statusPath } = egressStatePaths(repoRoot, config);
4861
4963
  const statusCandidates = [statusPath];
4862
- const controlPlaneStatus = path14.join(configuredControlPlaneDir(config), "egress-proxy.json");
4964
+ const controlPlaneStatus = path15.join(configuredControlPlaneDir(config), "egress-proxy.json");
4863
4965
  if (!statusCandidates.includes(controlPlaneStatus)) {
4864
4966
  statusCandidates.push(controlPlaneStatus);
4865
4967
  }
@@ -4885,7 +4987,7 @@ function isProcessAlive(pid) {
4885
4987
  }
4886
4988
  }
4887
4989
  async function egressStatus(options = {}) {
4888
- const repoRoot = path14.resolve(options.targetDir ?? process.cwd());
4990
+ const repoRoot = path15.resolve(options.targetDir ?? process.cwd());
4889
4991
  const config = await loadConfigFile(repoRoot);
4890
4992
  const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
4891
4993
  const ownedRunning = Boolean(status);
@@ -4916,7 +5018,7 @@ init_config();
4916
5018
  import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
4917
5019
  import { createReadStream as createReadStream2 } from "node:fs";
4918
5020
  import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
4919
- import path19 from "node:path";
5021
+ import path20 from "node:path";
4920
5022
  init_fingerprint2();
4921
5023
  init_path_utils();
4922
5024
 
@@ -5095,7 +5197,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
5095
5197
  }
5096
5198
 
5097
5199
  // src/core/contained-execution/docker-policy.ts
5098
- import path15 from "node:path";
5200
+ import path16 from "node:path";
5099
5201
 
5100
5202
  // src/core/contained-execution/policy.ts
5101
5203
  var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
@@ -5224,7 +5326,7 @@ var PROXY_ENV_NAMES = [
5224
5326
  var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
5225
5327
  var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
5226
5328
  function assertSafeDockerPath(value, code) {
5227
- if (!path15.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
5329
+ if (!path16.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
5228
5330
  throw new ContainedExecutionFailureError(code);
5229
5331
  }
5230
5332
  }
@@ -5303,11 +5405,11 @@ init_path_utils();
5303
5405
  import { createHash as createHash7 } from "node:crypto";
5304
5406
  import { constants as fsConstants } from "node:fs";
5305
5407
  import {
5306
- chmod as chmod2,
5408
+ chmod as chmod3,
5307
5409
  lstat as lstat3,
5308
- mkdir as mkdir5,
5410
+ mkdir as mkdir6,
5309
5411
  mkdtemp,
5310
- open as open3,
5412
+ open as open4,
5311
5413
  opendir,
5312
5414
  readlink as readlink2,
5313
5415
  realpath,
@@ -5315,41 +5417,41 @@ import {
5315
5417
  symlink
5316
5418
  } from "node:fs/promises";
5317
5419
  import os from "node:os";
5318
- import path18 from "node:path";
5420
+ import path19 from "node:path";
5319
5421
 
5320
5422
  // src/core/transactional/file-tree.ts
5321
5423
  import { createHash as createHash6 } from "node:crypto";
5322
5424
  import { lstat as lstat2, readdir } from "node:fs/promises";
5323
- import path17 from "node:path";
5425
+ import path18 from "node:path";
5324
5426
 
5325
5427
  // src/core/transactional/file-tree-path.ts
5326
5428
  init_path_utils();
5327
- import path16 from "node:path";
5429
+ import path17 from "node:path";
5328
5430
  var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
5329
5431
  function validateRelativePath(relativePath) {
5330
5432
  if (!relativePath || relativePath.includes("\0")) {
5331
5433
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
5332
5434
  }
5333
- if (path16.isAbsolute(relativePath)) {
5435
+ if (path17.isAbsolute(relativePath)) {
5334
5436
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
5335
5437
  }
5336
5438
  if (isPathOutsideRoot(relativePath)) {
5337
5439
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
5338
5440
  }
5339
- const normalized = path16.normalize(relativePath);
5340
- if (normalized === ".." || normalized.startsWith(`..${path16.sep}`)) {
5441
+ const normalized = path17.normalize(relativePath);
5442
+ if (normalized === ".." || normalized.startsWith(`..${path17.sep}`)) {
5341
5443
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
5342
5444
  }
5343
5445
  }
5344
5446
  function joinRelativePath(root, relativePath) {
5345
5447
  validateRelativePath(relativePath);
5346
- return path16.join(canonicalPath(root), relativePath);
5448
+ return path17.join(canonicalPath(root), relativePath);
5347
5449
  }
5348
5450
  function isRootGitMetadataPath(relativePath) {
5349
- return relativePath === ".git" || relativePath.startsWith(`.git${path16.sep}`);
5451
+ return relativePath === ".git" || relativePath.startsWith(`.git${path17.sep}`);
5350
5452
  }
5351
5453
  function isNestedGitPath(relativePath) {
5352
- const segments = relativePath.split(path16.sep).filter(Boolean);
5454
+ const segments = relativePath.split(path17.sep).filter(Boolean);
5353
5455
  if (segments.length === 0) {
5354
5456
  return false;
5355
5457
  }
@@ -5516,7 +5618,7 @@ async function readPresentNode(absolutePath, counters) {
5516
5618
  async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
5517
5619
  assertWithinDeadline(deadlineMs);
5518
5620
  assertWithinQuotas(counters, quotas);
5519
- const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path17.resolve(resourceRoot);
5621
+ const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path18.resolve(resourceRoot);
5520
5622
  const dirInfo = await lstat2(absoluteDir);
5521
5623
  if (!dirInfo.isDirectory()) {
5522
5624
  throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
@@ -5542,7 +5644,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
5542
5644
  const names = await readdir(absoluteDir);
5543
5645
  for (const name of names) {
5544
5646
  assertWithinDeadline(deadlineMs);
5545
- const childRelative = relativeDir ? path17.join(relativeDir, name) : name;
5647
+ const childRelative = relativeDir ? path18.join(relativeDir, name) : name;
5546
5648
  validateRelativePath(childRelative);
5547
5649
  if (isNestedGitPath(childRelative)) {
5548
5650
  throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
@@ -5550,7 +5652,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
5550
5652
  if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
5551
5653
  continue;
5552
5654
  }
5553
- const childAbsolute = path17.join(absoluteDir, name);
5655
+ const childAbsolute = path18.join(absoluteDir, name);
5554
5656
  const childInfo = await lstat2(childAbsolute);
5555
5657
  if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
5556
5658
  await walkDirectory(
@@ -5661,16 +5763,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
5661
5763
  );
5662
5764
  }
5663
5765
  var productionDependencies = {
5664
- makeTempRoot: () => mkdtemp(path18.join(os.tmpdir(), "belay-contained-mirror-")),
5766
+ makeTempRoot: () => mkdtemp(path19.join(os.tmpdir(), "belay-contained-mirror-")),
5665
5767
  removeRoot: (root) => rm(root, { recursive: true, force: true }),
5666
5768
  now: () => Date.now()
5667
5769
  };
5668
5770
  function isAtOrWithin(root, target) {
5669
- const relative = path18.relative(root, target);
5670
- return relative === "" || !path18.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path18.sep}`);
5771
+ const relative = path19.relative(root, target);
5772
+ return relative === "" || !path19.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path19.sep}`);
5671
5773
  }
5672
5774
  function isGitMetadataRelativePath(relativePath) {
5673
- return relativePath.split(path18.sep).some((segment) => segment.toLowerCase() === ".git");
5775
+ return relativePath.split(path19.sep).some((segment) => segment.toLowerCase() === ".git");
5674
5776
  }
5675
5777
  function identityFromStats(stats) {
5676
5778
  return {
@@ -5752,7 +5854,7 @@ async function readStableFile(absolutePath, context) {
5752
5854
  if (before.nlink > 1n) {
5753
5855
  throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
5754
5856
  }
5755
- const source = await open3(absolutePath, safeReadFlags());
5857
+ const source = await open4(absolutePath, safeReadFlags());
5756
5858
  try {
5757
5859
  const opened = await source.stat({ bigint: true });
5758
5860
  assertRegularSingleLinkBigInt(opened);
@@ -5807,7 +5909,7 @@ function addMetadataRoots(context, directoryPath) {
5807
5909
  }
5808
5910
  }
5809
5911
  function pathMatchesRoots(absolutePath, roots) {
5810
- const lexical = path18.resolve(absolutePath);
5912
+ const lexical = path19.resolve(absolutePath);
5811
5913
  const canonical = canonicalPath(absolutePath);
5812
5914
  for (const root of roots) {
5813
5915
  if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
@@ -5817,7 +5919,7 @@ function pathMatchesRoots(absolutePath, roots) {
5817
5919
  return false;
5818
5920
  }
5819
5921
  function pathLexicallyMatchesRoots(absolutePath, roots) {
5820
- const lexical = path18.resolve(absolutePath);
5922
+ const lexical = path19.resolve(absolutePath);
5821
5923
  for (const root of roots) {
5822
5924
  if (isAtOrWithin(root, lexical)) {
5823
5925
  return true;
@@ -5838,11 +5940,11 @@ async function readSafeSymlink(absolutePath, context) {
5838
5940
  }
5839
5941
  const identity = identityFromStats(before);
5840
5942
  const target = await readlink2(absolutePath);
5841
- if (path18.isAbsolute(target)) {
5943
+ if (path19.isAbsolute(target)) {
5842
5944
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5843
5945
  }
5844
- const lexicalTarget = path18.resolve(path18.dirname(absolutePath), target);
5845
- if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path18.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5946
+ const lexicalTarget = path19.resolve(path19.dirname(absolutePath), target);
5947
+ if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path19.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5846
5948
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5847
5949
  }
5848
5950
  let resolvedTarget;
@@ -5878,7 +5980,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5878
5980
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5879
5981
  }
5880
5982
  const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
5881
- const directory = await open3(absoluteDirectory, directoryFlags);
5983
+ const directory = await open4(absoluteDirectory, directoryFlags);
5882
5984
  try {
5883
5985
  const opened = await directory.stat({ bigint: true });
5884
5986
  if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
@@ -5887,7 +5989,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5887
5989
  const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
5888
5990
  for await (const directoryEntry of entriesDirectory) {
5889
5991
  assertDeadline(context.deadlineMs, context.now);
5890
- const relativePath = relativeDirectory ? path18.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5992
+ const relativePath = relativeDirectory ? path19.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5891
5993
  const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
5892
5994
  const info = await lstat3(absolutePath);
5893
5995
  if (info.isSymbolicLink()) {
@@ -5971,7 +6073,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5971
6073
  }
5972
6074
  assertEntryIdentity(entry, identityFromStats(before));
5973
6075
  await context.beforeCopyOpen?.(sourcePath);
5974
- const source = await open3(sourcePath, safeReadFlags());
6076
+ const source = await open4(sourcePath, safeReadFlags());
5975
6077
  let destination;
5976
6078
  try {
5977
6079
  const opened = await source.stat({ bigint: true });
@@ -5981,8 +6083,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5981
6083
  if (entry.node.kind !== "file") {
5982
6084
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5983
6085
  }
5984
- await mkdir5(path18.dirname(destinationPath), { recursive: true, mode: 448 });
5985
- destination = await open3(
6086
+ await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
6087
+ destination = await open4(
5986
6088
  destinationPath,
5987
6089
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
5988
6090
  safePermissionMode(opened.mode)
@@ -6024,7 +6126,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
6024
6126
  if (entry.node.kind !== "file") {
6025
6127
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
6026
6128
  }
6027
- await chmod2(destinationPath, safePermissionMode(entry.node.mode));
6129
+ await chmod3(destinationPath, safePermissionMode(entry.node.mode));
6028
6130
  }
6029
6131
  async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
6030
6132
  const directoryEntries = [];
@@ -6033,7 +6135,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
6033
6135
  const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
6034
6136
  const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
6035
6137
  if (entry.node.kind === "directory") {
6036
- await mkdir5(destinationPath, { recursive: true, mode: 448 });
6138
+ await mkdir6(destinationPath, { recursive: true, mode: 448 });
6037
6139
  directoryEntries.push(entry);
6038
6140
  continue;
6039
6141
  }
@@ -6045,7 +6147,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
6045
6147
  if (current !== entry.node.target) {
6046
6148
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
6047
6149
  }
6048
- await mkdir5(path18.dirname(destinationPath), { recursive: true, mode: 448 });
6150
+ await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
6049
6151
  await symlink(entry.node.target, destinationPath);
6050
6152
  continue;
6051
6153
  }
@@ -6055,12 +6157,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
6055
6157
  if (entry.node.kind !== "directory") {
6056
6158
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
6057
6159
  }
6058
- await chmod2(
6160
+ await chmod3(
6059
6161
  joinRelativePath(destinationRoot, entry.relativePath),
6060
6162
  safePermissionMode(entry.node.mode)
6061
6163
  );
6062
6164
  }
6063
- await chmod2(destinationRoot, 448);
6165
+ await chmod3(destinationRoot, 448);
6064
6166
  }
6065
6167
  async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
6066
6168
  const [after, copied] = await Promise.all([
@@ -6105,7 +6207,7 @@ async function cleanupOwnedRoot(root, dependencies) {
6105
6207
  }
6106
6208
  async function prepareWithDependencies(options, dependencies) {
6107
6209
  validateOptions(options);
6108
- const guestWorkspacePath = path18.resolve(options.sourceRoot);
6210
+ const guestWorkspacePath = path19.resolve(options.sourceRoot);
6109
6211
  const sourceRoot = canonicalPath(options.sourceRoot);
6110
6212
  const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
6111
6213
  if (pathMatchesRoots(sourceRoot, protectedRoots)) {
@@ -6113,7 +6215,7 @@ async function prepareWithDependencies(options, dependencies) {
6113
6215
  }
6114
6216
  const guestRoot = await dependencies.makeTempRoot();
6115
6217
  try {
6116
- await chmod2(guestRoot, 448);
6218
+ await chmod3(guestRoot, 448);
6117
6219
  const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
6118
6220
  const before = await buildSafeMirrorSnapshot(
6119
6221
  sourceRoot,
@@ -6141,7 +6243,7 @@ async function prepareWithDependencies(options, dependencies) {
6141
6243
  dependencies.now,
6142
6244
  before
6143
6245
  );
6144
- await chmod2(guestRoot, 448);
6246
+ await chmod3(guestRoot, 448);
6145
6247
  const lease = {
6146
6248
  sourceRoot,
6147
6249
  hostMirrorRoot: guestRoot,
@@ -6275,14 +6377,14 @@ async function digestFile(file) {
6275
6377
  return hash.digest("hex");
6276
6378
  }
6277
6379
  async function resolveConfiguredDockerSubstrate(params) {
6278
- if (!path19.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
6380
+ if (!path20.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
6279
6381
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
6280
6382
  }
6281
6383
  if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
6282
6384
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
6283
6385
  }
6284
6386
  const configuredSocket = params.host.slice("unix://".length);
6285
- if (!path19.isAbsolute(configuredSocket)) {
6387
+ if (!path20.isAbsolute(configuredSocket)) {
6286
6388
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
6287
6389
  }
6288
6390
  let binaryPath;
@@ -6543,7 +6645,7 @@ async function validatedGuestCwd(params) {
6543
6645
  const requiredExclusions = [
6544
6646
  ...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
6545
6647
  ];
6546
- if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path19.resolve(params.repoRoot) || !path19.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
6648
+ if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path20.resolve(params.repoRoot) || !path20.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
6547
6649
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
6548
6650
  const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
6549
6651
  const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
@@ -6555,10 +6657,10 @@ async function validatedGuestCwd(params) {
6555
6657
  protectedRoots: requiredExclusions
6556
6658
  }))
6557
6659
  throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
6558
- const relative = path19.relative(params.mirror.guestWorkspacePath, path19.resolve(params.guestCwd));
6660
+ const relative = path20.relative(params.mirror.guestWorkspacePath, path20.resolve(params.guestCwd));
6559
6661
  let resolvedCwd;
6560
6662
  try {
6561
- resolvedCwd = await realpath2(path19.join(resolvedRoot, relative));
6663
+ resolvedCwd = await realpath2(path20.join(resolvedRoot, relative));
6562
6664
  } catch {
6563
6665
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
6564
6666
  }
@@ -6566,7 +6668,7 @@ async function validatedGuestCwd(params) {
6566
6668
  if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
6567
6669
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
6568
6670
  }
6569
- return path19.join(params.mirror.guestWorkspacePath, path19.relative(resolvedRoot, resolvedCwd));
6671
+ return path20.join(params.mirror.guestWorkspacePath, path20.relative(resolvedRoot, resolvedCwd));
6570
6672
  }
6571
6673
  function exactKeys(value, allowed) {
6572
6674
  const names = new Set(allowed);
@@ -6793,19 +6895,19 @@ async function executeContainedDocker(params) {
6793
6895
  init_path_utils();
6794
6896
  import { spawn as spawn3 } from "node:child_process";
6795
6897
  import os3 from "node:os";
6796
- import path21 from "node:path";
6898
+ import path22 from "node:path";
6797
6899
 
6798
6900
  // src/core/transactional/apply-observed-changes.ts
6799
- import { copyFile, lstat as lstat5, mkdir as mkdir6, mkdtemp as mkdtemp3, readlink as readlink3, rm as rm3, rmdir, symlink as symlink2 } from "node:fs/promises";
6901
+ import { copyFile, lstat as lstat5, mkdir as mkdir7, mkdtemp as mkdtemp3, readlink as readlink3, rm as rm3, rmdir, symlink as symlink2 } from "node:fs/promises";
6800
6902
  import os2 from "node:os";
6801
- import path20 from "node:path";
6903
+ import path21 from "node:path";
6802
6904
  var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
6803
6905
  var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
6804
6906
  var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
6805
6907
  async function chmodSafe(target, mode) {
6806
6908
  try {
6807
- const { chmod: chmod4 } = await import("node:fs/promises");
6808
- await chmod4(target, mode & 511);
6909
+ const { chmod: chmod5 } = await import("node:fs/promises");
6910
+ await chmod5(target, mode & 511);
6809
6911
  } catch {
6810
6912
  }
6811
6913
  }
@@ -6839,13 +6941,13 @@ async function copyPathPreservingType(source, target) {
6839
6941
  }
6840
6942
  }
6841
6943
  await removePathIfExists(target);
6842
- await mkdir6(path20.dirname(target), { recursive: true });
6944
+ await mkdir7(path21.dirname(target), { recursive: true });
6843
6945
  if (info.isSymbolicLink()) {
6844
6946
  await symlink2(await readlink3(source), target);
6845
6947
  return;
6846
6948
  }
6847
6949
  if (info.isDirectory() && !info.isSymbolicLink()) {
6848
- await mkdir6(target, { recursive: true, mode: info.mode & 511 });
6950
+ await mkdir7(target, { recursive: true, mode: info.mode & 511 });
6849
6951
  return;
6850
6952
  }
6851
6953
  if (!info.isFile()) {
@@ -6855,12 +6957,12 @@ async function copyPathPreservingType(source, target) {
6855
6957
  await chmodSafe(target, info.mode);
6856
6958
  }
6857
6959
  async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
6858
- const segments = relativePath.split(path20.sep).filter(Boolean);
6960
+ const segments = relativePath.split(path21.sep).filter(Boolean);
6859
6961
  if (segments.length <= 1) {
6860
6962
  return;
6861
6963
  }
6862
6964
  for (let index = 1; index < segments.length; index++) {
6863
- const prefix = segments.slice(0, index).join(path20.sep);
6965
+ const prefix = segments.slice(0, index).join(path21.sep);
6864
6966
  const absolute = joinRelativePath(targetRoot, prefix);
6865
6967
  let info;
6866
6968
  try {
@@ -6880,8 +6982,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
6880
6982
  }
6881
6983
  }
6882
6984
  async function restorePathFromBackup(backupPath, target, rollbackRoot) {
6883
- const stagingRoot = await mkdtemp3(path20.join(rollbackRoot, "restore-"));
6884
- const staged = path20.join(stagingRoot, "node");
6985
+ const stagingRoot = await mkdtemp3(path21.join(rollbackRoot, "restore-"));
6986
+ const staged = path21.join(stagingRoot, "node");
6885
6987
  try {
6886
6988
  await copyPathPreservingType(backupPath, staged);
6887
6989
  await copyPathPreservingType(staged, target);
@@ -6965,7 +7067,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
6965
7067
  if (change.before.kind !== "directory") {
6966
7068
  await removePathIfExists(target);
6967
7069
  }
6968
- await mkdir6(target, { recursive: true, mode: change.after.mode & 511 });
7070
+ await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
6969
7071
  await chmodSafe(target, change.after.mode);
6970
7072
  return;
6971
7073
  }
@@ -6984,7 +7086,7 @@ async function applyObservedChanges(params) {
6984
7086
  await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
6985
7087
  await assertTargetMatches(targetRoot, change);
6986
7088
  }
6987
- const backupRoot = await mkdtemp3(path20.join(os2.tmpdir(), "belay-tx-rollback-"));
7089
+ const backupRoot = await mkdtemp3(path21.join(os2.tmpdir(), "belay-tx-rollback-"));
6988
7090
  const rollbackActions = [];
6989
7091
  let mutationAttempted = false;
6990
7092
  let resourceIdentityChanged = false;
@@ -7003,12 +7105,12 @@ async function applyObservedChanges(params) {
7003
7105
  targetExists = false;
7004
7106
  }
7005
7107
  if (targetExists) {
7006
- const backupPath = path20.join(
7108
+ const backupPath = path21.join(
7007
7109
  backupRoot,
7008
7110
  String(rollbackActions.length),
7009
7111
  change.relativePath
7010
7112
  );
7011
- await mkdir6(path20.dirname(backupPath), { recursive: true });
7113
+ await mkdir7(path21.dirname(backupPath), { recursive: true });
7012
7114
  await copyPathPreservingType(target, backupPath);
7013
7115
  await assertParentChainSafe(targetRoot, change.relativePath);
7014
7116
  rollbackActions.push({
@@ -7107,7 +7209,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
7107
7209
  if (ignoreRoots.length === 0) {
7108
7210
  return false;
7109
7211
  }
7110
- const absolutePath = canonicalPath(path21.join(repoRoot, relativePath));
7212
+ const absolutePath = canonicalPath(path22.join(repoRoot, relativePath));
7111
7213
  return ignoreRoots.some(
7112
7214
  (root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
7113
7215
  );
@@ -7145,7 +7247,7 @@ async function isDirtyWorktree(repoRoot, options) {
7145
7247
  }
7146
7248
  async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
7147
7249
  const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
7148
- const worktreePath = await mkdtemp6(path21.join(os3.tmpdir(), "belay-tx-"));
7250
+ const worktreePath = await mkdtemp6(path22.join(os3.tmpdir(), "belay-tx-"));
7149
7251
  await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
7150
7252
  return {
7151
7253
  worktreePath,
@@ -7164,14 +7266,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
7164
7266
  }
7165
7267
  function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
7166
7268
  const resolvedCwd = canonicalPath(cwd);
7167
- const relative = path21.relative(canonicalPath(repoRoot), resolvedCwd);
7168
- if (isPathOutsideRoot(relative) || path21.isAbsolute(relative)) {
7269
+ const relative = path22.relative(canonicalPath(repoRoot), resolvedCwd);
7270
+ if (isPathOutsideRoot(relative) || path22.isAbsolute(relative)) {
7169
7271
  return worktreePath;
7170
7272
  }
7171
7273
  if (relative === "") {
7172
7274
  return worktreePath;
7173
7275
  }
7174
- return path21.join(worktreePath, relative);
7276
+ return path22.join(worktreePath, relative);
7175
7277
  }
7176
7278
  function runShellCommand(command, cwd, timeoutMs) {
7177
7279
  return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
@@ -7236,7 +7338,7 @@ import { spawn as spawn4 } from "node:child_process";
7236
7338
  import { randomUUID as randomUUID3 } from "node:crypto";
7237
7339
 
7238
7340
  // src/core/capability/boundary-grant-materialize.ts
7239
- import path22 from "node:path";
7341
+ import path23 from "node:path";
7240
7342
 
7241
7343
  // src/core/glob.ts
7242
7344
  init_approval();
@@ -7396,7 +7498,7 @@ function grantMatchesRequest(grant, request) {
7396
7498
  var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
7397
7499
  var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
7398
7500
  function resolveCapabilityPath(targetPath, cwd) {
7399
- const joined = path22.isAbsolute(targetPath) ? targetPath : path22.join(cwd, targetPath);
7501
+ const joined = path23.isAbsolute(targetPath) ? targetPath : path23.join(cwd, targetPath);
7400
7502
  return canonicalPath(joined);
7401
7503
  }
7402
7504
  function isPathWithinBoundaryMount(request) {
@@ -7471,7 +7573,7 @@ function materializeContainerBoundaryGrant(request, params) {
7471
7573
 
7472
7574
  // src/core/capability/boundary-workspace-mount.ts
7473
7575
  init_path_utils();
7474
- import path23 from "node:path";
7576
+ import path24 from "node:path";
7475
7577
  var HOST_PATH_ENV_VARS = [
7476
7578
  "BELAY_EGRESS_REPO_ROOT",
7477
7579
  "BELAY_JUDGE_BROKER_REPO_ROOT",
@@ -7496,23 +7598,23 @@ function validateWorkspaceMount(mount) {
7496
7598
  if (mount.cwdRelative.includes("\0")) {
7497
7599
  throw new Error("boundary_workspace_mount_invalid_cwd");
7498
7600
  }
7499
- const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7601
+ const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7500
7602
  if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
7501
7603
  throw new Error("boundary_workspace_mount_invalid_cwd");
7502
7604
  }
7503
- if (path23.posix.isAbsolute(normalizedRelative)) {
7605
+ if (path24.posix.isAbsolute(normalizedRelative)) {
7504
7606
  throw new Error("boundary_workspace_mount_invalid_cwd");
7505
7607
  }
7506
7608
  }
7507
7609
  function resolveGuestWorkdir(mount) {
7508
7610
  validateWorkspaceMount(mount);
7509
7611
  const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
7510
- const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7612
+ const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7511
7613
  if (normalizedRelative === "." || normalizedRelative === "") {
7512
7614
  return guestTargetRoot;
7513
7615
  }
7514
7616
  const segments = normalizedRelative.split("/").filter(Boolean);
7515
- return path23.posix.join(guestTargetRoot, ...segments);
7617
+ return path24.posix.join(guestTargetRoot, ...segments);
7516
7618
  }
7517
7619
  function buildWorkspaceMountSpec(mount) {
7518
7620
  validateWorkspaceMount(mount);
@@ -7803,7 +7905,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
7803
7905
  }
7804
7906
  function boundaryAttestationPath(repoRoot, config) {
7805
7907
  const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
7806
- return path24.isAbsolute(rel) ? rel : path24.join(repoRoot, rel);
7908
+ return path25.isAbsolute(rel) ? rel : path25.join(repoRoot, rel);
7807
7909
  }
7808
7910
  async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
7809
7911
  try {
@@ -7908,7 +8010,7 @@ init_capability_request_hash();
7908
8010
 
7909
8011
  // src/core/capability/gate-policy-shadow.ts
7910
8012
  init_config();
7911
- import path29 from "node:path";
8013
+ import path30 from "node:path";
7912
8014
 
7913
8015
  // src/core/verdict/judge-outbound.ts
7914
8016
  init_scrub();
@@ -7918,7 +8020,7 @@ init_path_utils();
7918
8020
 
7919
8021
  // src/core/verdict/containment.ts
7920
8022
  init_git_resource_identity();
7921
- import path25 from "node:path";
8023
+ import path26 from "node:path";
7922
8024
  init_path_utils();
7923
8025
 
7924
8026
  // src/core/verdict/persistent-paths.ts
@@ -7938,7 +8040,7 @@ function expandHome(token) {
7938
8040
  if (!home) {
7939
8041
  return token;
7940
8042
  }
7941
- return token === "~" ? home : path25.join(home, token.slice(2));
8043
+ return token === "~" ? home : path26.join(home, token.slice(2));
7942
8044
  }
7943
8045
  return token;
7944
8046
  }
@@ -7950,10 +8052,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
7950
8052
  return null;
7951
8053
  }
7952
8054
  const expanded = expandHome(token);
7953
- if (path25.isAbsolute(expanded)) {
8055
+ if (path26.isAbsolute(expanded)) {
7954
8056
  return canonicalPath(expanded);
7955
8057
  }
7956
- return canonicalPath(path25.resolve(trustedCwd, expanded));
8058
+ return canonicalPath(path26.resolve(trustedCwd, expanded));
7957
8059
  }
7958
8060
  function isGitPath(resolvedPath, repoRoot) {
7959
8061
  if (isGitMetadataPath(resolvedPath, repoRoot)) {
@@ -8070,20 +8172,20 @@ function parseTier1Json(raw) {
8070
8172
  init_judge_runtime_config();
8071
8173
 
8072
8174
  // src/core/verdict/judge-session-kill-switch.ts
8073
- import { existsSync as existsSync6 } from "node:fs";
8074
- import { mkdir as mkdir7, readFile as readFile6, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
8075
- import path26 from "node:path";
8175
+ import { existsSync as existsSync7 } from "node:fs";
8176
+ import { mkdir as mkdir8, readFile as readFile7, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
8177
+ import path27 from "node:path";
8076
8178
  var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
8077
8179
  function judgeSessionKillSwitchPath(stateDir) {
8078
- return path26.join(stateDir, JUDGE_SESSION_KILL_FILE);
8180
+ return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
8079
8181
  }
8080
8182
  async function readJudgeSessionKillSwitch(stateDir) {
8081
8183
  const filePath = judgeSessionKillSwitchPath(stateDir);
8082
- if (!existsSync6(filePath)) {
8184
+ if (!existsSync7(filePath)) {
8083
8185
  return null;
8084
8186
  }
8085
8187
  try {
8086
- const raw = JSON.parse(await readFile6(filePath, "utf8"));
8188
+ const raw = JSON.parse(await readFile7(filePath, "utf8"));
8087
8189
  return raw.triggered === true ? raw : null;
8088
8190
  } catch {
8089
8191
  return null;
@@ -8094,7 +8196,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
8094
8196
  return record?.triggered === true;
8095
8197
  }
8096
8198
  async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
8097
- await mkdir7(stateDir, { recursive: true, mode: 448 });
8199
+ await mkdir8(stateDir, { recursive: true, mode: 448 });
8098
8200
  const record = {
8099
8201
  triggered: true,
8100
8202
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8255,10 +8357,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
8255
8357
 
8256
8358
  // src/core/verdict/judge-broker-service.ts
8257
8359
  import { spawn as spawn6 } from "node:child_process";
8258
- import { existsSync as existsSync7 } from "node:fs";
8259
- import { unlink as unlink5 } from "node:fs/promises";
8360
+ import { existsSync as existsSync8 } from "node:fs";
8361
+ import { unlink as unlink6 } from "node:fs/promises";
8260
8362
  import net2 from "node:net";
8261
- import path27 from "node:path";
8363
+ import path28 from "node:path";
8262
8364
  import { fileURLToPath } from "node:url";
8263
8365
 
8264
8366
  // src/core/verdict/judge-cli.ts
@@ -8804,10 +8906,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
8804
8906
  function judgeBrokerPaths(stateDir) {
8805
8907
  return {
8806
8908
  stateDir,
8807
- socketPath: path27.join(stateDir, JUDGE_BROKER_SOCKET),
8808
- statusPath: path27.join(stateDir, JUDGE_BROKER_STATUS),
8809
- pidPath: path27.join(stateDir, JUDGE_BROKER_PID),
8810
- sessionConfigPath: path27.join(stateDir, JUDGE_BROKER_SESSION)
8909
+ socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
8910
+ statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
8911
+ pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
8912
+ sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
8811
8913
  };
8812
8914
  }
8813
8915
  function daemonScriptPath() {
@@ -8825,12 +8927,12 @@ function useInProcessBroker(env = process.env) {
8825
8927
  return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
8826
8928
  }
8827
8929
  async function readBrokerStatus(statusPath) {
8828
- if (!existsSync7(statusPath)) {
8930
+ if (!existsSync8(statusPath)) {
8829
8931
  return null;
8830
8932
  }
8831
8933
  try {
8832
- const { readFile: readFile16 } = await import("node:fs/promises");
8833
- const raw = JSON.parse(await readFile16(statusPath, "utf8"));
8934
+ const { readFile: readFile17 } = await import("node:fs/promises");
8935
+ const raw = JSON.parse(await readFile17(statusPath, "utf8"));
8834
8936
  if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
8835
8937
  return null;
8836
8938
  }
@@ -8840,13 +8942,13 @@ async function readBrokerStatus(statusPath) {
8840
8942
  }
8841
8943
  }
8842
8944
  async function readBrokerSessionConfig(sessionConfigPath) {
8843
- if (!existsSync7(sessionConfigPath)) {
8945
+ if (!existsSync8(sessionConfigPath)) {
8844
8946
  return null;
8845
8947
  }
8846
8948
  try {
8847
- const { readFile: readFile16 } = await import("node:fs/promises");
8949
+ const { readFile: readFile17 } = await import("node:fs/promises");
8848
8950
  const raw = JSON.parse(
8849
- await readFile16(sessionConfigPath, "utf8")
8951
+ await readFile17(sessionConfigPath, "utf8")
8850
8952
  );
8851
8953
  return normalizeJudgeSessionConfig({ ...raw, enabled: true });
8852
8954
  } catch {
@@ -8937,7 +9039,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8937
9039
  const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
8938
9040
  const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
8939
9041
  const status = await readBrokerStatus(paths.statusPath);
8940
- if (status && isProcessAlive2(status.pid) && existsSync7(paths.socketPath) && !configChanged) {
9042
+ if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
8941
9043
  await writeBrokerSessionConfig(paths, brokerSessionConfig);
8942
9044
  return paths;
8943
9045
  }
@@ -8959,7 +9061,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8959
9061
  child.unref();
8960
9062
  const deadline = Date.now() + sessionConfig.connectTimeoutMs;
8961
9063
  while (Date.now() < deadline) {
8962
- if (existsSync7(paths.socketPath)) {
9064
+ if (existsSync8(paths.socketPath)) {
8963
9065
  const live = await readBrokerStatus(paths.statusPath);
8964
9066
  if (live && isProcessAlive2(live.pid)) {
8965
9067
  return paths;
@@ -8976,8 +9078,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
8976
9078
  paths.pidPath,
8977
9079
  paths.sessionConfigPath
8978
9080
  ]) {
8979
- if (existsSync7(artifact)) {
8980
- await unlink5(artifact).catch(() => void 0);
9081
+ if (existsSync8(artifact)) {
9082
+ await unlink6(artifact).catch(() => void 0);
8981
9083
  }
8982
9084
  }
8983
9085
  }
@@ -9473,9 +9575,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
9473
9575
  }
9474
9576
 
9475
9577
  // src/core/capability/gate-shadow-ratchet.ts
9476
- import { existsSync as existsSync8 } from "node:fs";
9477
- import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
9478
- import path28 from "node:path";
9578
+ import { existsSync as existsSync9 } from "node:fs";
9579
+ import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
9580
+ import path29 from "node:path";
9479
9581
  var DEFAULT_STATE = {
9480
9582
  version: 1,
9481
9583
  policyJudgeComparisons: 0,
@@ -9484,15 +9586,15 @@ var DEFAULT_STATE = {
9484
9586
  updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
9485
9587
  };
9486
9588
  function ratchetPath(stateDir) {
9487
- return path28.join(stateDir, "gate-shadow-ratchet.json");
9589
+ return path29.join(stateDir, "gate-shadow-ratchet.json");
9488
9590
  }
9489
9591
  async function loadState(stateDir) {
9490
9592
  const filePath = ratchetPath(stateDir);
9491
- if (!existsSync8(filePath)) {
9593
+ if (!existsSync9(filePath)) {
9492
9594
  return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
9493
9595
  }
9494
9596
  try {
9495
- const raw = JSON.parse(await readFile7(filePath, "utf8"));
9597
+ const raw = JSON.parse(await readFile8(filePath, "utf8"));
9496
9598
  return {
9497
9599
  ...DEFAULT_STATE,
9498
9600
  ...raw,
@@ -9503,7 +9605,7 @@ async function loadState(stateDir) {
9503
9605
  }
9504
9606
  }
9505
9607
  async function saveState(stateDir, state) {
9506
- await mkdir8(stateDir, { recursive: true, mode: 448 });
9608
+ await mkdir9(stateDir, { recursive: true, mode: 448 });
9507
9609
  await writeFile5(
9508
9610
  ratchetPath(stateDir),
9509
9611
  `${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
@@ -9626,7 +9728,7 @@ function scheduleGateShadowAudit(params) {
9626
9728
  async function runGatePolicyShadowComparison(params) {
9627
9729
  const judge = params.config.judge;
9628
9730
  const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
9629
- const stateDir = params.stateDir ?? belayStateDir(params.config, path29.join(params.repoRoot, ".belay"));
9731
+ const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
9630
9732
  const transport = await evaluateWithJudgeTransport(
9631
9733
  {
9632
9734
  prompt: buildTier1Prompt(params.command),
@@ -9916,15 +10018,15 @@ async function loadClassifierAuthorization(params) {
9916
10018
  }
9917
10019
 
9918
10020
  // src/core/capability/allowlist.ts
9919
- import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
10021
+ import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
9920
10022
  init_config();
9921
10023
  init_path_utils();
9922
- import path30 from "node:path";
10024
+ import path31 from "node:path";
9923
10025
  function fsScopeAllowlistPath(config, repoLocalStateDir) {
9924
- return path30.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
10026
+ return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
9925
10027
  }
9926
10028
  function loadFsScopeAllowlistSync(filePath) {
9927
- if (!existsSync9(filePath)) {
10029
+ if (!existsSync10(filePath)) {
9928
10030
  return { version: 1, paths: [] };
9929
10031
  }
9930
10032
  const raw = JSON.parse(readFileSync3(filePath, "utf8"));
@@ -10016,7 +10118,7 @@ function checkGatedActionLimits(action) {
10016
10118
  // src/core/capability/paths.ts
10017
10119
  init_path_utils();
10018
10120
  init_shell_tokenizer();
10019
- import path31 from "node:path";
10121
+ import path32 from "node:path";
10020
10122
  function applyPatchTargets(patch) {
10021
10123
  const targets = [];
10022
10124
  for (const line of patch.split("\n")) {
@@ -10081,7 +10183,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10081
10183
  const paths = /* @__PURE__ */ new Set();
10082
10184
  const filePath = extractToolFilePath(payload);
10083
10185
  if (filePath) {
10084
- const resolved = path31.isAbsolute(filePath) ? filePath : path31.resolve(cwd, filePath);
10186
+ const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
10085
10187
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10086
10188
  paths.add(resolved);
10087
10189
  }
@@ -10091,7 +10193,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10091
10193
  const patch = extractToolPatch(payload);
10092
10194
  if (patch) {
10093
10195
  for (const target of applyPatchTargets(patch)) {
10094
- const resolved = path31.isAbsolute(target) ? target : path31.resolve(cwd, target);
10196
+ const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
10095
10197
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10096
10198
  paths.add(resolved);
10097
10199
  }
@@ -10145,7 +10247,7 @@ function policyReasonToLegacyReason(decision) {
10145
10247
  // src/core/capability/policy-engine.ts
10146
10248
  init_git_resource_identity();
10147
10249
  import { createHash as createHash10 } from "node:crypto";
10148
- import path32 from "node:path";
10250
+ import path33 from "node:path";
10149
10251
  init_path_utils();
10150
10252
  init_shell_tokenizer();
10151
10253
  init_grant();
@@ -10437,7 +10539,7 @@ function hashSession(value) {
10437
10539
  return createHash10("sha256").update(value).digest("hex").slice(0, 16);
10438
10540
  }
10439
10541
  function resolveCapabilityPath2(targetPath, cwd) {
10440
- const joined = path32.isAbsolute(targetPath) ? targetPath : path32.join(cwd, targetPath);
10542
+ const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
10441
10543
  return canonicalPath(joined);
10442
10544
  }
10443
10545
  function actionForFileMutation(analysis) {
@@ -10516,7 +10618,7 @@ function isRepoLocalPackageExec(request) {
10516
10618
  return false;
10517
10619
  }
10518
10620
  const commandPath = canonicalPath(request.resource.command);
10519
- return path32.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10621
+ return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10520
10622
  }
10521
10623
  function isRepoLocalRoutineWrite(request, sensitivePaths) {
10522
10624
  if (!isRepoLocalShellLocation(request)) {
@@ -10764,6 +10866,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
10764
10866
  );
10765
10867
  return { request, decision };
10766
10868
  }
10869
+ function buildFileReadCapabilityRequest(analysis) {
10870
+ return {
10871
+ version: CAPABILITY_REQUEST_VERSION,
10872
+ principal: {
10873
+ adapter: analysis.adapter,
10874
+ repoRoot: analysis.repoRoot,
10875
+ sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
10876
+ },
10877
+ action: "fs.read",
10878
+ resource: { kind: "path", path: analysis.resolvedPath },
10879
+ context: {
10880
+ cwd: analysis.cwd,
10881
+ inputFingerprint: analysis.inputFingerprint,
10882
+ hookKind: analysis.hookKind,
10883
+ analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
10884
+ },
10885
+ evidence: {
10886
+ level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
10887
+ signals: [...analysis.signals, "effect.fs_read"]
10888
+ }
10889
+ };
10890
+ }
10891
+ function evaluateFileReadPolicy(analysis, config, auth) {
10892
+ const request = buildFileReadCapabilityRequest(analysis);
10893
+ const enriched = enrichAuthWithMaterializedGrants(request, config, {
10894
+ ...auth,
10895
+ sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
10896
+ });
10897
+ const decision = getDefaultPolicyEngine().evaluate(
10898
+ request,
10899
+ buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
10900
+ );
10901
+ return { request, decision };
10902
+ }
10767
10903
 
10768
10904
  // src/core/capability/reasons.ts
10769
10905
  var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
@@ -10780,15 +10916,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
10780
10916
  }
10781
10917
 
10782
10918
  // src/core/capability/trusted-workspace-roots.ts
10783
- import { existsSync as existsSync10, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10919
+ import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10784
10920
  init_config();
10785
10921
  init_path_utils();
10786
- import path33 from "node:path";
10922
+ import path34 from "node:path";
10787
10923
  function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
10788
- return path33.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10924
+ return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10789
10925
  }
10790
10926
  function loadTrustedWorkspaceRootsSync(filePath) {
10791
- if (!existsSync10(filePath)) {
10927
+ if (!existsSync11(filePath)) {
10792
10928
  return { version: 1, roots: [] };
10793
10929
  }
10794
10930
  const raw = JSON.parse(readFileSync4(filePath, "utf8"));
@@ -10814,7 +10950,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
10814
10950
  const source = record.source === "approval" ? "approval" : void 0;
10815
10951
  return [
10816
10952
  {
10817
- path: path33.resolve(record.path),
10953
+ path: path34.resolve(record.path),
10818
10954
  approvedAt,
10819
10955
  approvalId,
10820
10956
  ...source ? { source } : {}
@@ -10843,7 +10979,7 @@ function isDirectoryPath(targetPath) {
10843
10979
  function isBroadTrustedWorkspaceRoot(targetPath) {
10844
10980
  const home = process.env.HOME ?? process.env.USERPROFILE;
10845
10981
  const root = normalizeTrustedWorkspaceRootPath(targetPath);
10846
- if (root === normalizeTrustedWorkspaceRootPath(path33.parse(root).root)) {
10982
+ if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
10847
10983
  return true;
10848
10984
  }
10849
10985
  if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
@@ -10867,7 +11003,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
10867
11003
  return false;
10868
11004
  }
10869
11005
  return HOME_HIGH_STAKES_SEGMENTS.some(
10870
- (segment) => pathWithinRoot(path33.join(homeRoot, segment), normalized)
11006
+ (segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
10871
11007
  );
10872
11008
  }
10873
11009
  function validateTrustedWorkspaceRootCandidate(params) {
@@ -10904,7 +11040,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
10904
11040
  init_config_layers();
10905
11041
 
10906
11042
  // src/core/contained-execution/eligibility.ts
10907
- import path34 from "node:path";
11043
+ import path35 from "node:path";
10908
11044
  init_path_utils();
10909
11045
  var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10910
11046
  "command_substitution",
@@ -10921,7 +11057,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10921
11057
  ]);
10922
11058
  function isContainedUnknownExecutionEligible(config, action, result) {
10923
11059
  const contained = config.sandbox.containedExecution;
10924
- if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path34.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
11060
+ if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path35.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
10925
11061
  return false;
10926
11062
  }
10927
11063
  const plan = result.effectPlan;
@@ -11152,7 +11288,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11152
11288
  }
11153
11289
 
11154
11290
  // src/core/classify-tool.ts
11155
- import path49 from "node:path";
11291
+ import path50 from "node:path";
11156
11292
  init_fingerprint2();
11157
11293
  init_path_utils();
11158
11294
  init_scrub();
@@ -11272,13 +11408,13 @@ function worstEffectDecision(decisions) {
11272
11408
 
11273
11409
  // src/core/effect-ir/shell-lower.ts
11274
11410
  init_shell_tokenizer();
11275
- import path48 from "node:path";
11411
+ import path49 from "node:path";
11276
11412
 
11277
11413
  // src/core/verdict/docker-compose-run.ts
11278
- import path36 from "node:path";
11414
+ import path37 from "node:path";
11279
11415
 
11280
11416
  // src/core/verdict/recursive-invocation.ts
11281
- import path35 from "node:path";
11417
+ import path36 from "node:path";
11282
11418
  var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
11283
11419
  var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
11284
11420
  var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
@@ -11340,7 +11476,7 @@ var OSASCRIPT_PROFILE = {
11340
11476
  attachedValuePrefixes: []
11341
11477
  };
11342
11478
  function normalizeInterpreter(value) {
11343
- return path35.basename(value);
11479
+ return path36.basename(value);
11344
11480
  }
11345
11481
  function scriptResult(interpreter, token) {
11346
11482
  if (!token) {
@@ -11570,7 +11706,7 @@ function parseOptions(words, start, options) {
11570
11706
  function decodeDockerComposeRun(tokens) {
11571
11707
  if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11572
11708
  const words = tokens.filter((token) => token.kind === "word");
11573
- const head = path36.basename(words[0]?.value ?? "");
11709
+ const head = path37.basename(words[0]?.value ?? "");
11574
11710
  let index;
11575
11711
  if (head === "docker-compose") {
11576
11712
  index = 1;
@@ -11611,7 +11747,7 @@ function decodeDockerComposeRun(tokens) {
11611
11747
  }
11612
11748
 
11613
11749
  // src/core/verdict/egress-classify.ts
11614
- import path37 from "node:path";
11750
+ import path38 from "node:path";
11615
11751
  var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11616
11752
  "aws",
11617
11753
  "curl",
@@ -11660,7 +11796,7 @@ function isEgressToolHead(head) {
11660
11796
  return EGRESS_TOOL_HEADS.has(head);
11661
11797
  }
11662
11798
  function decodeEgressEffects(params) {
11663
- const head = path37.basename(params.tokens[0] ?? "");
11799
+ const head = path38.basename(params.tokens[0] ?? "");
11664
11800
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11665
11801
  return null;
11666
11802
  }
@@ -11668,7 +11804,7 @@ function decodeEgressEffects(params) {
11668
11804
  const provenance = { segment: params.segment };
11669
11805
  const requirements = [];
11670
11806
  for (const file of decoded.files) {
11671
- const resolved = path37.resolve(params.cwd, expandHome2(file));
11807
+ const resolved = path38.resolve(params.cwd, expandHome2(file));
11672
11808
  requirements.push(
11673
11809
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11674
11810
  "egress.explicit_file_read"
@@ -11690,7 +11826,7 @@ function decodeEgressEffects(params) {
11690
11826
  if (file === "-") {
11691
11827
  continue;
11692
11828
  }
11693
- const resolved = path37.resolve(params.cwd, expandHome2(file));
11829
+ const resolved = path38.resolve(params.cwd, expandHome2(file));
11694
11830
  if (resolved === "/dev/null") {
11695
11831
  continue;
11696
11832
  }
@@ -12168,13 +12304,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12168
12304
  if (head === "wget" && !explicitOutput) {
12169
12305
  outputFiles.push(
12170
12306
  ...endpointOutputNames.map(
12171
- (name) => outputDirectory ? path37.join(outputDirectory, name) : name
12307
+ (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12172
12308
  )
12173
12309
  );
12174
12310
  } else if (head === "curl" && remoteNameOutput) {
12175
12311
  outputFiles.push(
12176
12312
  ...endpointOutputNames.map(
12177
- (name) => outputDirectory ? path37.join(outputDirectory, name) : name
12313
+ (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12178
12314
  )
12179
12315
  );
12180
12316
  }
@@ -12188,7 +12324,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12188
12324
  outputFiles: [
12189
12325
  ...new Set(
12190
12326
  outputFiles.map(
12191
- (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path37.isAbsolute(file) ? path37.join(outputDirectory, file) : file
12327
+ (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
12192
12328
  )
12193
12329
  )
12194
12330
  ],
@@ -12203,7 +12339,7 @@ function remoteOutputName(spec) {
12203
12339
  } catch {
12204
12340
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
12205
12341
  }
12206
- const name = path37.posix.basename(pathname);
12342
+ const name = path38.posix.basename(pathname);
12207
12343
  return name && name !== "/" ? name : "index.html";
12208
12344
  }
12209
12345
  function decodeGhGrammar(tokens) {
@@ -12368,7 +12504,7 @@ function expandHome2(value) {
12368
12504
  return process.env.HOME ?? value;
12369
12505
  }
12370
12506
  if (value.startsWith("~/")) {
12371
- return path37.join(process.env.HOME ?? "~", value.slice(2));
12507
+ return path38.join(process.env.HOME ?? "~", value.slice(2));
12372
12508
  }
12373
12509
  return value;
12374
12510
  }
@@ -12387,7 +12523,7 @@ function requirement(tag, action, resource, segment, signals) {
12387
12523
  }
12388
12524
 
12389
12525
  // src/core/verdict/git-classifier.ts
12390
- import path38 from "node:path";
12526
+ import path39 from "node:path";
12391
12527
  init_shell_tokenizer();
12392
12528
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
12393
12529
  "--copy",
@@ -12483,7 +12619,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
12483
12619
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
12484
12620
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
12485
12621
  function isGitExecutable(token) {
12486
- return path38.basename(token) === "git";
12622
+ return path39.basename(token) === "git";
12487
12623
  }
12488
12624
  function takesValue(flag) {
12489
12625
  return flag === "-C" || flag === "-c" || flag === "--git-dir" || flag === "--work-tree" || flag === "--exec-path" || flag === "--paginate" || flag === "--config-env" || flag.startsWith("-C") || flag.startsWith("-c") || flag.startsWith("--git-dir=") || flag.startsWith("--work-tree=");
@@ -12511,7 +12647,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12511
12647
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12512
12648
  const value = tokens[index + 1];
12513
12649
  if (token === "-C" && value) {
12514
- effectiveCwd = path38.resolve(baseCwd, value);
12650
+ effectiveCwd = path39.resolve(baseCwd, value);
12515
12651
  } else if (token === "--work-tree" && value) {
12516
12652
  workTree = value;
12517
12653
  } else if (token === "--git-dir" && value) {
@@ -12521,7 +12657,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12521
12657
  continue;
12522
12658
  }
12523
12659
  if (token.startsWith("-C") && token.length > 2) {
12524
- effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12660
+ effectiveCwd = path39.resolve(baseCwd, token.slice(2));
12525
12661
  index += 1;
12526
12662
  continue;
12527
12663
  }
@@ -12664,7 +12800,7 @@ function looksLikeDiffPathOperand(token) {
12664
12800
  if (!looksLikeFileOperand(token)) {
12665
12801
  return false;
12666
12802
  }
12667
- if (token.startsWith(".") || path38.isAbsolute(token)) {
12803
+ if (token.startsWith(".") || path39.isAbsolute(token)) {
12668
12804
  return true;
12669
12805
  }
12670
12806
  return token.includes(".");
@@ -12672,12 +12808,12 @@ function looksLikeDiffPathOperand(token) {
12672
12808
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12673
12809
  const resolveBase = effectiveCwd ?? baseCwd;
12674
12810
  if (workTree) {
12675
- return path38.resolve(resolveBase, workTree);
12811
+ return path39.resolve(resolveBase, workTree);
12676
12812
  }
12677
12813
  if (gitDir) {
12678
- const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12679
- if (path38.basename(resolvedGitDir) === ".git") {
12680
- return path38.dirname(resolvedGitDir);
12814
+ const resolvedGitDir = path39.resolve(resolveBase, gitDir);
12815
+ if (path39.basename(resolvedGitDir) === ".git") {
12816
+ return path39.dirname(resolvedGitDir);
12681
12817
  }
12682
12818
  }
12683
12819
  return void 0;
@@ -12758,7 +12894,7 @@ function classifyGitCommand(tokens, baseCwd) {
12758
12894
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12759
12895
  const normalizedKey = `git ${subcommand}`;
12760
12896
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12761
- const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12897
+ const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12762
12898
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12763
12899
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12764
12900
  );
@@ -12910,9 +13046,9 @@ function decodeGitEffects(params) {
12910
13046
  ...subcommand === "push" ? ["tier0_external"] : []
12911
13047
  ];
12912
13048
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12913
- const workTreeRoot = normalized.workTree ? path38.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12914
- const gitRefRoot = normalized.gitDir ? path38.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12915
- const gitControlRoot = normalized.gitDir ? gitRefRoot : path38.join(gitRefRoot, ".git");
13049
+ const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
13050
+ const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
13051
+ const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
12916
13052
  const requirements = [];
12917
13053
  if (subcommand === "fetch" || subcommand === "pull") {
12918
13054
  const positionals = gitRemotePositionals(args);
@@ -13071,7 +13207,7 @@ function decodeGitEffects(params) {
13071
13207
  gitRequirement(
13072
13208
  "control_plane.write",
13073
13209
  "control_plane.write",
13074
- { kind: "path", path: path38.join(gitControlRoot, "logs") },
13210
+ { kind: "path", path: path39.join(gitControlRoot, "logs") },
13075
13211
  params.segment,
13076
13212
  [...signals, "git_history_destructive", "git.reflog.mutate"]
13077
13213
  )
@@ -13129,7 +13265,7 @@ function decodeGitEffects(params) {
13129
13265
  gitRequirement(
13130
13266
  "fs.read",
13131
13267
  "fs.read",
13132
- { kind: "path", path: path38.resolve(workTreeRoot, operand) },
13268
+ { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13133
13269
  params.segment,
13134
13270
  [...signals, "git.path.read"]
13135
13271
  )
@@ -13164,7 +13300,7 @@ function decodeGitEffects(params) {
13164
13300
  gitRequirement(
13165
13301
  "fs.write",
13166
13302
  "fs.write",
13167
- { kind: "path", path: path38.resolve(workTreeRoot, operand) },
13303
+ { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13168
13304
  params.segment,
13169
13305
  [...signals, "git.path.write"]
13170
13306
  )
@@ -13348,8 +13484,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
13348
13484
  }
13349
13485
 
13350
13486
  // src/core/verdict/launcher-resolve.ts
13351
- import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
13352
- import path39 from "node:path";
13487
+ import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
13488
+ import path40 from "node:path";
13353
13489
 
13354
13490
  // src/core/verdict/makefile-expand.ts
13355
13491
  var MAX_EXPAND_DEPTH = 16;
@@ -13522,8 +13658,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
13522
13658
  "why"
13523
13659
  ]);
13524
13660
  function readPackageJson(dir) {
13525
- const packagePath = path39.join(dir, "package.json");
13526
- if (!existsSync11(packagePath)) {
13661
+ const packagePath = path40.join(dir, "package.json");
13662
+ if (!existsSync12(packagePath)) {
13527
13663
  return null;
13528
13664
  }
13529
13665
  try {
@@ -13533,17 +13669,17 @@ function readPackageJson(dir) {
13533
13669
  }
13534
13670
  }
13535
13671
  function findPackageJson(startDir, stopDir) {
13536
- let current = path39.resolve(startDir);
13537
- const stop = path39.resolve(stopDir);
13672
+ let current = path40.resolve(startDir);
13673
+ const stop = path40.resolve(stopDir);
13538
13674
  while (true) {
13539
- const packagePath = path39.join(current, "package.json");
13540
- if (existsSync11(packagePath)) {
13675
+ const packagePath = path40.join(current, "package.json");
13676
+ if (existsSync12(packagePath)) {
13541
13677
  return packagePath;
13542
13678
  }
13543
- if (current === stop || current === path39.dirname(current)) {
13544
- return existsSync11(packagePath) ? packagePath : null;
13679
+ if (current === stop || current === path40.dirname(current)) {
13680
+ return existsSync12(packagePath) ? packagePath : null;
13545
13681
  }
13546
- const parent = path39.dirname(current);
13682
+ const parent = path40.dirname(current);
13547
13683
  if (!parent.startsWith(stop) && parent !== current) {
13548
13684
  }
13549
13685
  if (parent === current) {
@@ -13600,7 +13736,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
13600
13736
  }
13601
13737
  return { recipes: [], opaque: true, reason: "package_json_missing" };
13602
13738
  }
13603
- const pkg = readPackageJson(path39.dirname(packagePath));
13739
+ const pkg = readPackageJson(path40.dirname(packagePath));
13604
13740
  const scripts = pkg?.scripts;
13605
13741
  if (!scripts || typeof scripts !== "object") {
13606
13742
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -13691,20 +13827,20 @@ function parseMakefileRecipeContent(content) {
13691
13827
  function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13692
13828
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13693
13829
  let makefilePath = null;
13694
- let searchDir = path39.resolve(cwd);
13695
- const stop = path39.resolve(repoRoot);
13830
+ let searchDir = path40.resolve(cwd);
13831
+ const stop = path40.resolve(repoRoot);
13696
13832
  while (true) {
13697
13833
  for (const name of candidates) {
13698
- const candidate = path39.join(searchDir, name);
13699
- if (existsSync11(candidate)) {
13834
+ const candidate = path40.join(searchDir, name);
13835
+ if (existsSync12(candidate)) {
13700
13836
  makefilePath = candidate;
13701
13837
  break;
13702
13838
  }
13703
13839
  }
13704
- if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13840
+ if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
13705
13841
  break;
13706
13842
  }
13707
- searchDir = path39.dirname(searchDir);
13843
+ searchDir = path40.dirname(searchDir);
13708
13844
  }
13709
13845
  if (!makefilePath) {
13710
13846
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
@@ -13731,7 +13867,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13731
13867
  }
13732
13868
  const entry = targets.get(name);
13733
13869
  if (!entry) {
13734
- if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13870
+ if (!existsSync12(path40.resolve(path40.dirname(makefilePath), name))) {
13735
13871
  hasUndefinedPrerequisite = true;
13736
13872
  }
13737
13873
  return;
@@ -13830,7 +13966,7 @@ function resolveLauncherRecipe(params) {
13830
13966
  }
13831
13967
 
13832
13968
  // src/core/verdict/parser.ts
13833
- import path40 from "node:path";
13969
+ import path41 from "node:path";
13834
13970
 
13835
13971
  // src/core/shell-substitution.ts
13836
13972
  function findStructuralCommandSubstitutions(command) {
@@ -14070,7 +14206,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
14070
14206
  ".sh"
14071
14207
  ]);
14072
14208
  function normalizeHead(token) {
14073
- const base = path40.basename(token);
14209
+ const base = path41.basename(token);
14074
14210
  if (base && base !== "." && base !== "..") {
14075
14211
  return base;
14076
14212
  }
@@ -14380,7 +14516,7 @@ function isBareInterpreter(tokens) {
14380
14516
  return false;
14381
14517
  }
14382
14518
  const scriptArg = args.find((token) => !token.startsWith("-"));
14383
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
14519
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
14384
14520
  return false;
14385
14521
  }
14386
14522
  if (scriptArg) {
@@ -14620,7 +14756,7 @@ function mergeNodes(nodes) {
14620
14756
  }
14621
14757
 
14622
14758
  // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14623
- import path41 from "node:path";
14759
+ import path42 from "node:path";
14624
14760
  var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14625
14761
  "sudo",
14626
14762
  "env",
@@ -14641,7 +14777,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14641
14777
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14642
14778
  return false;
14643
14779
  }
14644
- const innerHead = path41.basename(innerTokens[0] ?? "");
14780
+ const innerHead = path42.basename(innerTokens[0] ?? "");
14645
14781
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14646
14782
  return false;
14647
14783
  }
@@ -14748,7 +14884,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
14748
14884
 
14749
14885
  // src/core/effect-ir/shell-lower/tokens.ts
14750
14886
  init_shell_tokenizer();
14751
- import path42 from "node:path";
14887
+ import path43 from "node:path";
14752
14888
  var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14753
14889
  var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14754
14890
  var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
@@ -14892,9 +15028,9 @@ function resolvePathOperand(operand, cwd) {
14892
15028
  return process.env.HOME ?? operand;
14893
15029
  }
14894
15030
  if (operand.startsWith("~/")) {
14895
- return path42.join(process.env.HOME ?? "~", operand.slice(2));
15031
+ return path43.join(process.env.HOME ?? "~", operand.slice(2));
14896
15032
  }
14897
- return path42.resolve(cwd, operand);
15033
+ return path43.resolve(cwd, operand);
14898
15034
  }
14899
15035
  function isOptionToken(value) {
14900
15036
  return value.startsWith("-") || value.startsWith("+");
@@ -14906,7 +15042,7 @@ function pipeToShell(command) {
14906
15042
  return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
14907
15043
  }
14908
15044
  function executableBaseName(head) {
14909
- return path42.basename(head);
15045
+ return path43.basename(head);
14910
15046
  }
14911
15047
  function isMetadataOnlyArgv(argv) {
14912
15048
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
@@ -15025,15 +15161,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
15025
15161
  }
15026
15162
 
15027
15163
  // src/core/effect-ir/shell-lower/decode-process.ts
15028
- import path46 from "node:path";
15164
+ import path47 from "node:path";
15029
15165
 
15030
15166
  // src/core/effect-ir/shell-lower/decoders/belay.ts
15031
- import path43 from "node:path";
15167
+ import path44 from "node:path";
15032
15168
  function decodeBelay(args, repoRoot, segment) {
15033
15169
  const [section, operation, key] = args;
15034
15170
  const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
15035
15171
  const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
15036
15172
  const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
15173
+ const approvalAuthorityCommand = [
15174
+ "approval-token",
15175
+ "approve",
15176
+ "revoke",
15177
+ "standing-allow"
15178
+ ].includes(section ?? "");
15179
+ const configTrustMutation = section === "config" && operation === "trust";
15037
15180
  if (judgeCommand || configRead || configJudgeMutation) {
15038
15181
  return [
15039
15182
  processRequirement("belay", "inspect", segment, [
@@ -15042,12 +15185,23 @@ function decodeBelay(args, repoRoot, segment) {
15042
15185
  ])
15043
15186
  ];
15044
15187
  }
15188
+ if (approvalAuthorityCommand || configTrustMutation) {
15189
+ return [
15190
+ requirement2(
15191
+ "control_plane.write",
15192
+ "control_plane.write",
15193
+ { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15194
+ segment,
15195
+ [approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
15196
+ )
15197
+ ];
15198
+ }
15045
15199
  if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
15046
15200
  return [
15047
15201
  requirement2(
15048
15202
  "control_plane.write",
15049
15203
  "control_plane.write",
15050
- { kind: "path", path: path43.join(repoRoot, ".belay-control-plane") },
15204
+ { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15051
15205
  segment,
15052
15206
  ["belay.config_non_judge_mutation"]
15053
15207
  )
@@ -15288,7 +15442,7 @@ function validDockerInfo(args) {
15288
15442
  // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15289
15443
  init_git_resource_identity();
15290
15444
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15291
- import path44 from "node:path";
15445
+ import path45 from "node:path";
15292
15446
  function decodeCopyMove(head, args, cwd, segment) {
15293
15447
  const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15294
15448
  const operands = [];
@@ -15416,11 +15570,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
15416
15570
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15417
15571
  try {
15418
15572
  if (finalOperandIsSymlink) {
15419
- return path44.join(realpathSync4.native(path44.dirname(targetPath)), path44.basename(targetPath));
15573
+ return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
15420
15574
  }
15421
15575
  return realpathSync4.native(targetPath);
15422
15576
  } catch {
15423
- return path44.resolve(targetPath);
15577
+ return path45.resolve(targetPath);
15424
15578
  }
15425
15579
  }
15426
15580
  function isSymbolicLink(targetPath) {
@@ -15431,8 +15585,8 @@ function isSymbolicLink(targetPath) {
15431
15585
  }
15432
15586
  }
15433
15587
  function pathContains(ancestor, candidate) {
15434
- const relative = path44.relative(path44.resolve(ancestor), path44.resolve(candidate));
15435
- return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
15588
+ const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
15589
+ return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
15436
15590
  }
15437
15591
  function filesystemReadOperands(head, args) {
15438
15592
  switch (head) {
@@ -15572,7 +15726,7 @@ function decodePrisma(args, env, repoRoot, segment) {
15572
15726
 
15573
15727
  // src/core/effect-ir/shell-lower/decoders/ruby.ts
15574
15728
  init_path_utils();
15575
- import path45 from "node:path";
15729
+ import path46 from "node:path";
15576
15730
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15577
15731
  function railsReadOnlySubcommand(args) {
15578
15732
  const subcommand = args[0];
@@ -15582,7 +15736,7 @@ function railsReadOnlySubcommand(args) {
15582
15736
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15583
15737
  }
15584
15738
  function isRubyTestScript(scriptPath) {
15585
- const base = path45.basename(scriptPath);
15739
+ const base = path46.basename(scriptPath);
15586
15740
  return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15587
15741
  }
15588
15742
  function parseRubyTestInvocation(args) {
@@ -15996,7 +16150,7 @@ function decodeProcessOrFilesystem(params) {
15996
16150
  requirement2(
15997
16151
  "fs.read",
15998
16152
  "fs.read",
15999
- { kind: "path", path: path46.resolve(cwd, syntax) },
16153
+ { kind: "path", path: path47.resolve(cwd, syntax) },
16000
16154
  segment,
16001
16155
  ["shell.syntax_source_read"]
16002
16156
  )
@@ -16202,7 +16356,7 @@ function packageExecInnerIsMetadata(peel) {
16202
16356
 
16203
16357
  // src/core/effect-ir/shell-lower/segment.ts
16204
16358
  init_shell_tokenizer();
16205
- import path47 from "node:path";
16359
+ import path48 from "node:path";
16206
16360
  var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16207
16361
  var SHELL_GLOB_PATTERN = /[*?[]/;
16208
16362
  function requiresKnownCwd(requirementValue) {
@@ -16217,11 +16371,11 @@ function joinNestedOpacity(outer, nested) {
16217
16371
  }
16218
16372
  function startsLocalPostgresService(command) {
16219
16373
  const tokens = tokenizeShell(command);
16220
- return path47.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16374
+ return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16221
16375
  }
16222
16376
  function resolveCdTransition(command, currentCwd) {
16223
16377
  const tokens = tokenizeShell(command);
16224
- if (path47.basename(tokens[0] ?? "") !== "cd") {
16378
+ if (path48.basename(tokens[0] ?? "") !== "cd") {
16225
16379
  return null;
16226
16380
  }
16227
16381
  const target = tokens[1] ?? "~";
@@ -16384,7 +16538,7 @@ function lowerSegment(command, context) {
16384
16538
  stripStructuredRedirects(lexed.tokens),
16385
16539
  stripRedirects(parsedTokens)
16386
16540
  );
16387
- const head = path48.basename(tokens[0] ?? parsed.head);
16541
+ const head = path49.basename(tokens[0] ?? parsed.head);
16388
16542
  let opacity = segmentOpacity(command);
16389
16543
  const signals = /* @__PURE__ */ new Set();
16390
16544
  const requirements = [];
@@ -16967,17 +17121,41 @@ function verdictToClassifyResult(result, config, options = {}) {
16967
17121
 
16968
17122
  // src/core/classify-tool.ts
16969
17123
  var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
16970
- var FILE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write"]);
16971
- var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
16972
- "edit",
16973
- "multiedit",
16974
- "multi_edit",
16975
- "patch",
16976
- "strreplace",
16977
- "str_replace"
16978
- ]);
16979
- var FILE_DELETE_TOOL_NAMES = /* @__PURE__ */ new Set(["delete"]);
16980
- var APPLY_PATCH_TOOL_NAMES = /* @__PURE__ */ new Set(["apply_patch", "applypatch"]);
17124
+ function toolInputRecord(payload) {
17125
+ const toolInput = payload.tool_input;
17126
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
17127
+ return null;
17128
+ }
17129
+ return toolInput;
17130
+ }
17131
+ function hasMutationFields(input) {
17132
+ return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
17133
+ }
17134
+ function inferToolPayloadEffect(payload, toolName) {
17135
+ if (extractShellCommand(payload)) {
17136
+ return "shell";
17137
+ }
17138
+ const input = toolInputRecord(payload);
17139
+ if (!input) {
17140
+ return "indeterminate";
17141
+ }
17142
+ if (hasMutationFields(input)) {
17143
+ return "file_mutation";
17144
+ }
17145
+ if (extractPatch(payload)) {
17146
+ return "apply_patch";
17147
+ }
17148
+ if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
17149
+ return "search_read";
17150
+ }
17151
+ if (typeof input.file_path === "string") {
17152
+ return "file_read";
17153
+ }
17154
+ if (typeof input.path === "string" || typeof input.target_file === "string") {
17155
+ return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
17156
+ }
17157
+ return "indeterminate";
17158
+ }
16981
17159
  function policyAuth(options) {
16982
17160
  if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
16983
17161
  return void 0;
@@ -17157,186 +17335,160 @@ function classifyFileMutationWithPolicy(params) {
17157
17335
  boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17158
17336
  };
17159
17337
  }
17160
- async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17161
- const toolName = String(payload.tool_name ?? "");
17162
- const toolKind = normalizedToolName(toolName);
17163
- const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17164
- const protectedRoots = [
17165
- ...options.protectedArtifactRoots ?? [],
17166
- ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17167
- ];
17168
- if (toolKind === "shell") {
17169
- const command = extractShellCommand(payload);
17170
- if (!command) {
17171
- if (options.unknownLocalEffect === "deny") {
17172
- return {
17173
- verdict: "deny_pending_approval",
17174
- reason: "tool_shell_missing_command",
17175
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17176
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17177
- assessment: {
17178
- reversibility: "irreversible",
17179
- external: false,
17180
- blastRadius: "tool shell",
17181
- confidence: 0.85,
17182
- signals: ["missing_command"]
17183
- }
17184
- };
17185
- }
17186
- return {
17187
- verdict: "allow_flagged",
17188
- reason: "tool_shell_missing_command",
17189
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17190
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17191
- assessment: {
17192
- reversibility: "recoverable_with_cost",
17193
- external: false,
17194
- blastRadius: "tool shell",
17195
- confidence: 0.5,
17196
- signals: ["missing_command"]
17197
- }
17198
- };
17199
- }
17200
- const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17201
- return {
17202
- ...shellResult,
17203
- summary: command
17204
- };
17205
- }
17206
- if (FILE_WRITE_TOOL_NAMES.has(toolKind) || FILE_EDIT_TOOL_NAMES.has(toolKind) || FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17207
- const filePath = extractFilePath(payload);
17208
- if (!filePath) {
17209
- if (options.unknownLocalEffect === "deny") {
17210
- return {
17211
- verdict: "deny_pending_approval",
17212
- reason: "file_mutation_missing_path",
17213
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17214
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17215
- assessment: {
17216
- reversibility: "irreversible",
17217
- external: false,
17218
- blastRadius: "file mutation",
17219
- confidence: 0.85,
17220
- signals: ["missing_path"]
17221
- }
17222
- };
17223
- }
17224
- return {
17225
- verdict: "allow_flagged",
17226
- reason: "file_mutation_missing_path",
17227
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17228
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17229
- assessment: {
17230
- reversibility: "recoverable_with_cost",
17231
- external: false,
17232
- blastRadius: "file mutation",
17233
- confidence: 0.55,
17234
- signals: ["missing_path"]
17235
- }
17236
- };
17237
- }
17238
- const signals = [];
17239
- const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(cwd, filePath);
17240
- const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17241
- if (hitsProtectedRoot) {
17242
- signals.push("control_plane_path");
17243
- return classifyFileMutationWithPolicy({
17244
- toolName,
17245
- toolKind,
17246
- filePath,
17247
- resolvedPath,
17248
- repoRoot,
17249
- cwd,
17250
- config,
17251
- options,
17252
- signals,
17253
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17254
- locationLabel: "control_plane"
17255
- });
17256
- }
17257
- const workspaceMatch = resolveWorkspaceRootMatch(
17338
+ function classifyFileReadWithPolicy(params) {
17339
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
17340
+ const { request, decision } = evaluateFileReadPolicy(
17341
+ {
17342
+ hookKind: "tool",
17343
+ toolKind: params.toolKind,
17344
+ filePath: params.filePath,
17345
+ resolvedPath: params.resolvedPath,
17346
+ repoRoot: params.repoRoot,
17347
+ cwd: params.cwd,
17348
+ inputFingerprint: fingerprint,
17349
+ signals: params.signals,
17350
+ locationLabel: params.locationLabel,
17351
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
17352
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
17353
+ },
17354
+ params.config,
17355
+ policyAuth(params.options)
17356
+ );
17357
+ if (policyDecisionRequiresAsk(decision)) {
17358
+ return {
17359
+ verdict: "deny_pending_approval",
17360
+ reason: policyReasonToLegacyReason(decision),
17361
+ summary: params.filePath,
17362
+ fingerprint,
17363
+ assessment: {
17364
+ reversibility: "reversible",
17365
+ external: params.locationLabel === "outside_repo",
17366
+ blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
17367
+ confidence: 0.85,
17368
+ signals: [...params.signals, ...decision.signals]
17369
+ },
17370
+ capabilityRequests: [request],
17371
+ authorizationDecision: decision,
17372
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17373
+ };
17374
+ }
17375
+ return {
17376
+ verdict: "allow",
17377
+ reason: "effect.fs_read",
17378
+ summary: params.filePath,
17379
+ fingerprint,
17380
+ assessment: {
17381
+ reversibility: "reversible",
17382
+ external: false,
17383
+ blastRadius: "tool scope",
17384
+ confidence: 0.88,
17385
+ signals: [...params.signals, ...decision.signals]
17386
+ },
17387
+ capabilityRequests: [request],
17388
+ authorizationDecision: decision,
17389
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17390
+ };
17391
+ }
17392
+ function classifyFilePathMutation(params) {
17393
+ const {
17394
+ toolName,
17395
+ toolKind,
17396
+ filePath,
17397
+ repoRoot,
17398
+ cwd,
17399
+ config,
17400
+ options,
17401
+ sensitivePaths,
17402
+ protectedRoots,
17403
+ isDelete
17404
+ } = params;
17405
+ const signals = [];
17406
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17407
+ const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17408
+ if (hitsProtectedRoot) {
17409
+ signals.push("control_plane_path");
17410
+ return classifyFileMutationWithPolicy({
17411
+ toolName,
17412
+ toolKind,
17413
+ filePath,
17414
+ resolvedPath,
17415
+ repoRoot,
17416
+ cwd,
17417
+ config,
17418
+ options,
17419
+ signals,
17420
+ isDelete,
17421
+ locationLabel: "control_plane"
17422
+ });
17423
+ }
17424
+ const workspaceMatch = resolveWorkspaceRootMatch(
17425
+ repoRoot,
17426
+ options.trustedWorkspaceRoots,
17427
+ resolvedPath
17428
+ );
17429
+ if (workspaceMatch === null) {
17430
+ signals.push("outside_repo_path");
17431
+ return classifyFileMutationWithPolicy({
17432
+ toolName,
17433
+ toolKind,
17434
+ filePath,
17435
+ resolvedPath,
17258
17436
  repoRoot,
17259
- options.trustedWorkspaceRoots,
17260
- resolvedPath
17261
- );
17262
- if (workspaceMatch === null) {
17263
- signals.push("outside_repo_path");
17264
- return classifyFileMutationWithPolicy({
17265
- toolName,
17266
- toolKind,
17267
- filePath,
17268
- resolvedPath,
17269
- repoRoot,
17270
- cwd,
17271
- config,
17272
- options,
17273
- signals,
17274
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17275
- locationLabel: "outside_repo"
17276
- });
17277
- }
17278
- if (workspaceMatch.kind === "trusted") {
17279
- signals.push("trusted_workspace_root");
17280
- }
17281
- const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17282
- const workspacePrescan = mutationPrescanRequiresAsk({
17283
- targets: [filePath],
17284
17437
  cwd,
17438
+ config,
17439
+ options,
17440
+ signals,
17441
+ isDelete,
17442
+ locationLabel: "outside_repo"
17443
+ });
17444
+ }
17445
+ if (workspaceMatch.kind === "trusted") {
17446
+ signals.push("trusted_workspace_root");
17447
+ }
17448
+ const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17449
+ const workspacePrescan = mutationPrescanRequiresAsk({
17450
+ targets: [filePath],
17451
+ cwd,
17452
+ repoRoot,
17453
+ trustedCwd,
17454
+ trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17455
+ sensitivePaths
17456
+ });
17457
+ if (workspacePrescan) {
17458
+ return classifyFileMutationWithPolicy({
17459
+ toolName,
17460
+ toolKind,
17461
+ filePath,
17462
+ resolvedPath,
17285
17463
  repoRoot,
17286
- trustedCwd,
17287
- trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17288
- sensitivePaths
17464
+ cwd,
17465
+ config,
17466
+ options,
17467
+ signals,
17468
+ isDelete,
17469
+ locationLabel: "sensitive_path"
17289
17470
  });
17290
- if (workspacePrescan) {
17291
- return classifyFileMutationWithPolicy({
17292
- toolName,
17293
- toolKind,
17294
- filePath,
17295
- resolvedPath,
17296
- repoRoot,
17297
- cwd,
17298
- config,
17299
- options,
17300
- signals,
17301
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17302
- locationLabel: "sensitive_path"
17303
- });
17304
- }
17305
- const relativePath = workspaceMatch.relativePath;
17306
- if (matchesSensitivePath(relativePath, sensitivePaths)) {
17307
- signals.push("sensitive_path");
17308
- return classifyFileMutationWithPolicy({
17309
- toolName,
17310
- toolKind,
17311
- filePath,
17312
- resolvedPath,
17313
- repoRoot,
17314
- cwd,
17315
- config,
17316
- options,
17317
- signals,
17318
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17319
- locationLabel: "sensitive_path"
17320
- });
17321
- }
17322
- if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17323
- if (isGitPath(resolvedPath, repoRoot)) {
17324
- signals.push("protected_artifact");
17325
- return classifyFileMutationWithPolicy({
17326
- toolName,
17327
- toolKind,
17328
- filePath,
17329
- resolvedPath,
17330
- repoRoot,
17331
- cwd,
17332
- config,
17333
- options,
17334
- signals,
17335
- isDelete: true,
17336
- locationLabel: "sensitive_path"
17337
- });
17338
- }
17339
- signals.push("file_delete");
17471
+ }
17472
+ const relativePath = workspaceMatch.relativePath;
17473
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17474
+ signals.push("sensitive_path");
17475
+ return classifyFileMutationWithPolicy({
17476
+ toolName,
17477
+ toolKind,
17478
+ filePath,
17479
+ resolvedPath,
17480
+ repoRoot,
17481
+ cwd,
17482
+ config,
17483
+ options,
17484
+ signals,
17485
+ isDelete,
17486
+ locationLabel: "sensitive_path"
17487
+ });
17488
+ }
17489
+ if (isDelete) {
17490
+ if (isGitPath(resolvedPath, repoRoot)) {
17491
+ signals.push("protected_artifact");
17340
17492
  return classifyFileMutationWithPolicy({
17341
17493
  toolName,
17342
17494
  toolKind,
@@ -17348,10 +17500,10 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17348
17500
  options,
17349
17501
  signals,
17350
17502
  isDelete: true,
17351
- locationLabel: "repo_local"
17503
+ locationLabel: "sensitive_path"
17352
17504
  });
17353
17505
  }
17354
- signals.push("file_mutation");
17506
+ signals.push("file_delete");
17355
17507
  return classifyFileMutationWithPolicy({
17356
17508
  toolName,
17357
17509
  toolKind,
@@ -17362,11 +17514,228 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17362
17514
  config,
17363
17515
  options,
17364
17516
  signals,
17365
- isDelete: false,
17517
+ isDelete: true,
17366
17518
  locationLabel: "repo_local"
17367
17519
  });
17368
17520
  }
17369
- if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
17521
+ signals.push("file_mutation");
17522
+ return classifyFileMutationWithPolicy({
17523
+ toolName,
17524
+ toolKind,
17525
+ filePath,
17526
+ resolvedPath,
17527
+ repoRoot,
17528
+ cwd,
17529
+ config,
17530
+ options,
17531
+ signals,
17532
+ isDelete: false,
17533
+ locationLabel: "repo_local"
17534
+ });
17535
+ }
17536
+ function classifyFilePathRead(params) {
17537
+ const {
17538
+ toolName,
17539
+ toolKind,
17540
+ filePath,
17541
+ repoRoot,
17542
+ cwd,
17543
+ config,
17544
+ options,
17545
+ sensitivePaths,
17546
+ protectedRoots
17547
+ } = params;
17548
+ const signals = ["effect.fs_read"];
17549
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17550
+ if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
17551
+ signals.push("control_plane_path");
17552
+ return classifyFileReadWithPolicy({
17553
+ toolName,
17554
+ toolKind,
17555
+ filePath,
17556
+ resolvedPath,
17557
+ repoRoot,
17558
+ cwd,
17559
+ config,
17560
+ options,
17561
+ signals,
17562
+ locationLabel: "control_plane"
17563
+ });
17564
+ }
17565
+ const workspaceMatch = resolveWorkspaceRootMatch(
17566
+ repoRoot,
17567
+ options.trustedWorkspaceRoots,
17568
+ resolvedPath
17569
+ );
17570
+ if (workspaceMatch === null) {
17571
+ signals.push("outside_repo_path");
17572
+ return classifyFileReadWithPolicy({
17573
+ toolName,
17574
+ toolKind,
17575
+ filePath,
17576
+ resolvedPath,
17577
+ repoRoot,
17578
+ cwd,
17579
+ config,
17580
+ options,
17581
+ signals,
17582
+ locationLabel: "outside_repo"
17583
+ });
17584
+ }
17585
+ const relativePath = workspaceMatch.relativePath;
17586
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17587
+ signals.push("sensitive_path");
17588
+ return classifyFileReadWithPolicy({
17589
+ toolName,
17590
+ toolKind,
17591
+ filePath,
17592
+ resolvedPath,
17593
+ repoRoot,
17594
+ cwd,
17595
+ config,
17596
+ options,
17597
+ signals,
17598
+ locationLabel: "sensitive_path"
17599
+ });
17600
+ }
17601
+ return classifyFileReadWithPolicy({
17602
+ toolName,
17603
+ toolKind,
17604
+ filePath,
17605
+ resolvedPath,
17606
+ repoRoot,
17607
+ cwd,
17608
+ config,
17609
+ options,
17610
+ signals,
17611
+ locationLabel: "repo_local"
17612
+ });
17613
+ }
17614
+ function indeterminateToolResult(toolName, payload, repoRoot, options) {
17615
+ const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
17616
+ const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
17617
+ if (options.unknownLocalEffect === "deny") {
17618
+ return {
17619
+ verdict: "deny_pending_approval",
17620
+ reason: "indeterminate_tool_effect",
17621
+ summary,
17622
+ fingerprint,
17623
+ assessment: {
17624
+ reversibility: "irreversible",
17625
+ external: false,
17626
+ blastRadius: "unknown tool action",
17627
+ confidence: 0.5,
17628
+ signals: ["indeterminate_tool_effect"]
17629
+ }
17630
+ };
17631
+ }
17632
+ return {
17633
+ verdict: "allow_flagged",
17634
+ reason: "indeterminate_tool_effect",
17635
+ summary,
17636
+ fingerprint,
17637
+ assessment: {
17638
+ reversibility: "recoverable_with_cost",
17639
+ external: false,
17640
+ blastRadius: "tool scope",
17641
+ confidence: 0.5,
17642
+ signals: ["indeterminate_tool_effect"]
17643
+ }
17644
+ };
17645
+ }
17646
+ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17647
+ const toolName = String(payload.tool_name ?? "");
17648
+ const toolKind = normalizedToolName(toolName);
17649
+ const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17650
+ const protectedRoots = [
17651
+ ...options.protectedArtifactRoots ?? [],
17652
+ ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17653
+ ];
17654
+ if (toolKind === "shell") {
17655
+ const command = extractShellCommand(payload);
17656
+ if (!command) {
17657
+ if (options.unknownLocalEffect === "deny") {
17658
+ return {
17659
+ verdict: "deny_pending_approval",
17660
+ reason: "tool_shell_missing_command",
17661
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17662
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17663
+ assessment: {
17664
+ reversibility: "irreversible",
17665
+ external: false,
17666
+ blastRadius: "tool shell",
17667
+ confidence: 0.85,
17668
+ signals: ["missing_command"]
17669
+ }
17670
+ };
17671
+ }
17672
+ return {
17673
+ verdict: "allow_flagged",
17674
+ reason: "tool_shell_missing_command",
17675
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17676
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17677
+ assessment: {
17678
+ reversibility: "recoverable_with_cost",
17679
+ external: false,
17680
+ blastRadius: "tool shell",
17681
+ confidence: 0.5,
17682
+ signals: ["missing_command"]
17683
+ }
17684
+ };
17685
+ }
17686
+ const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17687
+ return {
17688
+ ...shellResult,
17689
+ summary: command
17690
+ };
17691
+ }
17692
+ const effect = inferToolPayloadEffect(payload, toolName);
17693
+ if (effect === "file_mutation" || effect === "file_delete") {
17694
+ const filePath2 = extractFilePath(payload);
17695
+ if (!filePath2) {
17696
+ if (options.unknownLocalEffect === "deny") {
17697
+ return {
17698
+ verdict: "deny_pending_approval",
17699
+ reason: "file_mutation_missing_path",
17700
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17701
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17702
+ assessment: {
17703
+ reversibility: "irreversible",
17704
+ external: false,
17705
+ blastRadius: "file mutation",
17706
+ confidence: 0.85,
17707
+ signals: ["missing_path"]
17708
+ }
17709
+ };
17710
+ }
17711
+ return {
17712
+ verdict: "allow_flagged",
17713
+ reason: "file_mutation_missing_path",
17714
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17715
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17716
+ assessment: {
17717
+ reversibility: "recoverable_with_cost",
17718
+ external: false,
17719
+ blastRadius: "file mutation",
17720
+ confidence: 0.55,
17721
+ signals: ["missing_path"]
17722
+ }
17723
+ };
17724
+ }
17725
+ return classifyFilePathMutation({
17726
+ toolName,
17727
+ toolKind,
17728
+ filePath: filePath2,
17729
+ repoRoot,
17730
+ cwd,
17731
+ config,
17732
+ options,
17733
+ sensitivePaths,
17734
+ protectedRoots,
17735
+ isDelete: effect === "file_delete"
17736
+ });
17737
+ }
17738
+ if (effect === "apply_patch") {
17370
17739
  const patch = extractPatch(payload);
17371
17740
  const targets = patch ? applyPatchTargets2(patch) : [];
17372
17741
  if (targets.length === 0) {
@@ -17404,7 +17773,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17404
17773
  const result = await classifyToolUse(
17405
17774
  {
17406
17775
  tool_name: target.delete ? "Delete" : "Write",
17407
- tool_input: { path: target.path }
17776
+ tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
17408
17777
  },
17409
17778
  repoRoot,
17410
17779
  cwd,
@@ -17430,19 +17799,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17430
17799
  }
17431
17800
  };
17432
17801
  }
17433
- return {
17434
- verdict: "allow",
17435
- reason: "unclassified_tool",
17436
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17437
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17438
- assessment: {
17439
- reversibility: "reversible",
17440
- external: false,
17441
- blastRadius: "tool scope",
17442
- confidence: 0.5,
17443
- signals: ["unclassified_tool"]
17802
+ if (effect === "file_read") {
17803
+ const filePath2 = extractFilePath(payload);
17804
+ if (!filePath2) {
17805
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17444
17806
  }
17445
- };
17807
+ return classifyFilePathRead({
17808
+ toolName,
17809
+ toolKind,
17810
+ filePath: filePath2,
17811
+ repoRoot,
17812
+ cwd,
17813
+ config,
17814
+ options,
17815
+ sensitivePaths,
17816
+ protectedRoots
17817
+ });
17818
+ }
17819
+ if (effect === "search_read") {
17820
+ return {
17821
+ verdict: "allow",
17822
+ reason: "effect.search_read",
17823
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17824
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17825
+ assessment: {
17826
+ reversibility: "reversible",
17827
+ external: false,
17828
+ blastRadius: "tool scope",
17829
+ confidence: 0.88,
17830
+ signals: ["effect.search_read"]
17831
+ }
17832
+ };
17833
+ }
17834
+ const filePath = extractFilePath(payload);
17835
+ if (filePath) {
17836
+ return classifyFilePathMutation({
17837
+ toolName,
17838
+ toolKind,
17839
+ filePath,
17840
+ repoRoot,
17841
+ cwd,
17842
+ config,
17843
+ options,
17844
+ sensitivePaths,
17845
+ protectedRoots,
17846
+ isDelete: toolKind === "delete"
17847
+ });
17848
+ }
17849
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17446
17850
  }
17447
17851
 
17448
17852
  // src/core/gate-engine.ts
@@ -17777,15 +18181,8 @@ function gateEnabledForAction(config, action) {
17777
18181
  if (action.kind === "subagent") {
17778
18182
  return config.gates.subagent;
17779
18183
  }
17780
- const toolName = action.toolName ?? String(action.payload?.tool_name ?? "");
17781
- if (toolName === "Shell") {
17782
- return config.gates.toolShell;
17783
- }
17784
- if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
17785
- return config.gates.fileMutation;
17786
- }
17787
- if (toolName === "Task") {
17788
- return config.gates.subagent;
18184
+ if (action.kind === "tool") {
18185
+ return config.gates.fileMutation || config.gates.toolShell;
17789
18186
  }
17790
18187
  return true;
17791
18188
  }
@@ -17822,7 +18219,7 @@ function hashDecisionConfig(config) {
17822
18219
  init_fingerprint2();
17823
18220
 
17824
18221
  // src/version.ts
17825
- var PACKAGE_VERSION = "0.9.4";
18222
+ var PACKAGE_VERSION = "0.10.1";
17826
18223
 
17827
18224
  // src/runtime-provenance.ts
17828
18225
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17895,56 +18292,56 @@ function capabilityRequestsBlockRecovery(requests) {
17895
18292
  init_fingerprint2();
17896
18293
  init_path_utils();
17897
18294
  import { randomUUID as randomUUID5 } from "node:crypto";
17898
- import { existsSync as existsSync15 } from "node:fs";
17899
- import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17900
- import path54 from "node:path";
18295
+ import { existsSync as existsSync16 } from "node:fs";
18296
+ import { mkdir as mkdir12, readdir as readdir3, readFile as readFile13, rename as rename4, rm as rm6 } from "node:fs/promises";
18297
+ import path55 from "node:path";
17901
18298
 
17902
18299
  // src/core/recovery/artifact-store.ts
17903
18300
  init_fingerprint2();
17904
18301
  init_path_utils();
17905
18302
  import { randomUUID as randomUUID4 } from "node:crypto";
17906
- import { existsSync as existsSync13 } from "node:fs";
17907
- import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile9, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17908
- import path51 from "node:path";
18303
+ import { existsSync as existsSync14 } from "node:fs";
18304
+ import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile10, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
18305
+ import path52 from "node:path";
17909
18306
 
17910
18307
  // src/core/recovery/snapshot-node.ts
17911
18308
  init_fingerprint2();
17912
18309
  init_path_utils();
17913
18310
  import { createHash as createHash12 } from "node:crypto";
17914
- import { existsSync as existsSync12 } from "node:fs";
18311
+ import { existsSync as existsSync13 } from "node:fs";
17915
18312
  import {
17916
- chmod as chmod3,
18313
+ chmod as chmod4,
17917
18314
  copyFile as copyFile2,
17918
18315
  lstat as lstat6,
17919
- mkdir as mkdir9,
17920
- open as open4,
17921
- readFile as readFile8,
18316
+ mkdir as mkdir10,
18317
+ open as open5,
18318
+ readFile as readFile9,
17922
18319
  readlink as readlink4,
17923
18320
  rm as rm4,
17924
18321
  rmdir as rmdir2,
17925
18322
  symlink as symlink3,
17926
18323
  writeFile as writeFile6
17927
18324
  } from "node:fs/promises";
17928
- import path50 from "node:path";
18325
+ import path51 from "node:path";
17929
18326
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17930
18327
  function validRecoveryRelativePath(relativePath) {
17931
- if (!relativePath || relativePath.includes("\0") || path50.isAbsolute(relativePath)) return false;
17932
- const normalized = path50.normalize(relativePath);
17933
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path50.sep}`);
18328
+ if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
18329
+ const normalized = path51.normalize(relativePath);
18330
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
17934
18331
  }
17935
18332
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17936
18333
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17937
18334
  const root = canonicalPath(resourceRoot);
17938
- const target = path50.resolve(root, relativePath);
17939
- const relative = path50.relative(root, target);
17940
- if (relative === ".." || relative.startsWith(`..${path50.sep}`) || path50.isAbsolute(relative)) {
18335
+ const target = path51.resolve(root, relativePath);
18336
+ const relative = path51.relative(root, target);
18337
+ if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
17941
18338
  throw new Error("recovery_path_escape");
17942
18339
  }
17943
18340
  let current = root;
17944
- const parentParts = path50.relative(root, path50.dirname(target)).split(path50.sep).filter(Boolean);
18341
+ const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
17945
18342
  for (const part of parentParts) {
17946
- current = path50.join(current, part);
17947
- if (!existsSync12(current)) break;
18343
+ current = path51.join(current, part);
18344
+ if (!existsSync13(current)) break;
17948
18345
  const info = await lstat6(current);
17949
18346
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
17950
18347
  if (!info.isDirectory()) break;
@@ -17952,7 +18349,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17952
18349
  return target;
17953
18350
  }
17954
18351
  async function fsyncPath(filePath) {
17955
- const handle = await open4(filePath, "r");
18352
+ const handle = await open5(filePath, "r");
17956
18353
  try {
17957
18354
  await handle.sync();
17958
18355
  } finally {
@@ -17994,13 +18391,13 @@ async function captureRecoverySnapshot(filePath, options) {
17994
18391
  return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
17995
18392
  }
17996
18393
  if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
17997
- const content = await readFile8(filePath);
18394
+ const content = await readFile9(filePath);
17998
18395
  const hash = createHash12("sha256").update(content).digest("hex");
17999
18396
  let blob;
18000
18397
  if (options?.blobDir) {
18001
- await mkdir9(options.blobDir, { recursive: true, mode: 448 });
18002
- const blobPath = path50.join(options.blobDir, hash);
18003
- if (!existsSync12(blobPath)) {
18398
+ await mkdir10(options.blobDir, { recursive: true, mode: 448 });
18399
+ const blobPath = path51.join(options.blobDir, hash);
18400
+ if (!existsSync13(blobPath)) {
18004
18401
  await writeFile6(blobPath, content, { mode: 384 });
18005
18402
  await fsyncPath(blobPath);
18006
18403
  }
@@ -18054,7 +18451,7 @@ async function validateRecoverySnapshot(params) {
18054
18451
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
18055
18452
  let content;
18056
18453
  try {
18057
- content = await readFile8(path50.join(params.artifactDir, record.blob));
18454
+ content = await readFile9(path51.join(params.artifactDir, record.blob));
18058
18455
  } catch {
18059
18456
  throw new Error(params.corruptReason);
18060
18457
  }
@@ -18082,16 +18479,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
18082
18479
  ]);
18083
18480
  var STAGING_STALE_MS = 5 * 6e4;
18084
18481
  function checkpointsRoot(stateDir) {
18085
- return path51.join(stateDir, "recovery", "checkpoints");
18482
+ return path52.join(stateDir, "recovery", "checkpoints");
18086
18483
  }
18087
18484
  function checkpointDir(stateDir, checkpointId) {
18088
18485
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
18089
18486
  throw new Error("invalid_recovery_checkpoint_id");
18090
18487
  }
18091
- return path51.join(checkpointsRoot(stateDir), checkpointId);
18488
+ return path52.join(checkpointsRoot(stateDir), checkpointId);
18092
18489
  }
18093
18490
  async function fsyncPath2(filePath) {
18094
- const handle = await open5(filePath, "r");
18491
+ const handle = await open6(filePath, "r");
18095
18492
  try {
18096
18493
  await handle.sync();
18097
18494
  } finally {
@@ -18099,13 +18496,13 @@ async function fsyncPath2(filePath) {
18099
18496
  }
18100
18497
  }
18101
18498
  async function atomicWriteJson(filePath, value) {
18102
- await mkdir10(path51.dirname(filePath), { recursive: true, mode: 448 });
18499
+ await mkdir11(path52.dirname(filePath), { recursive: true, mode: 448 });
18103
18500
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
18104
18501
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
18105
18502
  `, { mode: 384 });
18106
18503
  await fsyncPath2(temporary);
18107
- await rename2(temporary, filePath);
18108
- await fsyncPath2(path51.dirname(filePath));
18504
+ await rename3(temporary, filePath);
18505
+ await fsyncPath2(path52.dirname(filePath));
18109
18506
  }
18110
18507
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18111
18508
  const value = {
@@ -18115,13 +18512,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18115
18512
  manifestHash,
18116
18513
  ...detail ? { detail } : {}
18117
18514
  };
18118
- await atomicWriteJson(path51.join(artifactDir, "state.json"), value);
18515
+ await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
18119
18516
  }
18120
18517
  async function directorySize(root) {
18121
- if (!existsSync13(root)) return 0;
18518
+ if (!existsSync14(root)) return 0;
18122
18519
  let total = 0;
18123
18520
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18124
- const entryPath = path51.join(root, entry.name);
18521
+ const entryPath = path52.join(root, entry.name);
18125
18522
  if (entry.isDirectory()) total += await directorySize(entryPath);
18126
18523
  else total += (await lstat7(entryPath)).size;
18127
18524
  }
@@ -18166,9 +18563,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18166
18563
  let rawManifest;
18167
18564
  let state;
18168
18565
  try {
18169
- rawManifest = JSON.parse(await readFile9(path51.join(artifactDir, "manifest.json"), "utf8"));
18566
+ rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
18170
18567
  state = JSON.parse(
18171
- await readFile9(path51.join(artifactDir, "state.json"), "utf8")
18568
+ await readFile10(path52.join(artifactDir, "state.json"), "utf8")
18172
18569
  );
18173
18570
  } catch {
18174
18571
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -18184,10 +18581,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18184
18581
  }
18185
18582
  const entryPaths = /* @__PURE__ */ new Set();
18186
18583
  for (const entry of manifest.entries) {
18187
- if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path51.normalize(entry.path))) {
18584
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path52.normalize(entry.path))) {
18188
18585
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18189
18586
  }
18190
- entryPaths.add(path51.normalize(entry.path));
18587
+ entryPaths.add(path52.normalize(entry.path));
18191
18588
  for (const [side, snapshot] of [
18192
18589
  ["before", entry.before],
18193
18590
  ["after", entry.after]
@@ -18201,9 +18598,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18201
18598
  });
18202
18599
  }
18203
18600
  }
18204
- const receiptPath = path51.join(artifactDir, "receipt.json");
18601
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18205
18602
  let receipt;
18206
- if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18603
+ if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
18207
18604
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18208
18605
  }
18209
18606
  return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
@@ -18211,7 +18608,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18211
18608
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18212
18609
  let rawReceipt;
18213
18610
  try {
18214
- rawReceipt = JSON.parse(await readFile9(path51.join(artifactDir, "receipt.json"), "utf8"));
18611
+ rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
18215
18612
  } catch {
18216
18613
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18217
18614
  }
@@ -18234,8 +18631,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18234
18631
  return receipt;
18235
18632
  }
18236
18633
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18237
- const receiptPath = path51.join(artifactDir, "receipt.json");
18238
- if (existsSync13(receiptPath)) {
18634
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18635
+ if (existsSync14(receiptPath)) {
18239
18636
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18240
18637
  }
18241
18638
  const receipt = {
@@ -18252,14 +18649,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18252
18649
  }
18253
18650
  async function checkpointIds(stateDir) {
18254
18651
  const root = checkpointsRoot(stateDir);
18255
- if (!existsSync13(root)) return [];
18652
+ if (!existsSync14(root)) return [];
18256
18653
  return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
18257
18654
  }
18258
18655
  async function artifactRepoRoot(stateDir, checkpointId) {
18259
18656
  const artifactDir = checkpointDir(stateDir, checkpointId);
18260
18657
  try {
18261
18658
  const manifest = JSON.parse(
18262
- await readFile9(path51.join(artifactDir, "manifest.json"), "utf8")
18659
+ await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
18263
18660
  );
18264
18661
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18265
18662
  return canonicalPath(manifest.repoRoot);
@@ -18267,7 +18664,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18267
18664
  } catch {
18268
18665
  }
18269
18666
  try {
18270
- const owner = JSON.parse(await readFile9(path51.join(artifactDir, "owner.json"), "utf8"));
18667
+ const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
18271
18668
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18272
18669
  } catch {
18273
18670
  return null;
@@ -18283,14 +18680,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
18283
18680
  }
18284
18681
  async function cleanupOrphanedStaging(stateDir) {
18285
18682
  const root = checkpointsRoot(stateDir);
18286
- if (!existsSync13(root)) return;
18683
+ if (!existsSync14(root)) return;
18287
18684
  const now = Date.now();
18288
18685
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18289
18686
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18290
- const stagingPath = path51.join(root, entry.name);
18687
+ const stagingPath = path52.join(root, entry.name);
18291
18688
  let stale = false;
18292
18689
  try {
18293
- const owner = JSON.parse(await readFile9(path51.join(stagingPath, "owner.json"), "utf8"));
18690
+ const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
18294
18691
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18295
18692
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18296
18693
  let alive = false;
@@ -18330,9 +18727,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18330
18727
 
18331
18728
  // src/core/recovery/reconcile.ts
18332
18729
  init_fingerprint2();
18333
- import { existsSync as existsSync14 } from "node:fs";
18334
- import { readFile as readFile10 } from "node:fs/promises";
18335
- import path52 from "node:path";
18730
+ import { existsSync as existsSync15 } from "node:fs";
18731
+ import { readFile as readFile11 } from "node:fs/promises";
18732
+ import path53 from "node:path";
18336
18733
  async function matchRecoverySide(resourceRoot, entries, side) {
18337
18734
  for (const entry of entries) {
18338
18735
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18346,9 +18743,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18346
18743
  loaded = await readRecoveryArtifact(stateDir, checkpointId);
18347
18744
  } catch {
18348
18745
  const artifactDir = checkpointDir(stateDir, checkpointId);
18349
- if (existsSync14(artifactDir)) {
18350
- const manifestPath = path52.join(artifactDir, "manifest.json");
18351
- const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
18746
+ if (existsSync15(artifactDir)) {
18747
+ const manifestPath = path53.join(artifactDir, "manifest.json");
18748
+ const hash = existsSync15(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
18352
18749
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18353
18750
  }
18354
18751
  return "corrupt";
@@ -18383,8 +18780,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18383
18780
 
18384
18781
  // src/core/recovery/resource-identity.ts
18385
18782
  init_fingerprint2();
18386
- import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
18387
- import path53 from "node:path";
18783
+ import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
18784
+ import path54 from "node:path";
18388
18785
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18389
18786
  const resolvedRoot = await realpath3(resourceRoot);
18390
18787
  if (resourceKind === "directory") {
@@ -18392,13 +18789,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18392
18789
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18393
18790
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18394
18791
  }
18395
- const dotGit = path53.join(resolvedRoot, ".git");
18792
+ const dotGit = path54.join(resolvedRoot, ".git");
18396
18793
  const gitInfo = await lstat8(dotGit);
18397
18794
  let gitMetadataPath = dotGit;
18398
18795
  if (gitInfo.isFile()) {
18399
- const marker = (await readFile11(dotGit, "utf8")).trim();
18796
+ const marker = (await readFile12(dotGit, "utf8")).trim();
18400
18797
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18401
- gitMetadataPath = path53.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18798
+ gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18402
18799
  } else if (!gitInfo.isDirectory()) {
18403
18800
  throw new Error("recovery_repo_identity_unavailable");
18404
18801
  }
@@ -18463,7 +18860,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
18463
18860
  async function prepareRecoveryCheckpoint(params) {
18464
18861
  const backend = params.backend ?? "git_worktree";
18465
18862
  const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
18466
- await mkdir11(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18863
+ await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18467
18864
  await cleanupOrphanedStaging(params.stateDir);
18468
18865
  await garbageCollect(params.stateDir, params.config, params.repoRoot);
18469
18866
  const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
@@ -18471,16 +18868,16 @@ async function prepareRecoveryCheckpoint(params) {
18471
18868
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18472
18869
  }
18473
18870
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18474
- const temporary = path54.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18871
+ const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18475
18872
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18476
- await mkdir11(temporary, { recursive: true, mode: 448 });
18477
- await atomicWriteJson(path54.join(temporary, "owner.json"), {
18873
+ await mkdir12(temporary, { recursive: true, mode: 448 });
18874
+ await atomicWriteJson(path55.join(temporary, "owner.json"), {
18478
18875
  version: 1,
18479
18876
  pid: process.pid,
18480
18877
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18481
18878
  repoRoot: canonicalPath(params.repoRoot)
18482
18879
  });
18483
- await mkdir11(path54.join(temporary, "blobs"), { recursive: true, mode: 448 });
18880
+ await mkdir12(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
18484
18881
  try {
18485
18882
  const entries = [];
18486
18883
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18489,8 +18886,8 @@ async function prepareRecoveryCheckpoint(params) {
18489
18886
  )) {
18490
18887
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18491
18888
  if (protectedRoots.some((root) => {
18492
- const relative = path54.relative(root, target);
18493
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path54.sep}`) && !path54.isAbsolute(relative);
18889
+ const relative = path55.relative(root, target);
18890
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
18494
18891
  })) {
18495
18892
  throw new Error("recovery_protected_path");
18496
18893
  }
@@ -18502,7 +18899,7 @@ async function prepareRecoveryCheckpoint(params) {
18502
18899
  entries.push({
18503
18900
  path: change.relativePath,
18504
18901
  before: await captureRecoverySnapshot(baseline, {
18505
- blobDir: path54.join(temporary, "blobs")
18902
+ blobDir: path55.join(temporary, "blobs")
18506
18903
  }),
18507
18904
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18508
18905
  });
@@ -18539,12 +18936,12 @@ async function prepareRecoveryCheckpoint(params) {
18539
18936
  entries
18540
18937
  };
18541
18938
  const manifestHash = hashValue(canonicalStringify(manifest));
18542
- await atomicWriteJson(path54.join(temporary, "manifest.json"), manifest);
18939
+ await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
18543
18940
  await writeRecoveryState(temporary, "prepared", manifestHash);
18544
18941
  await fsyncPath2(temporary);
18545
18942
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
18546
18943
  if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18547
- await rename3(temporary, finalDir);
18944
+ await rename4(temporary, finalDir);
18548
18945
  await fsyncPath2(checkpointsRoot(params.stateDir));
18549
18946
  return {
18550
18947
  checkpointId,
@@ -18582,7 +18979,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18582
18979
  } catch {
18583
18980
  try {
18584
18981
  const raw = JSON.parse(
18585
- await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18982
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18586
18983
  );
18587
18984
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18588
18985
  } catch {
@@ -18616,7 +19013,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18616
19013
  } catch {
18617
19014
  try {
18618
19015
  const manifest = JSON.parse(
18619
- await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
19016
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18620
19017
  );
18621
19018
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18622
19019
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18641,12 +19038,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18641
19038
  async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18642
19039
  const root = checkpointsRoot(stateDir);
18643
19040
  if (!repoRoot) return directorySize(root);
18644
- if (!existsSync15(root)) return 0;
19041
+ if (!existsSync16(root)) return 0;
18645
19042
  const expected = canonicalPath(repoRoot);
18646
19043
  let total = 0;
18647
19044
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18648
19045
  if (!entry.isDirectory()) continue;
18649
- const entryPath = path54.join(root, entry.name);
19046
+ const entryPath = path55.join(root, entry.name);
18650
19047
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18651
19048
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18652
19049
  total += await directorySize(entryPath);
@@ -18655,7 +19052,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18655
19052
  }
18656
19053
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18657
19054
  try {
18658
- const owner = JSON.parse(await readFile12(path54.join(entryPath, "owner.json"), "utf8"));
19055
+ const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
18659
19056
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18660
19057
  total += await directorySize(entryPath);
18661
19058
  }
@@ -18697,16 +19094,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
18697
19094
  init_scrub();
18698
19095
 
18699
19096
  // src/core/transactional/file-checkpoint-backend.ts
18700
- import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
19097
+ import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18701
19098
  import os5 from "node:os";
18702
- import path58 from "node:path";
19099
+ import path59 from "node:path";
18703
19100
 
18704
19101
  // src/core/transactional/file-checkpoint-git.ts
18705
19102
  init_path_utils();
18706
19103
  import { spawn as spawn8 } from "node:child_process";
18707
19104
  import { createHash as createHash13 } from "node:crypto";
18708
- import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18709
- import path55 from "node:path";
19105
+ import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
19106
+ import path56 from "node:path";
18710
19107
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18711
19108
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18712
19109
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18740,7 +19137,7 @@ function rethrowStableFileCheckpointError(error) {
18740
19137
  }
18741
19138
  async function rootGitMetadataPresent(repoRoot) {
18742
19139
  try {
18743
- await lstat9(path55.join(repoRoot, ".git"));
19140
+ await lstat9(path56.join(repoRoot, ".git"));
18744
19141
  return true;
18745
19142
  } catch {
18746
19143
  return false;
@@ -18789,10 +19186,10 @@ function execGit2(repoRoot, args) {
18789
19186
  }
18790
19187
  async function resolveGitPath(repoRoot, gitPath) {
18791
19188
  const trimmed = gitPath.trim();
18792
- if (path55.isAbsolute(trimmed)) {
19189
+ if (path56.isAbsolute(trimmed)) {
18793
19190
  return trimmed;
18794
19191
  }
18795
- return path55.join(repoRoot, trimmed);
19192
+ return path56.join(repoRoot, trimmed);
18796
19193
  }
18797
19194
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18798
19195
  await execGit2(sourceRoot, [
@@ -18834,7 +19231,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18834
19231
  destinationRoot,
18835
19232
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18836
19233
  );
18837
- const destinationShared = path55.join(destinationGitDir, path55.basename(sourceShared));
19234
+ const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
18838
19235
  try {
18839
19236
  await copyFile3(sourceShared, destinationShared);
18840
19237
  } catch (error) {
@@ -18843,14 +19240,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18843
19240
  }
18844
19241
  async function readGitFile(gitDir, relativePath) {
18845
19242
  try {
18846
- return await readFile13(path55.join(gitDir, relativePath));
19243
+ return await readFile14(path56.join(gitDir, relativePath));
18847
19244
  } catch {
18848
19245
  return null;
18849
19246
  }
18850
19247
  }
18851
19248
  async function readAbsoluteGitFile(absolutePath) {
18852
19249
  try {
18853
- return await readFile13(absolutePath);
19250
+ return await readFile14(absolutePath);
18854
19251
  } catch {
18855
19252
  return null;
18856
19253
  }
@@ -18867,8 +19264,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18867
19264
  repoRoot,
18868
19265
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18869
19266
  );
18870
- const relative = path55.resolve(resolved).startsWith(path55.resolve(gitDir)) ? path55.relative(gitDir, resolved) : resolved;
18871
- const content = typeof relative === "string" && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19267
+ const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
19268
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18872
19269
  if (content !== null) {
18873
19270
  hashGitFileContent(hash, gitPath, content);
18874
19271
  }
@@ -18878,13 +19275,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18878
19275
  async function hashGitTree(gitDir, relativeDir, hash) {
18879
19276
  let names;
18880
19277
  try {
18881
- names = await readdir4(path55.join(gitDir, relativeDir));
19278
+ names = await readdir4(path56.join(gitDir, relativeDir));
18882
19279
  } catch {
18883
19280
  return;
18884
19281
  }
18885
19282
  for (const name of names.sort()) {
18886
- const relativePath = relativeDir ? path55.join(relativeDir, name) : name;
18887
- const absolutePath = path55.join(gitDir, relativePath);
19283
+ const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
19284
+ const absolutePath = path56.join(gitDir, relativePath);
18888
19285
  let childNames = null;
18889
19286
  try {
18890
19287
  childNames = await readdir4(absolutePath);
@@ -18906,7 +19303,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18906
19303
  }
18907
19304
  async function computeGitMetadataFingerprint(repoRoot) {
18908
19305
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18909
- const gitDir = path55.isAbsolute(gitDirRel) ? gitDirRel : path55.join(repoRoot, gitDirRel);
19306
+ const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
18910
19307
  const hash = createHash13("sha256");
18911
19308
  for (const file of [
18912
19309
  "HEAD",
@@ -18929,8 +19326,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18929
19326
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18930
19327
  if (sharedIndex) {
18931
19328
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18932
- const relative = path55.relative(gitDir, resolved);
18933
- const content = relative && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19329
+ const relative = path56.relative(gitDir, resolved);
19330
+ const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18934
19331
  if (content !== null) {
18935
19332
  hashGitFileContent(hash, "shared-index", content);
18936
19333
  }
@@ -18941,7 +19338,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18941
19338
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18942
19339
  }
18943
19340
  try {
18944
- const rootGitPath = path55.join(repoRoot, ".git");
19341
+ const rootGitPath = path56.join(repoRoot, ".git");
18945
19342
  const rootGitInfo = await lstat9(rootGitPath);
18946
19343
  if (rootGitInfo.isFile()) {
18947
19344
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18957,14 +19354,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18957
19354
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18958
19355
  const resolvedCwd = canonicalPath(cwd);
18959
19356
  const resourceCanonical = canonicalPath(resourceRoot);
18960
- const relative = path55.relative(resourceCanonical, resolvedCwd);
19357
+ const relative = path56.relative(resourceCanonical, resolvedCwd);
18961
19358
  if (relative === "" || relative === ".") {
18962
19359
  return "";
18963
19360
  }
18964
- if (relative.startsWith("..") || path55.isAbsolute(relative)) {
19361
+ if (relative.startsWith("..") || path56.isAbsolute(relative)) {
18965
19362
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18966
19363
  }
18967
- return relative.split(path55.sep).join("/");
19364
+ return relative.split(path56.sep).join("/");
18968
19365
  }
18969
19366
 
18970
19367
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18985,8 +19382,8 @@ function fileCheckpointIsolationReason(context) {
18985
19382
  }
18986
19383
 
18987
19384
  // src/core/transactional/file-checkpoint-staging.ts
18988
- import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18989
- import path56 from "node:path";
19385
+ import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
19386
+ import path57 from "node:path";
18990
19387
  function isOwnerProcessAlive(pid) {
18991
19388
  try {
18992
19389
  process.kill(pid, 0);
@@ -18996,12 +19393,12 @@ function isOwnerProcessAlive(pid) {
18996
19393
  }
18997
19394
  }
18998
19395
  async function writeOwnerMarker(stagingRoot, marker) {
18999
- await writeFile8(path56.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19396
+ await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19000
19397
  `, "utf8");
19001
19398
  }
19002
19399
  async function readOwnerMarker(stagingRoot) {
19003
19400
  try {
19004
- const raw = await readFile14(path56.join(stagingRoot, "owner.json"), "utf8");
19401
+ const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
19005
19402
  return JSON.parse(raw.trim());
19006
19403
  } catch {
19007
19404
  return null;
@@ -19019,7 +19416,7 @@ async function collectDeadOwnerStaging(parentDir) {
19019
19416
  if (!name.startsWith("belay-file-checkpoint-")) {
19020
19417
  continue;
19021
19418
  }
19022
- const stagingRoot = path56.join(parentDir, name);
19419
+ const stagingRoot = path57.join(parentDir, name);
19023
19420
  const marker = await readOwnerMarker(stagingRoot);
19024
19421
  if (!marker) {
19025
19422
  dead.push(stagingRoot);
@@ -19042,7 +19439,7 @@ import { constants as fsConstants2 } from "node:fs";
19042
19439
  import {
19043
19440
  copyFile as copyFile4,
19044
19441
  lstat as lstat10,
19045
- mkdir as mkdir12,
19442
+ mkdir as mkdir13,
19046
19443
  mkdtemp as mkdtemp4,
19047
19444
  readlink as readlink5,
19048
19445
  rm as rm8,
@@ -19051,17 +19448,17 @@ import {
19051
19448
  writeFile as writeFile9
19052
19449
  } from "node:fs/promises";
19053
19450
  import os4 from "node:os";
19054
- import path57 from "node:path";
19451
+ import path58 from "node:path";
19055
19452
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
19056
19453
  async function chmodSafe2(target, mode) {
19057
19454
  try {
19058
- const { chmod: chmod4 } = await import("node:fs/promises");
19059
- await chmod4(target, mode & 511);
19455
+ const { chmod: chmod5 } = await import("node:fs/promises");
19456
+ await chmod5(target, mode & 511);
19060
19457
  } catch {
19061
19458
  }
19062
19459
  }
19063
19460
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
19064
- await mkdir12(path57.dirname(destinationPath), { recursive: true });
19461
+ await mkdir13(path58.dirname(destinationPath), { recursive: true });
19065
19462
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
19066
19463
  try {
19067
19464
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -19083,11 +19480,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
19083
19480
  const info = await lstat10(sourcePath);
19084
19481
  await rm8(destinationPath, { force: true, recursive: false });
19085
19482
  if (info.isDirectory() && !info.isSymbolicLink()) {
19086
- await mkdir12(destinationPath, { recursive: true, mode: info.mode & 511 });
19483
+ await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
19087
19484
  return strategy;
19088
19485
  }
19089
19486
  if (info.isSymbolicLink()) {
19090
- await mkdir12(path57.dirname(destinationPath), { recursive: true });
19487
+ await mkdir13(path58.dirname(destinationPath), { recursive: true });
19091
19488
  await symlink4(await readlink5(sourcePath), destinationPath);
19092
19489
  return strategy;
19093
19490
  }
@@ -19143,9 +19540,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
19143
19540
  async function probeFileCloneStrategy() {
19144
19541
  let tempDir = null;
19145
19542
  try {
19146
- tempDir = await mkdtemp4(path57.join(os4.tmpdir(), "belay-clone-probe-"));
19147
- const source = path57.join(tempDir, "source.txt");
19148
- const destination = path57.join(tempDir, "dest.txt");
19543
+ tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
19544
+ const source = path58.join(tempDir, "source.txt");
19545
+ const destination = path58.join(tempDir, "dest.txt");
19149
19546
  await writeFile9(source, "probe\n");
19150
19547
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
19151
19548
  try {
@@ -19298,11 +19695,11 @@ async function protectedRootState(root) {
19298
19695
  return `directory:${node.hash}:${index.treeHash}`;
19299
19696
  }
19300
19697
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19301
- const relative = path58.relative(path58.resolve(resourceRoot), path58.resolve(protectedRoot));
19302
- if (relative === "" || relative.startsWith("..") || path58.isAbsolute(relative)) {
19698
+ const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
19699
+ if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
19303
19700
  return null;
19304
19701
  }
19305
- return path58.join(executionRoot, relative);
19702
+ return path59.join(executionRoot, relative);
19306
19703
  }
19307
19704
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19308
19705
  const states = /* @__PURE__ */ new Map();
@@ -19327,15 +19724,15 @@ async function directoryByteSize(root, deadlineMs) {
19327
19724
  }
19328
19725
  let total = 0;
19329
19726
  for (const name of await readdir6(root)) {
19330
- total += await directoryByteSize(path58.join(root, name), deadlineMs);
19727
+ total += await directoryByteSize(path59.join(root, name), deadlineMs);
19331
19728
  }
19332
19729
  return total;
19333
19730
  }
19334
19731
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19335
19732
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
19336
- const sourceGitDir = path58.isAbsolute(gitDirRel) ? gitDirRel : path58.join(sourceRoot, gitDirRel);
19337
- const relativeGitDir = path58.relative(path58.resolve(sourceRoot), path58.resolve(sourceGitDir));
19338
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path58.join(destinationRoot, relativeGitDir) : path58.join(destinationRoot, ".git");
19733
+ const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
19734
+ const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
19735
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
19339
19736
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19340
19737
  }
19341
19738
  async function prepareDirtyGitSnapshot(context) {
@@ -19344,7 +19741,7 @@ async function prepareDirtyGitSnapshot(context) {
19344
19741
  const quotas = context.fileCheckpoint;
19345
19742
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19346
19743
  await removeDeadOwnerStaging(os5.tmpdir());
19347
- const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19744
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19348
19745
  await writeOwnerMarker(stagingRoot, {
19349
19746
  version: 1,
19350
19747
  pid: process.pid,
@@ -19352,8 +19749,8 @@ async function prepareDirtyGitSnapshot(context) {
19352
19749
  resourceRoot: context.repoRoot,
19353
19750
  backend: "file_checkpoint"
19354
19751
  });
19355
- const baselineRoot = path58.join(stagingRoot, "baseline");
19356
- const executionRoot = path58.join(stagingRoot, "execution");
19752
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19753
+ const executionRoot = path59.join(stagingRoot, "execution");
19357
19754
  try {
19358
19755
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19359
19756
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19387,7 +19784,7 @@ async function prepareDirtyGitSnapshot(context) {
19387
19784
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19388
19785
  }
19389
19786
  await writeFile10(
19390
- path58.join(stagingRoot, "baseline-index.json"),
19787
+ path59.join(stagingRoot, "baseline-index.json"),
19391
19788
  `${JSON.stringify(baselineIndex)}
19392
19789
  `,
19393
19790
  "utf8"
@@ -19437,7 +19834,7 @@ async function prepareNonGitSnapshot(context) {
19437
19834
  const quotas = context.fileCheckpoint;
19438
19835
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19439
19836
  await removeDeadOwnerStaging(os5.tmpdir());
19440
- const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19837
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19441
19838
  await writeOwnerMarker(stagingRoot, {
19442
19839
  version: 1,
19443
19840
  pid: process.pid,
@@ -19445,8 +19842,8 @@ async function prepareNonGitSnapshot(context) {
19445
19842
  resourceRoot: context.repoRoot,
19446
19843
  backend: "file_checkpoint"
19447
19844
  });
19448
- const baselineRoot = path58.join(stagingRoot, "baseline");
19449
- const executionRoot = path58.join(stagingRoot, "execution");
19845
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19846
+ const executionRoot = path59.join(stagingRoot, "execution");
19450
19847
  try {
19451
19848
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19452
19849
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19455,7 +19852,7 @@ async function prepareNonGitSnapshot(context) {
19455
19852
  quotas,
19456
19853
  deadlineMs
19457
19854
  });
19458
- await mkdir13(baselineRoot, { recursive: true });
19855
+ await mkdir14(baselineRoot, { recursive: true });
19459
19856
  const baselineIndex = await buildFileTreeIndex({
19460
19857
  resourceRoot: baselineRoot,
19461
19858
  excludedRoots,
@@ -19475,7 +19872,7 @@ async function prepareNonGitSnapshot(context) {
19475
19872
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19476
19873
  }
19477
19874
  await writeFile10(
19478
- path58.join(stagingRoot, "baseline-index.json"),
19875
+ path59.join(stagingRoot, "baseline-index.json"),
19479
19876
  `${JSON.stringify(baselineIndex)}
19480
19877
  `,
19481
19878
  "utf8"
@@ -19485,7 +19882,7 @@ async function prepareNonGitSnapshot(context) {
19485
19882
  quotas,
19486
19883
  deadlineMs
19487
19884
  });
19488
- await mkdir13(executionRoot, { recursive: true });
19885
+ await mkdir14(executionRoot, { recursive: true });
19489
19886
  const finalSourceIndex = await buildFileTreeIndex({
19490
19887
  resourceRoot: context.repoRoot,
19491
19888
  excludedRoots,
@@ -19828,10 +20225,10 @@ async function selectTransactionalBackend(context) {
19828
20225
  }
19829
20226
 
19830
20227
  // src/core/transactional/diff-evaluator.ts
19831
- import path59 from "node:path";
20228
+ import path60 from "node:path";
19832
20229
  init_path_utils();
19833
20230
  function categorizeChange(change, ctx) {
19834
- const absolutePath = canonicalPath(path59.join(ctx.repoRoot, change.relativePath));
20231
+ const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
19835
20232
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19836
20233
  return "repo_outside";
19837
20234
  }
@@ -20381,17 +20778,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
20381
20778
  }
20382
20779
 
20383
20780
  // src/core/notify.ts
20781
+ init_path_utils();
20384
20782
  import { execFile } from "node:child_process";
20783
+ import path61 from "node:path";
20385
20784
  import { promisify } from "node:util";
20386
20785
  var execFileAsync = promisify(execFile);
20387
- async function notifyDeny(config, event) {
20388
- const payload = JSON.stringify(event);
20389
- if (config.webhookUrl) {
20786
+ var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
20787
+ function webhookConfigIssue(url) {
20788
+ let parsed;
20789
+ try {
20790
+ parsed = new URL(url);
20791
+ } catch {
20792
+ return `notifications.webhookUrl is invalid: ${url}`;
20793
+ }
20794
+ if (parsed.protocol === "https:") {
20795
+ return null;
20796
+ }
20797
+ const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
20798
+ if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
20799
+ return null;
20800
+ }
20801
+ return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
20802
+ }
20803
+ function commandHookConfigIssue(commandHook, repoRoot) {
20804
+ if (!path61.isAbsolute(commandHook)) {
20805
+ return `notifications.commandHook must be an absolute path: ${commandHook}`;
20806
+ }
20807
+ if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
20808
+ return `notifications.commandHook must not be inside the repository: ${commandHook}`;
20809
+ }
20810
+ return null;
20811
+ }
20812
+ async function notifyDeny(config, event, deps = {
20813
+ fetch: globalThis.fetch.bind(globalThis),
20814
+ execFile: (file, args, options) => execFileAsync(file, [...args], options)
20815
+ }) {
20816
+ const payload = JSON.stringify({
20817
+ approvalId: event.approvalId,
20818
+ reason: event.reason,
20819
+ summary: event.summary,
20820
+ repoRoot: event.repoRoot,
20821
+ fingerprint: event.fingerprint
20822
+ });
20823
+ const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
20824
+ if (config.webhookUrl && !webhookIssue) {
20390
20825
  try {
20391
20826
  const controller = new AbortController();
20392
20827
  const timeout = setTimeout(() => controller.abort(), 5e3);
20393
20828
  try {
20394
- await fetch(config.webhookUrl, {
20829
+ await deps.fetch(config.webhookUrl, {
20395
20830
  method: "POST",
20396
20831
  headers: { "content-type": "application/json" },
20397
20832
  body: payload,
@@ -20403,17 +20838,16 @@ async function notifyDeny(config, event) {
20403
20838
  } catch {
20404
20839
  }
20405
20840
  }
20406
- if (config.commandHook) {
20841
+ const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
20842
+ if (config.commandHook && !commandHookIssue) {
20407
20843
  try {
20408
- await execFileAsync(config.commandHook, [], {
20844
+ await deps.execFile(config.commandHook, [], {
20409
20845
  env: {
20410
- ...process.env,
20411
20846
  BELAY_APPROVAL_ID: event.approvalId,
20412
20847
  BELAY_REASON: event.reason,
20413
20848
  BELAY_SUMMARY: event.summary,
20414
20849
  BELAY_REPO_ROOT: event.repoRoot,
20415
- BELAY_FINGERPRINT: event.fingerprint,
20416
- BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
20850
+ BELAY_FINGERPRINT: event.fingerprint
20417
20851
  }
20418
20852
  });
20419
20853
  } catch {
@@ -20423,9 +20857,10 @@ async function notifyDeny(config, event) {
20423
20857
 
20424
20858
  // src/adapters/shared/gate-runtime.ts
20425
20859
  init_path_utils();
20860
+ init_repo_config_trust();
20426
20861
 
20427
20862
  // src/adapters/layouts/protected-paths.ts
20428
- import path60 from "node:path";
20863
+ import path62 from "node:path";
20429
20864
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20430
20865
  const roots = [
20431
20866
  layout.configPath(repoRoot),
@@ -20437,7 +20872,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20437
20872
  if (controlPlaneDir) {
20438
20873
  roots.push(controlPlaneDir);
20439
20874
  }
20440
- return roots.map((entry) => path60.resolve(entry));
20875
+ return roots.map((entry) => path62.resolve(entry));
20441
20876
  }
20442
20877
 
20443
20878
  // src/adapters/shared/gate-runtime.ts
@@ -20478,7 +20913,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
20478
20913
  }
20479
20914
  async function loadJsonFile(filePath, fallback) {
20480
20915
  try {
20481
- const raw = await readFile15(filePath, "utf8");
20916
+ const raw = await readFile16(filePath, "utf8");
20482
20917
  return JSON.parse(raw);
20483
20918
  } catch {
20484
20919
  return fallback;
@@ -20487,11 +20922,18 @@ async function loadJsonFile(filePath, fallback) {
20487
20922
  function createDefaultGateRuntimeDeps() {
20488
20923
  return {
20489
20924
  async readConfig(configPath) {
20490
- return loadJsonFile(configPath, {});
20925
+ try {
20926
+ return JSON.parse(await readFile16(configPath, "utf8"));
20927
+ } catch (error) {
20928
+ if (error.code === "ENOENT") {
20929
+ return {};
20930
+ }
20931
+ throw error;
20932
+ }
20491
20933
  },
20492
20934
  async appendAudit(ctx, event) {
20493
- const auditPath = path61.join(ctx.repoRoot, ctx.config.audit.logPath);
20494
- await mkdir14(path61.dirname(auditPath), { recursive: true });
20935
+ const auditPath = path63.join(ctx.repoRoot, ctx.config.audit.logPath);
20936
+ await mkdir15(path63.dirname(auditPath), { recursive: true });
20495
20937
  const provenance = auditProvenance(ctx.config);
20496
20938
  const record = {
20497
20939
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20524,7 +20966,7 @@ function createDefaultGateRuntimeDeps() {
20524
20966
  };
20525
20967
  },
20526
20968
  async writeApprovals(filePath, state) {
20527
- await mkdir14(path61.dirname(filePath), { recursive: true });
20969
+ await mkdir15(path63.dirname(filePath), { recursive: true });
20528
20970
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20529
20971
  `, "utf8");
20530
20972
  },
@@ -20545,10 +20987,11 @@ function createDefaultGateRuntimeDeps() {
20545
20987
  }
20546
20988
  async function resolveGateConfig(ctx, deps) {
20547
20989
  const loaded = await deps.readConfig(ctx.configPath);
20990
+ await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
20548
20991
  let teamConfig = null;
20549
20992
  const teamPath = teamConfigPath();
20550
- if (existsSync16(teamPath)) {
20551
- teamConfig = JSON.parse(await readFile15(teamPath, "utf8"));
20993
+ if (existsSync17(teamPath)) {
20994
+ teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
20552
20995
  }
20553
20996
  return resolveLayeredConfig({
20554
20997
  repoConfig: loaded,
@@ -20686,7 +21129,7 @@ function deriveWorkspaceRootScopeHint(params) {
20686
21129
  if (!targetPath) {
20687
21130
  return void 0;
20688
21131
  }
20689
- const candidateRoot = canonicalPath(path61.dirname(targetPath));
21132
+ const candidateRoot = canonicalPath(path63.dirname(targetPath));
20690
21133
  const validation = validateTrustedWorkspaceRootCandidate({
20691
21134
  candidatePath: candidateRoot,
20692
21135
  repoRoot: action.repoRoot,
@@ -21262,7 +21705,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21262
21705
  event: auditEvent,
21263
21706
  sourceEvent,
21264
21707
  kind,
21265
- repoLabel: path61.basename(ctx.repoRoot),
21708
+ repoLabel: path63.basename(ctx.repoRoot),
21266
21709
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21267
21710
  fingerprint: result.fingerprint,
21268
21711
  summary: result.normalizedCommand ?? result.summary ?? "",
@@ -21294,21 +21737,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21294
21737
  if (created) {
21295
21738
  await recordGateApprovalAsk(stateDir, result.reason, false);
21296
21739
  }
21297
- let approvalToken;
21298
- try {
21299
- approvalToken = await issueApprovalToken(
21300
- {
21301
- approvalId: approval.approvalId,
21302
- fingerprint: approval.fingerprint,
21303
- repoRoot: approval.repoRoot,
21304
- issuedAt: approval.createdAt,
21305
- expiresAt: approval.expiresAt
21306
- },
21307
- configuredControlPlaneDir(ctx.config)
21308
- );
21309
- } catch {
21310
- approvalToken = void 0;
21311
- }
21312
21740
  const denialReason = failure?.reason ?? result.reason;
21313
21741
  if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
21314
21742
  await notifyDeny(ctx.config.notifications, {
@@ -21316,8 +21744,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21316
21744
  reason: denialReason,
21317
21745
  summary: result.normalizedCommand ?? result.summary ?? "",
21318
21746
  repoRoot: ctx.repoRoot,
21319
- fingerprint: result.fingerprint,
21320
- approvalToken
21747
+ fingerprint: result.fingerprint
21321
21748
  });
21322
21749
  }
21323
21750
  const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
@@ -21728,38 +22155,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
21728
22155
  }
21729
22156
 
21730
22157
  // src/adapters/shared/repo-root.ts
21731
- import { existsSync as existsSync17 } from "node:fs";
21732
- import path62 from "node:path";
22158
+ import { existsSync as existsSync18 } from "node:fs";
22159
+ import path64 from "node:path";
21733
22160
  function belayConfigPath(current, adapterName) {
21734
22161
  if (adapterName === "cursor") {
21735
- return path62.join(current, ".cursor", "belay.config.json");
22162
+ return path64.join(current, ".cursor", "belay.config.json");
21736
22163
  }
21737
22164
  if (adapterName === "claude") {
21738
- return path62.join(current, ".claude", "belay.config.json");
22165
+ return path64.join(current, ".claude", "belay.config.json");
21739
22166
  }
21740
- return path62.join(current, ".codex", "belay.config.json");
22167
+ return path64.join(current, ".codex", "belay.config.json");
21741
22168
  }
21742
22169
  function markerMatches(current, marker, layout) {
21743
- const markerPath = path62.join(current, marker);
21744
- if (!existsSync17(markerPath)) {
22170
+ const markerPath = path64.join(current, marker);
22171
+ if (!existsSync18(markerPath)) {
21745
22172
  return false;
21746
22173
  }
21747
22174
  if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
21748
- return existsSync17(belayConfigPath(current, layout.name));
22175
+ return existsSync18(belayConfigPath(current, layout.name));
21749
22176
  }
21750
22177
  return true;
21751
22178
  }
21752
22179
  function findRepoRoot(startPath, layout) {
21753
- let current = path62.resolve(startPath);
22180
+ let current = path64.resolve(startPath);
21754
22181
  while (true) {
21755
22182
  for (const marker of layout.repoRootMarkers) {
21756
22183
  if (markerMatches(current, marker, layout)) {
21757
22184
  return current;
21758
22185
  }
21759
22186
  }
21760
- const parent = path62.dirname(current);
22187
+ const parent = path64.dirname(current);
21761
22188
  if (parent === current) {
21762
- return path62.resolve(startPath);
22189
+ return path64.resolve(startPath);
21763
22190
  }
21764
22191
  current = parent;
21765
22192
  }
@@ -21957,25 +22384,12 @@ async function runToolGateHook(_eventName) {
21957
22384
  return;
21958
22385
  }
21959
22386
  if (!mappedKind) {
21960
- const verdict3 = claudeFallbackToolVerdict({
21961
- toolName,
21962
- payload,
21963
- repoRoot: ctx.repoRoot,
21964
- reason: "unmapped_tool",
21965
- mode: ctx.config.mode,
21966
- userMessage: "belay does not recognize this tool action. Run belay doctor, then retry.",
21967
- agentMessage: "Belay denied this action because the tool could not be normalized."
21968
- });
21969
- await deps.appendAudit(ctx, {
21970
- event: "preToolUse",
22387
+ const verdict3 = await evaluateGatedAction(ctx, deps, {
21971
22388
  kind: "tool",
21972
- verdict: "deny_pending_approval",
21973
- reason: "unmapped_tool",
21974
- mode: ctx.config.mode,
21975
- wouldBlock: true,
21976
- permission: "deny",
21977
- summary: toolName,
21978
- ...effectPlanAuditFields(verdict3.effectPlan)
22389
+ cwd,
22390
+ payload,
22391
+ toolName,
22392
+ sourceEvent: "PreToolUse"
21979
22393
  });
21980
22394
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict3));
21981
22395
  return;