@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
@@ -1847,7 +1847,7 @@ function normalizeConfig(config) {
1847
1847
  policy: {
1848
1848
  unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
1849
1849
  unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
1850
- codexUnmappedTool: v4.policy?.codexUnmappedTool === "allow" ? "allow" : "deny",
1850
+ codexUnmappedTool: "allow",
1851
1851
  fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
1852
1852
  confidenceThresholds: {
1853
1853
  allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
@@ -2149,7 +2149,7 @@ var init_config = __esm({
2149
2149
  DEFAULT_POLICY_V3 = {
2150
2150
  unknownLocalEffect: "allow_flagged",
2151
2151
  unparseableShell: "deny",
2152
- codexUnmappedTool: "deny",
2152
+ codexUnmappedTool: "allow",
2153
2153
  confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
2154
2154
  modelAssist: { ...DEFAULT_MODEL_ASSIST },
2155
2155
  transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
@@ -3510,9 +3510,125 @@ 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 existsSync2 } 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 path11 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 path11.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 (!existsSync2(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
+ function isRepoConfigTrustError(error) {
3601
+ return error instanceof RepoConfigTrustError;
3602
+ }
3603
+ async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
3604
+ const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
3605
+ if (status.trusted) {
3606
+ return;
3607
+ }
3608
+ throw new RepoConfigTrustError(status);
3609
+ }
3610
+ var TRUST_MESSAGE, RepoConfigTrustError;
3611
+ var init_repo_config_trust = __esm({
3612
+ "src/core/repo-config-trust.ts"() {
3613
+ "use strict";
3614
+ init_config();
3615
+ init_fingerprint2();
3616
+ init_path_utils();
3617
+ TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
3618
+ RepoConfigTrustError = class extends Error {
3619
+ status;
3620
+ constructor(status) {
3621
+ super(TRUST_MESSAGE);
3622
+ this.name = "RepoConfigTrustError";
3623
+ this.status = status;
3624
+ }
3625
+ };
3626
+ }
3627
+ });
3628
+
3629
+ // src/config-io.ts
3630
+ import { existsSync as existsSync3 } from "node:fs";
3631
+ 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
3632
  function resolveAdapterName(config) {
3517
3633
  if (config.adapter === "claude") {
3518
3634
  return "claude";
@@ -3523,10 +3639,10 @@ function resolveAdapterName(config) {
3523
3639
  return "cursor";
3524
3640
  }
3525
3641
  function detectAdapterName(repoRoot) {
3526
- if (existsSync2(configPathFor(repoRoot, "claude"))) {
3642
+ if (existsSync3(configPathFor(repoRoot, "claude"))) {
3527
3643
  return "claude";
3528
3644
  }
3529
- if (existsSync2(configPathFor(repoRoot, "codex"))) {
3645
+ if (existsSync3(configPathFor(repoRoot, "codex"))) {
3530
3646
  return "codex";
3531
3647
  }
3532
3648
  return "cursor";
@@ -3547,20 +3663,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
3547
3663
  const layout = getAdapterLayout(adapter);
3548
3664
  const configPath = configPathFor(repoRoot, adapter);
3549
3665
  let repoConfig = {};
3550
- if (existsSync2(configPath)) {
3551
- repoConfig = JSON.parse(await readFile2(configPath, "utf8"));
3666
+ if (existsSync3(configPath)) {
3667
+ repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
3552
3668
  }
3553
3669
  let teamConfig = null;
3554
3670
  const teamPath = teamConfigPath();
3555
- if (existsSync2(teamPath)) {
3556
- teamConfig = JSON.parse(await readFile2(teamPath, "utf8"));
3671
+ if (existsSync3(teamPath)) {
3672
+ teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
3557
3673
  }
3558
3674
  return resolveLayeredConfig({
3559
3675
  repoConfig,
3560
3676
  adapterDefaults: layout.defaultConfig(repoRoot),
3561
3677
  teamConfig,
3562
3678
  teamConfigPath: teamPath,
3563
- repoConfigPath: existsSync2(configPath) ? configPath : void 0
3679
+ repoConfigPath: existsSync3(configPath) ? configPath : void 0
3564
3680
  });
3565
3681
  }
3566
3682
  async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
@@ -3575,21 +3691,22 @@ var init_config_io = __esm({
3575
3691
  init_approval_state_mutation();
3576
3692
  init_config();
3577
3693
  init_config_layers();
3694
+ init_repo_config_trust();
3578
3695
  }
3579
3696
  });
3580
3697
 
3581
3698
  // src/adapters/cursor/runtime-entry.ts
3582
3699
  init_approval();
3583
- import path65 from "node:path";
3700
+ import path67 from "node:path";
3584
3701
  import process2 from "node:process";
3585
3702
 
3586
3703
  // src/core/approval-repo-lookup.ts
3587
3704
  init_config_io();
3588
- import { readFile as readFile3 } from "node:fs/promises";
3589
- import path11 from "node:path";
3705
+ import { readFile as readFile4 } from "node:fs/promises";
3706
+ import path12 from "node:path";
3590
3707
  async function approvalStateContainsId(filePath, approvalId) {
3591
3708
  try {
3592
- const raw = await readFile3(filePath, "utf8");
3709
+ const raw = await readFile4(filePath, "utf8");
3593
3710
  const parsed = JSON.parse(raw);
3594
3711
  return parsed.approvals?.some((entry) => entry.approvalId === approvalId) ?? false;
3595
3712
  } catch {
@@ -3600,7 +3717,7 @@ async function findApprovalRepoRoots(params) {
3600
3717
  const matches = [];
3601
3718
  const seen = /* @__PURE__ */ new Set();
3602
3719
  for (const candidate of params.candidateRepoRoots) {
3603
- const repoRoot = path11.resolve(candidate);
3720
+ const repoRoot = path12.resolve(candidate);
3604
3721
  if (seen.has(repoRoot)) {
3605
3722
  continue;
3606
3723
  }
@@ -3632,15 +3749,16 @@ function formatAmbiguousApprovalRepoMessage(repoRoots) {
3632
3749
 
3633
3750
  // src/adapters/cursor/runtime-entry.ts
3634
3751
  init_config();
3752
+ init_repo_config_trust();
3635
3753
  init_cursor();
3636
3754
 
3637
3755
  // src/adapters/shared/gate-runtime.ts
3638
3756
  init_approval();
3639
3757
  init_approval_replay();
3640
3758
  import { randomUUID as randomUUID6 } from "node:crypto";
3641
- import { existsSync as existsSync16 } from "node:fs";
3642
- import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
3643
- import path62 from "node:path";
3759
+ import { existsSync as existsSync17 } from "node:fs";
3760
+ import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile11 } from "node:fs/promises";
3761
+ import path64 from "node:path";
3644
3762
 
3645
3763
  // src/core/approval-service.ts
3646
3764
  init_config_io();
@@ -3650,47 +3768,35 @@ init_approval_replay();
3650
3768
  // src/core/approval-token.ts
3651
3769
  init_config();
3652
3770
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
3653
- import { existsSync as existsSync3 } from "node:fs";
3654
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
3655
- import path12 from "node:path";
3656
- function base64UrlEncode(value) {
3657
- return Buffer.from(value, "utf8").toString("base64url");
3658
- }
3771
+ import { existsSync as existsSync4 } from "node:fs";
3772
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "node:fs/promises";
3773
+ import path13 from "node:path";
3659
3774
  function base64UrlDecode(value) {
3660
3775
  return Buffer.from(value, "base64url").toString("utf8");
3661
3776
  }
3662
3777
  function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
3663
- return path12.join(controlPlaneDir, "approval-signing.key");
3778
+ return path13.join(controlPlaneDir, "approval-signing.key");
3664
3779
  }
3665
3780
  async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
3666
3781
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
3667
- if (existsSync3(keyPath)) {
3668
- return readFile4(keyPath);
3782
+ if (existsSync4(keyPath)) {
3783
+ return readFile5(keyPath);
3669
3784
  }
3670
- await mkdir3(controlPlaneDir, { recursive: true });
3785
+ await mkdir4(controlPlaneDir, { recursive: true });
3671
3786
  const key = randomBytes(32);
3672
3787
  await writeFile2(keyPath, key, { mode: 384 });
3673
3788
  return key;
3674
3789
  }
3675
- function signPayload(payload, key) {
3676
- const body = base64UrlEncode(JSON.stringify(payload));
3677
- const signature = createHmac("sha256", key).update(body).digest("base64url");
3678
- return `${body}.${signature}`;
3679
- }
3680
- async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
3681
- const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
3682
- return signPayload(payload, key);
3683
- }
3684
3790
  async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
3685
3791
  const [body, signature] = token.split(".");
3686
3792
  if (!body || !signature) {
3687
3793
  return null;
3688
3794
  }
3689
3795
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
3690
- if (!existsSync3(keyPath)) {
3796
+ if (!existsSync4(keyPath)) {
3691
3797
  return null;
3692
3798
  }
3693
- const key = await readFile4(keyPath);
3799
+ const key = await readFile5(keyPath);
3694
3800
  const expected = createHmac("sha256", key).update(body).digest("base64url");
3695
3801
  const actualBuffer = Buffer.from(signature);
3696
3802
  const expectedBuffer = Buffer.from(expected);
@@ -3897,11 +4003,11 @@ init_approval_v3();
3897
4003
 
3898
4004
  // src/core/capability/boundary-attestation-sign.ts
3899
4005
  import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
3900
- import { access, readFile as readFile5 } from "node:fs/promises";
4006
+ import { access, readFile as readFile6 } from "node:fs/promises";
3901
4007
  init_fingerprint2();
3902
4008
 
3903
4009
  // src/core/capability/attestation.ts
3904
- import path13 from "node:path";
4010
+ import path14 from "node:path";
3905
4011
  var BOUNDARY_ATTESTATION_VERSION = 1;
3906
4012
  var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
3907
4013
  var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
@@ -3969,7 +4075,7 @@ function validateContainedExecutionAttestation(value) {
3969
4075
  const record = value;
3970
4076
  const dockerSubstrate = record.dockerSubstrate;
3971
4077
  const dockerConfiguration = record.dockerConfiguration;
3972
- 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") {
4078
+ 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") {
3973
4079
  return false;
3974
4080
  }
3975
4081
  const probedAt = Date.parse(record.probedAt);
@@ -4039,7 +4145,7 @@ async function verifySignedBoundaryAttestation(params) {
4039
4145
  return record.attestation;
4040
4146
  }
4041
4147
  async function readSignedAttestationFile(filePath) {
4042
- return JSON.parse(await readFile5(filePath, "utf8"));
4148
+ return JSON.parse(await readFile6(filePath, "utf8"));
4043
4149
  }
4044
4150
 
4045
4151
  // src/core/capability/boundary-egress.ts
@@ -4208,13 +4314,13 @@ async function runWithBoundaryRunnable(target, params) {
4208
4314
  }
4209
4315
 
4210
4316
  // src/core/capability/boundary-session.ts
4211
- import path24 from "node:path";
4317
+ import path25 from "node:path";
4212
4318
 
4213
4319
  // src/services/egress-service.ts
4214
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
4215
- import { mkdir as mkdir4, readFile as readFile6, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
4320
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
4321
+ import { mkdir as mkdir5, readFile as readFile7, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
4216
4322
  import net from "node:net";
4217
- import path14 from "node:path";
4323
+ import path15 from "node:path";
4218
4324
  init_config_io();
4219
4325
  init_config();
4220
4326
 
@@ -4227,16 +4333,16 @@ function egressStatePaths(repoRoot, config) {
4227
4333
  const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
4228
4334
  return {
4229
4335
  stateDir,
4230
- pidPath: path14.join(stateDir, "egress-proxy.pid"),
4231
- statusPath: path14.join(stateDir, "egress-proxy.json")
4336
+ pidPath: path15.join(stateDir, "egress-proxy.pid"),
4337
+ statusPath: path15.join(stateDir, "egress-proxy.json")
4232
4338
  };
4233
4339
  }
4234
4340
  async function readStatusFile(statusPath) {
4235
- if (!existsSync4(statusPath)) {
4341
+ if (!existsSync5(statusPath)) {
4236
4342
  return null;
4237
4343
  }
4238
4344
  try {
4239
- const raw = JSON.parse(await readFile6(statusPath, "utf8"));
4345
+ const raw = JSON.parse(await readFile7(statusPath, "utf8"));
4240
4346
  if (typeof raw.pid !== "number") {
4241
4347
  return null;
4242
4348
  }
@@ -4254,9 +4360,9 @@ async function readStatusFile(statusPath) {
4254
4360
  async function isPortOpen(host, port) {
4255
4361
  return new Promise((resolve) => {
4256
4362
  const socket = net.createConnection({ host, port });
4257
- const finish = (open6) => {
4363
+ const finish = (open7) => {
4258
4364
  socket.destroy();
4259
- resolve(open6);
4365
+ resolve(open7);
4260
4366
  };
4261
4367
  socket.setTimeout(300);
4262
4368
  socket.on("connect", () => finish(true));
@@ -4267,7 +4373,7 @@ async function isPortOpen(host, port) {
4267
4373
  async function resolveLiveEgressStatus(repoRoot, config) {
4268
4374
  const { statusPath } = egressStatePaths(repoRoot, config);
4269
4375
  const statusCandidates = [statusPath];
4270
- const controlPlaneStatus = path14.join(configuredControlPlaneDir(config), "egress-proxy.json");
4376
+ const controlPlaneStatus = path15.join(configuredControlPlaneDir(config), "egress-proxy.json");
4271
4377
  if (!statusCandidates.includes(controlPlaneStatus)) {
4272
4378
  statusCandidates.push(controlPlaneStatus);
4273
4379
  }
@@ -4293,7 +4399,7 @@ function isProcessAlive(pid) {
4293
4399
  }
4294
4400
  }
4295
4401
  async function egressStatus(options = {}) {
4296
- const repoRoot = path14.resolve(options.targetDir ?? process.cwd());
4402
+ const repoRoot = path15.resolve(options.targetDir ?? process.cwd());
4297
4403
  const config = await loadConfigFile(repoRoot);
4298
4404
  const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
4299
4405
  const ownedRunning = Boolean(status);
@@ -4324,7 +4430,7 @@ init_config();
4324
4430
  import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
4325
4431
  import { createReadStream as createReadStream2 } from "node:fs";
4326
4432
  import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
4327
- import path19 from "node:path";
4433
+ import path20 from "node:path";
4328
4434
  init_fingerprint2();
4329
4435
  init_path_utils();
4330
4436
 
@@ -4503,7 +4609,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
4503
4609
  }
4504
4610
 
4505
4611
  // src/core/contained-execution/docker-policy.ts
4506
- import path15 from "node:path";
4612
+ import path16 from "node:path";
4507
4613
 
4508
4614
  // src/core/contained-execution/policy.ts
4509
4615
  var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
@@ -4632,7 +4738,7 @@ var PROXY_ENV_NAMES = [
4632
4738
  var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
4633
4739
  var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
4634
4740
  function assertSafeDockerPath(value, code) {
4635
- if (!path15.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
4741
+ if (!path16.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
4636
4742
  throw new ContainedExecutionFailureError(code);
4637
4743
  }
4638
4744
  }
@@ -4711,11 +4817,11 @@ init_path_utils();
4711
4817
  import { createHash as createHash7 } from "node:crypto";
4712
4818
  import { constants as fsConstants } from "node:fs";
4713
4819
  import {
4714
- chmod as chmod2,
4820
+ chmod as chmod3,
4715
4821
  lstat as lstat3,
4716
- mkdir as mkdir5,
4822
+ mkdir as mkdir6,
4717
4823
  mkdtemp,
4718
- open as open3,
4824
+ open as open4,
4719
4825
  opendir,
4720
4826
  readlink as readlink2,
4721
4827
  realpath,
@@ -4723,41 +4829,41 @@ import {
4723
4829
  symlink
4724
4830
  } from "node:fs/promises";
4725
4831
  import os from "node:os";
4726
- import path18 from "node:path";
4832
+ import path19 from "node:path";
4727
4833
 
4728
4834
  // src/core/transactional/file-tree.ts
4729
4835
  import { createHash as createHash6 } from "node:crypto";
4730
4836
  import { lstat as lstat2, readdir } from "node:fs/promises";
4731
- import path17 from "node:path";
4837
+ import path18 from "node:path";
4732
4838
 
4733
4839
  // src/core/transactional/file-tree-path.ts
4734
4840
  init_path_utils();
4735
- import path16 from "node:path";
4841
+ import path17 from "node:path";
4736
4842
  var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
4737
4843
  function validateRelativePath(relativePath) {
4738
4844
  if (!relativePath || relativePath.includes("\0")) {
4739
4845
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4740
4846
  }
4741
- if (path16.isAbsolute(relativePath)) {
4847
+ if (path17.isAbsolute(relativePath)) {
4742
4848
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4743
4849
  }
4744
4850
  if (isPathOutsideRoot(relativePath)) {
4745
4851
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4746
4852
  }
4747
- const normalized = path16.normalize(relativePath);
4748
- if (normalized === ".." || normalized.startsWith(`..${path16.sep}`)) {
4853
+ const normalized = path17.normalize(relativePath);
4854
+ if (normalized === ".." || normalized.startsWith(`..${path17.sep}`)) {
4749
4855
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4750
4856
  }
4751
4857
  }
4752
4858
  function joinRelativePath(root, relativePath) {
4753
4859
  validateRelativePath(relativePath);
4754
- return path16.join(canonicalPath(root), relativePath);
4860
+ return path17.join(canonicalPath(root), relativePath);
4755
4861
  }
4756
4862
  function isRootGitMetadataPath(relativePath) {
4757
- return relativePath === ".git" || relativePath.startsWith(`.git${path16.sep}`);
4863
+ return relativePath === ".git" || relativePath.startsWith(`.git${path17.sep}`);
4758
4864
  }
4759
4865
  function isNestedGitPath(relativePath) {
4760
- const segments = relativePath.split(path16.sep).filter(Boolean);
4866
+ const segments = relativePath.split(path17.sep).filter(Boolean);
4761
4867
  if (segments.length === 0) {
4762
4868
  return false;
4763
4869
  }
@@ -4924,7 +5030,7 @@ async function readPresentNode(absolutePath, counters) {
4924
5030
  async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
4925
5031
  assertWithinDeadline(deadlineMs);
4926
5032
  assertWithinQuotas(counters, quotas);
4927
- const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path17.resolve(resourceRoot);
5033
+ const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path18.resolve(resourceRoot);
4928
5034
  const dirInfo = await lstat2(absoluteDir);
4929
5035
  if (!dirInfo.isDirectory()) {
4930
5036
  throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
@@ -4950,7 +5056,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
4950
5056
  const names = await readdir(absoluteDir);
4951
5057
  for (const name of names) {
4952
5058
  assertWithinDeadline(deadlineMs);
4953
- const childRelative = relativeDir ? path17.join(relativeDir, name) : name;
5059
+ const childRelative = relativeDir ? path18.join(relativeDir, name) : name;
4954
5060
  validateRelativePath(childRelative);
4955
5061
  if (isNestedGitPath(childRelative)) {
4956
5062
  throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
@@ -4958,7 +5064,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
4958
5064
  if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
4959
5065
  continue;
4960
5066
  }
4961
- const childAbsolute = path17.join(absoluteDir, name);
5067
+ const childAbsolute = path18.join(absoluteDir, name);
4962
5068
  const childInfo = await lstat2(childAbsolute);
4963
5069
  if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
4964
5070
  await walkDirectory(
@@ -5069,16 +5175,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
5069
5175
  );
5070
5176
  }
5071
5177
  var productionDependencies = {
5072
- makeTempRoot: () => mkdtemp(path18.join(os.tmpdir(), "belay-contained-mirror-")),
5178
+ makeTempRoot: () => mkdtemp(path19.join(os.tmpdir(), "belay-contained-mirror-")),
5073
5179
  removeRoot: (root) => rm(root, { recursive: true, force: true }),
5074
5180
  now: () => Date.now()
5075
5181
  };
5076
5182
  function isAtOrWithin(root, target) {
5077
- const relative = path18.relative(root, target);
5078
- return relative === "" || !path18.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path18.sep}`);
5183
+ const relative = path19.relative(root, target);
5184
+ return relative === "" || !path19.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path19.sep}`);
5079
5185
  }
5080
5186
  function isGitMetadataRelativePath(relativePath) {
5081
- return relativePath.split(path18.sep).some((segment) => segment.toLowerCase() === ".git");
5187
+ return relativePath.split(path19.sep).some((segment) => segment.toLowerCase() === ".git");
5082
5188
  }
5083
5189
  function identityFromStats(stats) {
5084
5190
  return {
@@ -5160,7 +5266,7 @@ async function readStableFile(absolutePath, context) {
5160
5266
  if (before.nlink > 1n) {
5161
5267
  throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
5162
5268
  }
5163
- const source = await open3(absolutePath, safeReadFlags());
5269
+ const source = await open4(absolutePath, safeReadFlags());
5164
5270
  try {
5165
5271
  const opened = await source.stat({ bigint: true });
5166
5272
  assertRegularSingleLinkBigInt(opened);
@@ -5215,7 +5321,7 @@ function addMetadataRoots(context, directoryPath) {
5215
5321
  }
5216
5322
  }
5217
5323
  function pathMatchesRoots(absolutePath, roots) {
5218
- const lexical = path18.resolve(absolutePath);
5324
+ const lexical = path19.resolve(absolutePath);
5219
5325
  const canonical = canonicalPath(absolutePath);
5220
5326
  for (const root of roots) {
5221
5327
  if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
@@ -5225,7 +5331,7 @@ function pathMatchesRoots(absolutePath, roots) {
5225
5331
  return false;
5226
5332
  }
5227
5333
  function pathLexicallyMatchesRoots(absolutePath, roots) {
5228
- const lexical = path18.resolve(absolutePath);
5334
+ const lexical = path19.resolve(absolutePath);
5229
5335
  for (const root of roots) {
5230
5336
  if (isAtOrWithin(root, lexical)) {
5231
5337
  return true;
@@ -5246,11 +5352,11 @@ async function readSafeSymlink(absolutePath, context) {
5246
5352
  }
5247
5353
  const identity = identityFromStats(before);
5248
5354
  const target = await readlink2(absolutePath);
5249
- if (path18.isAbsolute(target)) {
5355
+ if (path19.isAbsolute(target)) {
5250
5356
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5251
5357
  }
5252
- const lexicalTarget = path18.resolve(path18.dirname(absolutePath), target);
5253
- if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path18.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5358
+ const lexicalTarget = path19.resolve(path19.dirname(absolutePath), target);
5359
+ if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path19.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5254
5360
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5255
5361
  }
5256
5362
  let resolvedTarget;
@@ -5286,7 +5392,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5286
5392
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5287
5393
  }
5288
5394
  const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
5289
- const directory = await open3(absoluteDirectory, directoryFlags);
5395
+ const directory = await open4(absoluteDirectory, directoryFlags);
5290
5396
  try {
5291
5397
  const opened = await directory.stat({ bigint: true });
5292
5398
  if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
@@ -5295,7 +5401,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5295
5401
  const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
5296
5402
  for await (const directoryEntry of entriesDirectory) {
5297
5403
  assertDeadline(context.deadlineMs, context.now);
5298
- const relativePath = relativeDirectory ? path18.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5404
+ const relativePath = relativeDirectory ? path19.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5299
5405
  const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
5300
5406
  const info = await lstat3(absolutePath);
5301
5407
  if (info.isSymbolicLink()) {
@@ -5379,7 +5485,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5379
5485
  }
5380
5486
  assertEntryIdentity(entry, identityFromStats(before));
5381
5487
  await context.beforeCopyOpen?.(sourcePath);
5382
- const source = await open3(sourcePath, safeReadFlags());
5488
+ const source = await open4(sourcePath, safeReadFlags());
5383
5489
  let destination;
5384
5490
  try {
5385
5491
  const opened = await source.stat({ bigint: true });
@@ -5389,8 +5495,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5389
5495
  if (entry.node.kind !== "file") {
5390
5496
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5391
5497
  }
5392
- await mkdir5(path18.dirname(destinationPath), { recursive: true, mode: 448 });
5393
- destination = await open3(
5498
+ await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
5499
+ destination = await open4(
5394
5500
  destinationPath,
5395
5501
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
5396
5502
  safePermissionMode(opened.mode)
@@ -5432,7 +5538,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5432
5538
  if (entry.node.kind !== "file") {
5433
5539
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5434
5540
  }
5435
- await chmod2(destinationPath, safePermissionMode(entry.node.mode));
5541
+ await chmod3(destinationPath, safePermissionMode(entry.node.mode));
5436
5542
  }
5437
5543
  async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
5438
5544
  const directoryEntries = [];
@@ -5441,7 +5547,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5441
5547
  const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
5442
5548
  const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
5443
5549
  if (entry.node.kind === "directory") {
5444
- await mkdir5(destinationPath, { recursive: true, mode: 448 });
5550
+ await mkdir6(destinationPath, { recursive: true, mode: 448 });
5445
5551
  directoryEntries.push(entry);
5446
5552
  continue;
5447
5553
  }
@@ -5453,7 +5559,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5453
5559
  if (current !== entry.node.target) {
5454
5560
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5455
5561
  }
5456
- await mkdir5(path18.dirname(destinationPath), { recursive: true, mode: 448 });
5562
+ await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
5457
5563
  await symlink(entry.node.target, destinationPath);
5458
5564
  continue;
5459
5565
  }
@@ -5463,12 +5569,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5463
5569
  if (entry.node.kind !== "directory") {
5464
5570
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5465
5571
  }
5466
- await chmod2(
5572
+ await chmod3(
5467
5573
  joinRelativePath(destinationRoot, entry.relativePath),
5468
5574
  safePermissionMode(entry.node.mode)
5469
5575
  );
5470
5576
  }
5471
- await chmod2(destinationRoot, 448);
5577
+ await chmod3(destinationRoot, 448);
5472
5578
  }
5473
5579
  async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
5474
5580
  const [after, copied] = await Promise.all([
@@ -5513,7 +5619,7 @@ async function cleanupOwnedRoot(root, dependencies) {
5513
5619
  }
5514
5620
  async function prepareWithDependencies(options, dependencies) {
5515
5621
  validateOptions(options);
5516
- const guestWorkspacePath = path18.resolve(options.sourceRoot);
5622
+ const guestWorkspacePath = path19.resolve(options.sourceRoot);
5517
5623
  const sourceRoot = canonicalPath(options.sourceRoot);
5518
5624
  const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
5519
5625
  if (pathMatchesRoots(sourceRoot, protectedRoots)) {
@@ -5521,7 +5627,7 @@ async function prepareWithDependencies(options, dependencies) {
5521
5627
  }
5522
5628
  const guestRoot = await dependencies.makeTempRoot();
5523
5629
  try {
5524
- await chmod2(guestRoot, 448);
5630
+ await chmod3(guestRoot, 448);
5525
5631
  const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
5526
5632
  const before = await buildSafeMirrorSnapshot(
5527
5633
  sourceRoot,
@@ -5549,7 +5655,7 @@ async function prepareWithDependencies(options, dependencies) {
5549
5655
  dependencies.now,
5550
5656
  before
5551
5657
  );
5552
- await chmod2(guestRoot, 448);
5658
+ await chmod3(guestRoot, 448);
5553
5659
  const lease = {
5554
5660
  sourceRoot,
5555
5661
  hostMirrorRoot: guestRoot,
@@ -5683,14 +5789,14 @@ async function digestFile(file) {
5683
5789
  return hash.digest("hex");
5684
5790
  }
5685
5791
  async function resolveConfiguredDockerSubstrate(params) {
5686
- if (!path19.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
5792
+ if (!path20.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
5687
5793
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
5688
5794
  }
5689
5795
  if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
5690
5796
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
5691
5797
  }
5692
5798
  const configuredSocket = params.host.slice("unix://".length);
5693
- if (!path19.isAbsolute(configuredSocket)) {
5799
+ if (!path20.isAbsolute(configuredSocket)) {
5694
5800
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
5695
5801
  }
5696
5802
  let binaryPath;
@@ -5951,7 +6057,7 @@ async function validatedGuestCwd(params) {
5951
6057
  const requiredExclusions = [
5952
6058
  ...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
5953
6059
  ];
5954
- if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path19.resolve(params.repoRoot) || !path19.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
6060
+ if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path20.resolve(params.repoRoot) || !path20.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
5955
6061
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5956
6062
  const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
5957
6063
  const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
@@ -5963,10 +6069,10 @@ async function validatedGuestCwd(params) {
5963
6069
  protectedRoots: requiredExclusions
5964
6070
  }))
5965
6071
  throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
5966
- const relative = path19.relative(params.mirror.guestWorkspacePath, path19.resolve(params.guestCwd));
6072
+ const relative = path20.relative(params.mirror.guestWorkspacePath, path20.resolve(params.guestCwd));
5967
6073
  let resolvedCwd;
5968
6074
  try {
5969
- resolvedCwd = await realpath2(path19.join(resolvedRoot, relative));
6075
+ resolvedCwd = await realpath2(path20.join(resolvedRoot, relative));
5970
6076
  } catch {
5971
6077
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5972
6078
  }
@@ -5974,7 +6080,7 @@ async function validatedGuestCwd(params) {
5974
6080
  if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
5975
6081
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5976
6082
  }
5977
- return path19.join(params.mirror.guestWorkspacePath, path19.relative(resolvedRoot, resolvedCwd));
6083
+ return path20.join(params.mirror.guestWorkspacePath, path20.relative(resolvedRoot, resolvedCwd));
5978
6084
  }
5979
6085
  function exactKeys(value, allowed) {
5980
6086
  const names = new Set(allowed);
@@ -6201,19 +6307,19 @@ async function executeContainedDocker(params) {
6201
6307
  init_path_utils();
6202
6308
  import { spawn as spawn3 } from "node:child_process";
6203
6309
  import os3 from "node:os";
6204
- import path21 from "node:path";
6310
+ import path22 from "node:path";
6205
6311
 
6206
6312
  // src/core/transactional/apply-observed-changes.ts
6207
- 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";
6313
+ 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";
6208
6314
  import os2 from "node:os";
6209
- import path20 from "node:path";
6315
+ import path21 from "node:path";
6210
6316
  var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
6211
6317
  var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
6212
6318
  var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
6213
6319
  async function chmodSafe(target, mode) {
6214
6320
  try {
6215
- const { chmod: chmod4 } = await import("node:fs/promises");
6216
- await chmod4(target, mode & 511);
6321
+ const { chmod: chmod5 } = await import("node:fs/promises");
6322
+ await chmod5(target, mode & 511);
6217
6323
  } catch {
6218
6324
  }
6219
6325
  }
@@ -6247,13 +6353,13 @@ async function copyPathPreservingType(source, target) {
6247
6353
  }
6248
6354
  }
6249
6355
  await removePathIfExists(target);
6250
- await mkdir6(path20.dirname(target), { recursive: true });
6356
+ await mkdir7(path21.dirname(target), { recursive: true });
6251
6357
  if (info.isSymbolicLink()) {
6252
6358
  await symlink2(await readlink3(source), target);
6253
6359
  return;
6254
6360
  }
6255
6361
  if (info.isDirectory() && !info.isSymbolicLink()) {
6256
- await mkdir6(target, { recursive: true, mode: info.mode & 511 });
6362
+ await mkdir7(target, { recursive: true, mode: info.mode & 511 });
6257
6363
  return;
6258
6364
  }
6259
6365
  if (!info.isFile()) {
@@ -6263,12 +6369,12 @@ async function copyPathPreservingType(source, target) {
6263
6369
  await chmodSafe(target, info.mode);
6264
6370
  }
6265
6371
  async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
6266
- const segments = relativePath.split(path20.sep).filter(Boolean);
6372
+ const segments = relativePath.split(path21.sep).filter(Boolean);
6267
6373
  if (segments.length <= 1) {
6268
6374
  return;
6269
6375
  }
6270
6376
  for (let index = 1; index < segments.length; index++) {
6271
- const prefix = segments.slice(0, index).join(path20.sep);
6377
+ const prefix = segments.slice(0, index).join(path21.sep);
6272
6378
  const absolute = joinRelativePath(targetRoot, prefix);
6273
6379
  let info;
6274
6380
  try {
@@ -6288,8 +6394,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
6288
6394
  }
6289
6395
  }
6290
6396
  async function restorePathFromBackup(backupPath, target, rollbackRoot) {
6291
- const stagingRoot = await mkdtemp3(path20.join(rollbackRoot, "restore-"));
6292
- const staged = path20.join(stagingRoot, "node");
6397
+ const stagingRoot = await mkdtemp3(path21.join(rollbackRoot, "restore-"));
6398
+ const staged = path21.join(stagingRoot, "node");
6293
6399
  try {
6294
6400
  await copyPathPreservingType(backupPath, staged);
6295
6401
  await copyPathPreservingType(staged, target);
@@ -6373,7 +6479,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
6373
6479
  if (change.before.kind !== "directory") {
6374
6480
  await removePathIfExists(target);
6375
6481
  }
6376
- await mkdir6(target, { recursive: true, mode: change.after.mode & 511 });
6482
+ await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
6377
6483
  await chmodSafe(target, change.after.mode);
6378
6484
  return;
6379
6485
  }
@@ -6392,7 +6498,7 @@ async function applyObservedChanges(params) {
6392
6498
  await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
6393
6499
  await assertTargetMatches(targetRoot, change);
6394
6500
  }
6395
- const backupRoot = await mkdtemp3(path20.join(os2.tmpdir(), "belay-tx-rollback-"));
6501
+ const backupRoot = await mkdtemp3(path21.join(os2.tmpdir(), "belay-tx-rollback-"));
6396
6502
  const rollbackActions = [];
6397
6503
  let mutationAttempted = false;
6398
6504
  let resourceIdentityChanged = false;
@@ -6411,12 +6517,12 @@ async function applyObservedChanges(params) {
6411
6517
  targetExists = false;
6412
6518
  }
6413
6519
  if (targetExists) {
6414
- const backupPath = path20.join(
6520
+ const backupPath = path21.join(
6415
6521
  backupRoot,
6416
6522
  String(rollbackActions.length),
6417
6523
  change.relativePath
6418
6524
  );
6419
- await mkdir6(path20.dirname(backupPath), { recursive: true });
6525
+ await mkdir7(path21.dirname(backupPath), { recursive: true });
6420
6526
  await copyPathPreservingType(target, backupPath);
6421
6527
  await assertParentChainSafe(targetRoot, change.relativePath);
6422
6528
  rollbackActions.push({
@@ -6515,7 +6621,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
6515
6621
  if (ignoreRoots.length === 0) {
6516
6622
  return false;
6517
6623
  }
6518
- const absolutePath = canonicalPath(path21.join(repoRoot, relativePath));
6624
+ const absolutePath = canonicalPath(path22.join(repoRoot, relativePath));
6519
6625
  return ignoreRoots.some(
6520
6626
  (root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
6521
6627
  );
@@ -6553,7 +6659,7 @@ async function isDirtyWorktree(repoRoot, options) {
6553
6659
  }
6554
6660
  async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
6555
6661
  const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
6556
- const worktreePath = await mkdtemp6(path21.join(os3.tmpdir(), "belay-tx-"));
6662
+ const worktreePath = await mkdtemp6(path22.join(os3.tmpdir(), "belay-tx-"));
6557
6663
  await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
6558
6664
  return {
6559
6665
  worktreePath,
@@ -6572,14 +6678,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
6572
6678
  }
6573
6679
  function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
6574
6680
  const resolvedCwd = canonicalPath(cwd);
6575
- const relative = path21.relative(canonicalPath(repoRoot), resolvedCwd);
6576
- if (isPathOutsideRoot(relative) || path21.isAbsolute(relative)) {
6681
+ const relative = path22.relative(canonicalPath(repoRoot), resolvedCwd);
6682
+ if (isPathOutsideRoot(relative) || path22.isAbsolute(relative)) {
6577
6683
  return worktreePath;
6578
6684
  }
6579
6685
  if (relative === "") {
6580
6686
  return worktreePath;
6581
6687
  }
6582
- return path21.join(worktreePath, relative);
6688
+ return path22.join(worktreePath, relative);
6583
6689
  }
6584
6690
  function runShellCommand(command, cwd, timeoutMs) {
6585
6691
  return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
@@ -6644,7 +6750,7 @@ import { spawn as spawn4 } from "node:child_process";
6644
6750
  import { randomUUID as randomUUID3 } from "node:crypto";
6645
6751
 
6646
6752
  // src/core/capability/boundary-grant-materialize.ts
6647
- import path22 from "node:path";
6753
+ import path23 from "node:path";
6648
6754
 
6649
6755
  // src/core/glob.ts
6650
6756
  init_approval();
@@ -6804,7 +6910,7 @@ function grantMatchesRequest(grant, request) {
6804
6910
  var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
6805
6911
  var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
6806
6912
  function resolveCapabilityPath(targetPath, cwd) {
6807
- const joined = path22.isAbsolute(targetPath) ? targetPath : path22.join(cwd, targetPath);
6913
+ const joined = path23.isAbsolute(targetPath) ? targetPath : path23.join(cwd, targetPath);
6808
6914
  return canonicalPath(joined);
6809
6915
  }
6810
6916
  function isPathWithinBoundaryMount(request) {
@@ -6879,7 +6985,7 @@ function materializeContainerBoundaryGrant(request, params) {
6879
6985
 
6880
6986
  // src/core/capability/boundary-workspace-mount.ts
6881
6987
  init_path_utils();
6882
- import path23 from "node:path";
6988
+ import path24 from "node:path";
6883
6989
  var HOST_PATH_ENV_VARS = [
6884
6990
  "BELAY_EGRESS_REPO_ROOT",
6885
6991
  "BELAY_JUDGE_BROKER_REPO_ROOT",
@@ -6904,23 +7010,23 @@ function validateWorkspaceMount(mount) {
6904
7010
  if (mount.cwdRelative.includes("\0")) {
6905
7011
  throw new Error("boundary_workspace_mount_invalid_cwd");
6906
7012
  }
6907
- const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7013
+ const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6908
7014
  if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
6909
7015
  throw new Error("boundary_workspace_mount_invalid_cwd");
6910
7016
  }
6911
- if (path23.posix.isAbsolute(normalizedRelative)) {
7017
+ if (path24.posix.isAbsolute(normalizedRelative)) {
6912
7018
  throw new Error("boundary_workspace_mount_invalid_cwd");
6913
7019
  }
6914
7020
  }
6915
7021
  function resolveGuestWorkdir(mount) {
6916
7022
  validateWorkspaceMount(mount);
6917
7023
  const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
6918
- const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
7024
+ const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6919
7025
  if (normalizedRelative === "." || normalizedRelative === "") {
6920
7026
  return guestTargetRoot;
6921
7027
  }
6922
7028
  const segments = normalizedRelative.split("/").filter(Boolean);
6923
- return path23.posix.join(guestTargetRoot, ...segments);
7029
+ return path24.posix.join(guestTargetRoot, ...segments);
6924
7030
  }
6925
7031
  function buildWorkspaceMountSpec(mount) {
6926
7032
  validateWorkspaceMount(mount);
@@ -7211,7 +7317,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
7211
7317
  }
7212
7318
  function boundaryAttestationPath(repoRoot, config) {
7213
7319
  const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
7214
- return path24.isAbsolute(rel) ? rel : path24.join(repoRoot, rel);
7320
+ return path25.isAbsolute(rel) ? rel : path25.join(repoRoot, rel);
7215
7321
  }
7216
7322
  async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
7217
7323
  try {
@@ -7316,7 +7422,7 @@ init_capability_request_hash();
7316
7422
 
7317
7423
  // src/core/capability/gate-policy-shadow.ts
7318
7424
  init_config();
7319
- import path30 from "node:path";
7425
+ import path31 from "node:path";
7320
7426
 
7321
7427
  // src/core/effect-ir/audit.ts
7322
7428
  init_fingerprint2();
@@ -7410,8 +7516,8 @@ function evidenceRank(level) {
7410
7516
  }
7411
7517
 
7412
7518
  // src/core/effect-ir/package-exec.ts
7413
- import { existsSync as existsSync5, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
7414
- import path25 from "node:path";
7519
+ import { existsSync as existsSync6, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
7520
+ import path26 from "node:path";
7415
7521
 
7416
7522
  // src/core/network-endpoint.ts
7417
7523
  var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "ssh:", "git:"]);
@@ -7702,18 +7808,18 @@ function classifyPackageAcquisitionSpec(spec) {
7702
7808
  return { kind: "registry" };
7703
7809
  }
7704
7810
  function resolveLocalBin(binName, cwd, repoRoot) {
7705
- const base = path25.basename(binName);
7811
+ const base = path26.basename(binName);
7706
7812
  if (!base || base === "." || base === "..") {
7707
7813
  return null;
7708
7814
  }
7709
- let current = path25.resolve(cwd);
7710
- const stop = path25.resolve(repoRoot);
7815
+ let current = path26.resolve(cwd);
7816
+ const stop = path26.resolve(repoRoot);
7711
7817
  if (!pathWithinRoot(stop, current)) {
7712
7818
  return null;
7713
7819
  }
7714
7820
  while (true) {
7715
- const candidate = path25.join(current, "node_modules", ".bin", base);
7716
- if (existsSync5(candidate)) {
7821
+ const candidate = path26.join(current, "node_modules", ".bin", base);
7822
+ if (existsSync6(candidate)) {
7717
7823
  try {
7718
7824
  const resolvedCandidate = realpathSync3.native(candidate);
7719
7825
  if (!pathWithinRoot(stop, resolvedCandidate) || !statSync2(resolvedCandidate).isFile()) {
@@ -7727,7 +7833,7 @@ function resolveLocalBin(binName, cwd, repoRoot) {
7727
7833
  if (current === stop) {
7728
7834
  break;
7729
7835
  }
7730
- const parent = path25.dirname(current);
7836
+ const parent = path26.dirname(current);
7731
7837
  if (parent === current) {
7732
7838
  break;
7733
7839
  }
@@ -7922,7 +8028,7 @@ init_path_utils();
7922
8028
 
7923
8029
  // src/core/verdict/containment.ts
7924
8030
  init_git_resource_identity();
7925
- import path26 from "node:path";
8031
+ import path27 from "node:path";
7926
8032
  init_path_utils();
7927
8033
 
7928
8034
  // src/core/verdict/persistent-paths.ts
@@ -7942,7 +8048,7 @@ function expandHome(token) {
7942
8048
  if (!home) {
7943
8049
  return token;
7944
8050
  }
7945
- return token === "~" ? home : path26.join(home, token.slice(2));
8051
+ return token === "~" ? home : path27.join(home, token.slice(2));
7946
8052
  }
7947
8053
  return token;
7948
8054
  }
@@ -7954,10 +8060,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
7954
8060
  return null;
7955
8061
  }
7956
8062
  const expanded = expandHome(token);
7957
- if (path26.isAbsolute(expanded)) {
8063
+ if (path27.isAbsolute(expanded)) {
7958
8064
  return canonicalPath(expanded);
7959
8065
  }
7960
- return canonicalPath(path26.resolve(trustedCwd, expanded));
8066
+ return canonicalPath(path27.resolve(trustedCwd, expanded));
7961
8067
  }
7962
8068
  function isGitPath(resolvedPath, repoRoot) {
7963
8069
  if (isGitMetadataPath(resolvedPath, repoRoot)) {
@@ -8074,20 +8180,20 @@ function parseTier1Json(raw) {
8074
8180
  init_judge_runtime_config();
8075
8181
 
8076
8182
  // src/core/verdict/judge-session-kill-switch.ts
8077
- import { existsSync as existsSync6 } from "node:fs";
8078
- import { mkdir as mkdir7, readFile as readFile7, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
8079
- import path27 from "node:path";
8183
+ import { existsSync as existsSync7 } from "node:fs";
8184
+ import { mkdir as mkdir8, readFile as readFile8, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
8185
+ import path28 from "node:path";
8080
8186
  var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
8081
8187
  function judgeSessionKillSwitchPath(stateDir) {
8082
- return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
8188
+ return path28.join(stateDir, JUDGE_SESSION_KILL_FILE);
8083
8189
  }
8084
8190
  async function readJudgeSessionKillSwitch(stateDir) {
8085
8191
  const filePath = judgeSessionKillSwitchPath(stateDir);
8086
- if (!existsSync6(filePath)) {
8192
+ if (!existsSync7(filePath)) {
8087
8193
  return null;
8088
8194
  }
8089
8195
  try {
8090
- const raw = JSON.parse(await readFile7(filePath, "utf8"));
8196
+ const raw = JSON.parse(await readFile8(filePath, "utf8"));
8091
8197
  return raw.triggered === true ? raw : null;
8092
8198
  } catch {
8093
8199
  return null;
@@ -8098,7 +8204,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
8098
8204
  return record?.triggered === true;
8099
8205
  }
8100
8206
  async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
8101
- await mkdir7(stateDir, { recursive: true, mode: 448 });
8207
+ await mkdir8(stateDir, { recursive: true, mode: 448 });
8102
8208
  const record = {
8103
8209
  triggered: true,
8104
8210
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8259,10 +8365,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
8259
8365
 
8260
8366
  // src/core/verdict/judge-broker-service.ts
8261
8367
  import { spawn as spawn6 } from "node:child_process";
8262
- import { existsSync as existsSync7 } from "node:fs";
8263
- import { unlink as unlink5 } from "node:fs/promises";
8368
+ import { existsSync as existsSync8 } from "node:fs";
8369
+ import { unlink as unlink6 } from "node:fs/promises";
8264
8370
  import net2 from "node:net";
8265
- import path28 from "node:path";
8371
+ import path29 from "node:path";
8266
8372
  import { fileURLToPath } from "node:url";
8267
8373
 
8268
8374
  // src/core/verdict/judge-cli.ts
@@ -8808,10 +8914,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
8808
8914
  function judgeBrokerPaths(stateDir) {
8809
8915
  return {
8810
8916
  stateDir,
8811
- socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
8812
- statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
8813
- pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
8814
- sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
8917
+ socketPath: path29.join(stateDir, JUDGE_BROKER_SOCKET),
8918
+ statusPath: path29.join(stateDir, JUDGE_BROKER_STATUS),
8919
+ pidPath: path29.join(stateDir, JUDGE_BROKER_PID),
8920
+ sessionConfigPath: path29.join(stateDir, JUDGE_BROKER_SESSION)
8815
8921
  };
8816
8922
  }
8817
8923
  function daemonScriptPath() {
@@ -8829,12 +8935,12 @@ function useInProcessBroker(env = process.env) {
8829
8935
  return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
8830
8936
  }
8831
8937
  async function readBrokerStatus(statusPath) {
8832
- if (!existsSync7(statusPath)) {
8938
+ if (!existsSync8(statusPath)) {
8833
8939
  return null;
8834
8940
  }
8835
8941
  try {
8836
- const { readFile: readFile17 } = await import("node:fs/promises");
8837
- const raw = JSON.parse(await readFile17(statusPath, "utf8"));
8942
+ const { readFile: readFile18 } = await import("node:fs/promises");
8943
+ const raw = JSON.parse(await readFile18(statusPath, "utf8"));
8838
8944
  if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
8839
8945
  return null;
8840
8946
  }
@@ -8844,13 +8950,13 @@ async function readBrokerStatus(statusPath) {
8844
8950
  }
8845
8951
  }
8846
8952
  async function readBrokerSessionConfig(sessionConfigPath) {
8847
- if (!existsSync7(sessionConfigPath)) {
8953
+ if (!existsSync8(sessionConfigPath)) {
8848
8954
  return null;
8849
8955
  }
8850
8956
  try {
8851
- const { readFile: readFile17 } = await import("node:fs/promises");
8957
+ const { readFile: readFile18 } = await import("node:fs/promises");
8852
8958
  const raw = JSON.parse(
8853
- await readFile17(sessionConfigPath, "utf8")
8959
+ await readFile18(sessionConfigPath, "utf8")
8854
8960
  );
8855
8961
  return normalizeJudgeSessionConfig({ ...raw, enabled: true });
8856
8962
  } catch {
@@ -8941,7 +9047,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8941
9047
  const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
8942
9048
  const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
8943
9049
  const status = await readBrokerStatus(paths.statusPath);
8944
- if (status && isProcessAlive2(status.pid) && existsSync7(paths.socketPath) && !configChanged) {
9050
+ if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
8945
9051
  await writeBrokerSessionConfig(paths, brokerSessionConfig);
8946
9052
  return paths;
8947
9053
  }
@@ -8963,7 +9069,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8963
9069
  child.unref();
8964
9070
  const deadline = Date.now() + sessionConfig.connectTimeoutMs;
8965
9071
  while (Date.now() < deadline) {
8966
- if (existsSync7(paths.socketPath)) {
9072
+ if (existsSync8(paths.socketPath)) {
8967
9073
  const live = await readBrokerStatus(paths.statusPath);
8968
9074
  if (live && isProcessAlive2(live.pid)) {
8969
9075
  return paths;
@@ -8980,8 +9086,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
8980
9086
  paths.pidPath,
8981
9087
  paths.sessionConfigPath
8982
9088
  ]) {
8983
- if (existsSync7(artifact)) {
8984
- await unlink5(artifact).catch(() => void 0);
9089
+ if (existsSync8(artifact)) {
9090
+ await unlink6(artifact).catch(() => void 0);
8985
9091
  }
8986
9092
  }
8987
9093
  }
@@ -9477,9 +9583,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
9477
9583
  }
9478
9584
 
9479
9585
  // src/core/capability/gate-shadow-ratchet.ts
9480
- import { existsSync as existsSync8 } from "node:fs";
9481
- import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
9482
- import path29 from "node:path";
9586
+ import { existsSync as existsSync9 } from "node:fs";
9587
+ import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
9588
+ import path30 from "node:path";
9483
9589
  var DEFAULT_STATE = {
9484
9590
  version: 1,
9485
9591
  policyJudgeComparisons: 0,
@@ -9488,15 +9594,15 @@ var DEFAULT_STATE = {
9488
9594
  updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
9489
9595
  };
9490
9596
  function ratchetPath(stateDir) {
9491
- return path29.join(stateDir, "gate-shadow-ratchet.json");
9597
+ return path30.join(stateDir, "gate-shadow-ratchet.json");
9492
9598
  }
9493
9599
  async function loadState(stateDir) {
9494
9600
  const filePath = ratchetPath(stateDir);
9495
- if (!existsSync8(filePath)) {
9601
+ if (!existsSync9(filePath)) {
9496
9602
  return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
9497
9603
  }
9498
9604
  try {
9499
- const raw = JSON.parse(await readFile8(filePath, "utf8"));
9605
+ const raw = JSON.parse(await readFile9(filePath, "utf8"));
9500
9606
  return {
9501
9607
  ...DEFAULT_STATE,
9502
9608
  ...raw,
@@ -9507,7 +9613,7 @@ async function loadState(stateDir) {
9507
9613
  }
9508
9614
  }
9509
9615
  async function saveState(stateDir, state) {
9510
- await mkdir8(stateDir, { recursive: true, mode: 448 });
9616
+ await mkdir9(stateDir, { recursive: true, mode: 448 });
9511
9617
  await writeFile5(
9512
9618
  ratchetPath(stateDir),
9513
9619
  `${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
@@ -9630,7 +9736,7 @@ function scheduleGateShadowAudit(params) {
9630
9736
  async function runGatePolicyShadowComparison(params) {
9631
9737
  const judge = params.config.judge;
9632
9738
  const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
9633
- const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
9739
+ const stateDir = params.stateDir ?? belayStateDir(params.config, path31.join(params.repoRoot, ".belay"));
9634
9740
  const transport = await evaluateWithJudgeTransport(
9635
9741
  {
9636
9742
  prompt: buildTier1Prompt(params.command),
@@ -9920,15 +10026,15 @@ async function loadClassifierAuthorization(params) {
9920
10026
  }
9921
10027
 
9922
10028
  // src/core/capability/allowlist.ts
9923
- import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
10029
+ import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
9924
10030
  init_config();
9925
10031
  init_path_utils();
9926
- import path31 from "node:path";
10032
+ import path32 from "node:path";
9927
10033
  function fsScopeAllowlistPath(config, repoLocalStateDir) {
9928
- return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
10034
+ return path32.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
9929
10035
  }
9930
10036
  function loadFsScopeAllowlistSync(filePath) {
9931
- if (!existsSync9(filePath)) {
10037
+ if (!existsSync10(filePath)) {
9932
10038
  return { version: 1, paths: [] };
9933
10039
  }
9934
10040
  const raw = JSON.parse(readFileSync3(filePath, "utf8"));
@@ -10020,7 +10126,7 @@ function checkGatedActionLimits(action) {
10020
10126
  // src/core/capability/paths.ts
10021
10127
  init_path_utils();
10022
10128
  init_shell_tokenizer();
10023
- import path32 from "node:path";
10129
+ import path33 from "node:path";
10024
10130
  function applyPatchTargets(patch) {
10025
10131
  const targets = [];
10026
10132
  for (const line of patch.split("\n")) {
@@ -10085,7 +10191,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10085
10191
  const paths = /* @__PURE__ */ new Set();
10086
10192
  const filePath = extractToolFilePath(payload);
10087
10193
  if (filePath) {
10088
- const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
10194
+ const resolved = path33.isAbsolute(filePath) ? filePath : path33.resolve(cwd, filePath);
10089
10195
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10090
10196
  paths.add(resolved);
10091
10197
  }
@@ -10095,7 +10201,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10095
10201
  const patch = extractToolPatch(payload);
10096
10202
  if (patch) {
10097
10203
  for (const target of applyPatchTargets(patch)) {
10098
- const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
10204
+ const resolved = path33.isAbsolute(target) ? target : path33.resolve(cwd, target);
10099
10205
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10100
10206
  paths.add(resolved);
10101
10207
  }
@@ -10149,7 +10255,7 @@ function policyReasonToLegacyReason(decision) {
10149
10255
  // src/core/capability/policy-engine.ts
10150
10256
  init_git_resource_identity();
10151
10257
  import { createHash as createHash10 } from "node:crypto";
10152
- import path33 from "node:path";
10258
+ import path34 from "node:path";
10153
10259
  init_path_utils();
10154
10260
  init_shell_tokenizer();
10155
10261
  init_grant();
@@ -10441,7 +10547,7 @@ function hashSession(value) {
10441
10547
  return createHash10("sha256").update(value).digest("hex").slice(0, 16);
10442
10548
  }
10443
10549
  function resolveCapabilityPath2(targetPath, cwd) {
10444
- const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
10550
+ const joined = path34.isAbsolute(targetPath) ? targetPath : path34.join(cwd, targetPath);
10445
10551
  return canonicalPath(joined);
10446
10552
  }
10447
10553
  function actionForFileMutation(analysis) {
@@ -10520,7 +10626,7 @@ function isRepoLocalPackageExec(request) {
10520
10626
  return false;
10521
10627
  }
10522
10628
  const commandPath = canonicalPath(request.resource.command);
10523
- return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10629
+ return path34.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10524
10630
  }
10525
10631
  function isRepoLocalRoutineWrite(request, sensitivePaths) {
10526
10632
  if (!isRepoLocalShellLocation(request)) {
@@ -10768,6 +10874,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
10768
10874
  );
10769
10875
  return { request, decision };
10770
10876
  }
10877
+ function buildFileReadCapabilityRequest(analysis) {
10878
+ return {
10879
+ version: CAPABILITY_REQUEST_VERSION,
10880
+ principal: {
10881
+ adapter: analysis.adapter,
10882
+ repoRoot: analysis.repoRoot,
10883
+ sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
10884
+ },
10885
+ action: "fs.read",
10886
+ resource: { kind: "path", path: analysis.resolvedPath },
10887
+ context: {
10888
+ cwd: analysis.cwd,
10889
+ inputFingerprint: analysis.inputFingerprint,
10890
+ hookKind: analysis.hookKind,
10891
+ analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
10892
+ },
10893
+ evidence: {
10894
+ level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
10895
+ signals: [...analysis.signals, "effect.fs_read"]
10896
+ }
10897
+ };
10898
+ }
10899
+ function evaluateFileReadPolicy(analysis, config, auth) {
10900
+ const request = buildFileReadCapabilityRequest(analysis);
10901
+ const enriched = enrichAuthWithMaterializedGrants(request, config, {
10902
+ ...auth,
10903
+ sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
10904
+ });
10905
+ const decision = getDefaultPolicyEngine().evaluate(
10906
+ request,
10907
+ buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
10908
+ );
10909
+ return { request, decision };
10910
+ }
10771
10911
 
10772
10912
  // src/core/capability/reasons.ts
10773
10913
  var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
@@ -10784,15 +10924,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
10784
10924
  }
10785
10925
 
10786
10926
  // src/core/capability/trusted-workspace-roots.ts
10787
- import { existsSync as existsSync10, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10927
+ import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10788
10928
  init_config();
10789
10929
  init_path_utils();
10790
- import path34 from "node:path";
10930
+ import path35 from "node:path";
10791
10931
  function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
10792
- return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10932
+ return path35.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10793
10933
  }
10794
10934
  function loadTrustedWorkspaceRootsSync(filePath) {
10795
- if (!existsSync10(filePath)) {
10935
+ if (!existsSync11(filePath)) {
10796
10936
  return { version: 1, roots: [] };
10797
10937
  }
10798
10938
  const raw = JSON.parse(readFileSync4(filePath, "utf8"));
@@ -10818,7 +10958,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
10818
10958
  const source = record.source === "approval" ? "approval" : void 0;
10819
10959
  return [
10820
10960
  {
10821
- path: path34.resolve(record.path),
10961
+ path: path35.resolve(record.path),
10822
10962
  approvedAt,
10823
10963
  approvalId,
10824
10964
  ...source ? { source } : {}
@@ -10847,7 +10987,7 @@ function isDirectoryPath(targetPath) {
10847
10987
  function isBroadTrustedWorkspaceRoot(targetPath) {
10848
10988
  const home = process.env.HOME ?? process.env.USERPROFILE;
10849
10989
  const root = normalizeTrustedWorkspaceRootPath(targetPath);
10850
- if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
10990
+ if (root === normalizeTrustedWorkspaceRootPath(path35.parse(root).root)) {
10851
10991
  return true;
10852
10992
  }
10853
10993
  if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
@@ -10871,7 +11011,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
10871
11011
  return false;
10872
11012
  }
10873
11013
  return HOME_HIGH_STAKES_SEGMENTS.some(
10874
- (segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
11014
+ (segment) => pathWithinRoot(path35.join(homeRoot, segment), normalized)
10875
11015
  );
10876
11016
  }
10877
11017
  function validateTrustedWorkspaceRootCandidate(params) {
@@ -10908,7 +11048,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
10908
11048
  init_config_layers();
10909
11049
 
10910
11050
  // src/core/contained-execution/eligibility.ts
10911
- import path35 from "node:path";
11051
+ import path36 from "node:path";
10912
11052
  init_path_utils();
10913
11053
  var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10914
11054
  "command_substitution",
@@ -10925,7 +11065,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10925
11065
  ]);
10926
11066
  function isContainedUnknownExecutionEligible(config, action, result) {
10927
11067
  const contained = config.sandbox.containedExecution;
10928
- 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) {
11068
+ if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path36.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
10929
11069
  return false;
10930
11070
  }
10931
11071
  const plan = result.effectPlan;
@@ -11205,7 +11345,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11205
11345
  }
11206
11346
 
11207
11347
  // src/core/classify-tool.ts
11208
- import path50 from "node:path";
11348
+ import path51 from "node:path";
11209
11349
  init_fingerprint2();
11210
11350
  init_path_utils();
11211
11351
  init_scrub();
@@ -11325,13 +11465,13 @@ function worstEffectDecision(decisions) {
11325
11465
 
11326
11466
  // src/core/effect-ir/shell-lower.ts
11327
11467
  init_shell_tokenizer();
11328
- import path49 from "node:path";
11468
+ import path50 from "node:path";
11329
11469
 
11330
11470
  // src/core/verdict/docker-compose-run.ts
11331
- import path37 from "node:path";
11471
+ import path38 from "node:path";
11332
11472
 
11333
11473
  // src/core/verdict/recursive-invocation.ts
11334
- import path36 from "node:path";
11474
+ import path37 from "node:path";
11335
11475
  var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
11336
11476
  var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
11337
11477
  var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
@@ -11393,7 +11533,7 @@ var OSASCRIPT_PROFILE = {
11393
11533
  attachedValuePrefixes: []
11394
11534
  };
11395
11535
  function normalizeInterpreter(value) {
11396
- return path36.basename(value);
11536
+ return path37.basename(value);
11397
11537
  }
11398
11538
  function scriptResult(interpreter, token) {
11399
11539
  if (!token) {
@@ -11623,7 +11763,7 @@ function parseOptions(words, start, options) {
11623
11763
  function decodeDockerComposeRun(tokens) {
11624
11764
  if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11625
11765
  const words = tokens.filter((token) => token.kind === "word");
11626
- const head = path37.basename(words[0]?.value ?? "");
11766
+ const head = path38.basename(words[0]?.value ?? "");
11627
11767
  let index;
11628
11768
  if (head === "docker-compose") {
11629
11769
  index = 1;
@@ -11664,7 +11804,7 @@ function decodeDockerComposeRun(tokens) {
11664
11804
  }
11665
11805
 
11666
11806
  // src/core/verdict/egress-classify.ts
11667
- import path38 from "node:path";
11807
+ import path39 from "node:path";
11668
11808
  var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11669
11809
  "aws",
11670
11810
  "curl",
@@ -11713,7 +11853,7 @@ function isEgressToolHead(head) {
11713
11853
  return EGRESS_TOOL_HEADS.has(head);
11714
11854
  }
11715
11855
  function decodeEgressEffects(params) {
11716
- const head = path38.basename(params.tokens[0] ?? "");
11856
+ const head = path39.basename(params.tokens[0] ?? "");
11717
11857
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11718
11858
  return null;
11719
11859
  }
@@ -11721,7 +11861,7 @@ function decodeEgressEffects(params) {
11721
11861
  const provenance = { segment: params.segment };
11722
11862
  const requirements = [];
11723
11863
  for (const file of decoded.files) {
11724
- const resolved = path38.resolve(params.cwd, expandHome2(file));
11864
+ const resolved = path39.resolve(params.cwd, expandHome2(file));
11725
11865
  requirements.push(
11726
11866
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11727
11867
  "egress.explicit_file_read"
@@ -11743,7 +11883,7 @@ function decodeEgressEffects(params) {
11743
11883
  if (file === "-") {
11744
11884
  continue;
11745
11885
  }
11746
- const resolved = path38.resolve(params.cwd, expandHome2(file));
11886
+ const resolved = path39.resolve(params.cwd, expandHome2(file));
11747
11887
  if (resolved === "/dev/null") {
11748
11888
  continue;
11749
11889
  }
@@ -12221,13 +12361,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12221
12361
  if (head === "wget" && !explicitOutput) {
12222
12362
  outputFiles.push(
12223
12363
  ...endpointOutputNames.map(
12224
- (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12364
+ (name) => outputDirectory ? path39.join(outputDirectory, name) : name
12225
12365
  )
12226
12366
  );
12227
12367
  } else if (head === "curl" && remoteNameOutput) {
12228
12368
  outputFiles.push(
12229
12369
  ...endpointOutputNames.map(
12230
- (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12370
+ (name) => outputDirectory ? path39.join(outputDirectory, name) : name
12231
12371
  )
12232
12372
  );
12233
12373
  }
@@ -12241,7 +12381,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12241
12381
  outputFiles: [
12242
12382
  ...new Set(
12243
12383
  outputFiles.map(
12244
- (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
12384
+ (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path39.isAbsolute(file) ? path39.join(outputDirectory, file) : file
12245
12385
  )
12246
12386
  )
12247
12387
  ],
@@ -12256,7 +12396,7 @@ function remoteOutputName(spec) {
12256
12396
  } catch {
12257
12397
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
12258
12398
  }
12259
- const name = path38.posix.basename(pathname);
12399
+ const name = path39.posix.basename(pathname);
12260
12400
  return name && name !== "/" ? name : "index.html";
12261
12401
  }
12262
12402
  function decodeGhGrammar(tokens) {
@@ -12421,7 +12561,7 @@ function expandHome2(value) {
12421
12561
  return process.env.HOME ?? value;
12422
12562
  }
12423
12563
  if (value.startsWith("~/")) {
12424
- return path38.join(process.env.HOME ?? "~", value.slice(2));
12564
+ return path39.join(process.env.HOME ?? "~", value.slice(2));
12425
12565
  }
12426
12566
  return value;
12427
12567
  }
@@ -12440,7 +12580,7 @@ function requirement(tag, action, resource, segment, signals) {
12440
12580
  }
12441
12581
 
12442
12582
  // src/core/verdict/git-classifier.ts
12443
- import path39 from "node:path";
12583
+ import path40 from "node:path";
12444
12584
  init_shell_tokenizer();
12445
12585
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
12446
12586
  "--copy",
@@ -12536,7 +12676,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
12536
12676
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
12537
12677
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
12538
12678
  function isGitExecutable(token) {
12539
- return path39.basename(token) === "git";
12679
+ return path40.basename(token) === "git";
12540
12680
  }
12541
12681
  function takesValue(flag) {
12542
12682
  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=");
@@ -12564,7 +12704,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12564
12704
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12565
12705
  const value = tokens[index + 1];
12566
12706
  if (token === "-C" && value) {
12567
- effectiveCwd = path39.resolve(baseCwd, value);
12707
+ effectiveCwd = path40.resolve(baseCwd, value);
12568
12708
  } else if (token === "--work-tree" && value) {
12569
12709
  workTree = value;
12570
12710
  } else if (token === "--git-dir" && value) {
@@ -12574,7 +12714,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12574
12714
  continue;
12575
12715
  }
12576
12716
  if (token.startsWith("-C") && token.length > 2) {
12577
- effectiveCwd = path39.resolve(baseCwd, token.slice(2));
12717
+ effectiveCwd = path40.resolve(baseCwd, token.slice(2));
12578
12718
  index += 1;
12579
12719
  continue;
12580
12720
  }
@@ -12717,7 +12857,7 @@ function looksLikeDiffPathOperand(token) {
12717
12857
  if (!looksLikeFileOperand(token)) {
12718
12858
  return false;
12719
12859
  }
12720
- if (token.startsWith(".") || path39.isAbsolute(token)) {
12860
+ if (token.startsWith(".") || path40.isAbsolute(token)) {
12721
12861
  return true;
12722
12862
  }
12723
12863
  return token.includes(".");
@@ -12725,12 +12865,12 @@ function looksLikeDiffPathOperand(token) {
12725
12865
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12726
12866
  const resolveBase = effectiveCwd ?? baseCwd;
12727
12867
  if (workTree) {
12728
- return path39.resolve(resolveBase, workTree);
12868
+ return path40.resolve(resolveBase, workTree);
12729
12869
  }
12730
12870
  if (gitDir) {
12731
- const resolvedGitDir = path39.resolve(resolveBase, gitDir);
12732
- if (path39.basename(resolvedGitDir) === ".git") {
12733
- return path39.dirname(resolvedGitDir);
12871
+ const resolvedGitDir = path40.resolve(resolveBase, gitDir);
12872
+ if (path40.basename(resolvedGitDir) === ".git") {
12873
+ return path40.dirname(resolvedGitDir);
12734
12874
  }
12735
12875
  }
12736
12876
  return void 0;
@@ -12811,7 +12951,7 @@ function classifyGitCommand(tokens, baseCwd) {
12811
12951
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12812
12952
  const normalizedKey = `git ${subcommand}`;
12813
12953
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12814
- const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12954
+ const effectiveGitDir = gitDir ? path40.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12815
12955
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12816
12956
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12817
12957
  );
@@ -12963,9 +13103,9 @@ function decodeGitEffects(params) {
12963
13103
  ...subcommand === "push" ? ["tier0_external"] : []
12964
13104
  ];
12965
13105
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12966
- const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12967
- const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12968
- const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
13106
+ const workTreeRoot = normalized.workTree ? path40.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
13107
+ const gitRefRoot = normalized.gitDir ? path40.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
13108
+ const gitControlRoot = normalized.gitDir ? gitRefRoot : path40.join(gitRefRoot, ".git");
12969
13109
  const requirements = [];
12970
13110
  if (subcommand === "fetch" || subcommand === "pull") {
12971
13111
  const positionals = gitRemotePositionals(args);
@@ -13124,7 +13264,7 @@ function decodeGitEffects(params) {
13124
13264
  gitRequirement(
13125
13265
  "control_plane.write",
13126
13266
  "control_plane.write",
13127
- { kind: "path", path: path39.join(gitControlRoot, "logs") },
13267
+ { kind: "path", path: path40.join(gitControlRoot, "logs") },
13128
13268
  params.segment,
13129
13269
  [...signals, "git_history_destructive", "git.reflog.mutate"]
13130
13270
  )
@@ -13182,7 +13322,7 @@ function decodeGitEffects(params) {
13182
13322
  gitRequirement(
13183
13323
  "fs.read",
13184
13324
  "fs.read",
13185
- { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13325
+ { kind: "path", path: path40.resolve(workTreeRoot, operand) },
13186
13326
  params.segment,
13187
13327
  [...signals, "git.path.read"]
13188
13328
  )
@@ -13217,7 +13357,7 @@ function decodeGitEffects(params) {
13217
13357
  gitRequirement(
13218
13358
  "fs.write",
13219
13359
  "fs.write",
13220
- { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13360
+ { kind: "path", path: path40.resolve(workTreeRoot, operand) },
13221
13361
  params.segment,
13222
13362
  [...signals, "git.path.write"]
13223
13363
  )
@@ -13401,8 +13541,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
13401
13541
  }
13402
13542
 
13403
13543
  // src/core/verdict/launcher-resolve.ts
13404
- import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
13405
- import path40 from "node:path";
13544
+ import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
13545
+ import path41 from "node:path";
13406
13546
 
13407
13547
  // src/core/verdict/makefile-expand.ts
13408
13548
  var MAX_EXPAND_DEPTH = 16;
@@ -13575,8 +13715,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
13575
13715
  "why"
13576
13716
  ]);
13577
13717
  function readPackageJson(dir) {
13578
- const packagePath = path40.join(dir, "package.json");
13579
- if (!existsSync11(packagePath)) {
13718
+ const packagePath = path41.join(dir, "package.json");
13719
+ if (!existsSync12(packagePath)) {
13580
13720
  return null;
13581
13721
  }
13582
13722
  try {
@@ -13586,17 +13726,17 @@ function readPackageJson(dir) {
13586
13726
  }
13587
13727
  }
13588
13728
  function findPackageJson(startDir, stopDir) {
13589
- let current = path40.resolve(startDir);
13590
- const stop = path40.resolve(stopDir);
13729
+ let current = path41.resolve(startDir);
13730
+ const stop = path41.resolve(stopDir);
13591
13731
  while (true) {
13592
- const packagePath = path40.join(current, "package.json");
13593
- if (existsSync11(packagePath)) {
13732
+ const packagePath = path41.join(current, "package.json");
13733
+ if (existsSync12(packagePath)) {
13594
13734
  return packagePath;
13595
13735
  }
13596
- if (current === stop || current === path40.dirname(current)) {
13597
- return existsSync11(packagePath) ? packagePath : null;
13736
+ if (current === stop || current === path41.dirname(current)) {
13737
+ return existsSync12(packagePath) ? packagePath : null;
13598
13738
  }
13599
- const parent = path40.dirname(current);
13739
+ const parent = path41.dirname(current);
13600
13740
  if (!parent.startsWith(stop) && parent !== current) {
13601
13741
  }
13602
13742
  if (parent === current) {
@@ -13653,7 +13793,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
13653
13793
  }
13654
13794
  return { recipes: [], opaque: true, reason: "package_json_missing" };
13655
13795
  }
13656
- const pkg = readPackageJson(path40.dirname(packagePath));
13796
+ const pkg = readPackageJson(path41.dirname(packagePath));
13657
13797
  const scripts = pkg?.scripts;
13658
13798
  if (!scripts || typeof scripts !== "object") {
13659
13799
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -13744,20 +13884,20 @@ function parseMakefileRecipeContent(content) {
13744
13884
  function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13745
13885
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13746
13886
  let makefilePath = null;
13747
- let searchDir = path40.resolve(cwd);
13748
- const stop = path40.resolve(repoRoot);
13887
+ let searchDir = path41.resolve(cwd);
13888
+ const stop = path41.resolve(repoRoot);
13749
13889
  while (true) {
13750
13890
  for (const name of candidates) {
13751
- const candidate = path40.join(searchDir, name);
13752
- if (existsSync11(candidate)) {
13891
+ const candidate = path41.join(searchDir, name);
13892
+ if (existsSync12(candidate)) {
13753
13893
  makefilePath = candidate;
13754
13894
  break;
13755
13895
  }
13756
13896
  }
13757
- if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
13897
+ if (makefilePath || searchDir === stop || searchDir === path41.dirname(searchDir)) {
13758
13898
  break;
13759
13899
  }
13760
- searchDir = path40.dirname(searchDir);
13900
+ searchDir = path41.dirname(searchDir);
13761
13901
  }
13762
13902
  if (!makefilePath) {
13763
13903
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
@@ -13784,7 +13924,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13784
13924
  }
13785
13925
  const entry = targets.get(name);
13786
13926
  if (!entry) {
13787
- if (!existsSync11(path40.resolve(path40.dirname(makefilePath), name))) {
13927
+ if (!existsSync12(path41.resolve(path41.dirname(makefilePath), name))) {
13788
13928
  hasUndefinedPrerequisite = true;
13789
13929
  }
13790
13930
  return;
@@ -13883,7 +14023,7 @@ function resolveLauncherRecipe(params) {
13883
14023
  }
13884
14024
 
13885
14025
  // src/core/verdict/parser.ts
13886
- import path41 from "node:path";
14026
+ import path42 from "node:path";
13887
14027
 
13888
14028
  // src/core/shell-substitution.ts
13889
14029
  function findStructuralCommandSubstitutions(command) {
@@ -14123,7 +14263,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
14123
14263
  ".sh"
14124
14264
  ]);
14125
14265
  function normalizeHead(token) {
14126
- const base = path41.basename(token);
14266
+ const base = path42.basename(token);
14127
14267
  if (base && base !== "." && base !== "..") {
14128
14268
  return base;
14129
14269
  }
@@ -14433,7 +14573,7 @@ function isBareInterpreter(tokens) {
14433
14573
  return false;
14434
14574
  }
14435
14575
  const scriptArg = args.find((token) => !token.startsWith("-"));
14436
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
14576
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path42.extname(scriptArg))) {
14437
14577
  return false;
14438
14578
  }
14439
14579
  if (scriptArg) {
@@ -14673,7 +14813,7 @@ function mergeNodes(nodes) {
14673
14813
  }
14674
14814
 
14675
14815
  // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14676
- import path42 from "node:path";
14816
+ import path43 from "node:path";
14677
14817
  var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14678
14818
  "sudo",
14679
14819
  "env",
@@ -14694,7 +14834,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14694
14834
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14695
14835
  return false;
14696
14836
  }
14697
- const innerHead = path42.basename(innerTokens[0] ?? "");
14837
+ const innerHead = path43.basename(innerTokens[0] ?? "");
14698
14838
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14699
14839
  return false;
14700
14840
  }
@@ -14801,7 +14941,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
14801
14941
 
14802
14942
  // src/core/effect-ir/shell-lower/tokens.ts
14803
14943
  init_shell_tokenizer();
14804
- import path43 from "node:path";
14944
+ import path44 from "node:path";
14805
14945
  var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14806
14946
  var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14807
14947
  var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
@@ -14945,9 +15085,9 @@ function resolvePathOperand(operand, cwd) {
14945
15085
  return process.env.HOME ?? operand;
14946
15086
  }
14947
15087
  if (operand.startsWith("~/")) {
14948
- return path43.join(process.env.HOME ?? "~", operand.slice(2));
15088
+ return path44.join(process.env.HOME ?? "~", operand.slice(2));
14949
15089
  }
14950
- return path43.resolve(cwd, operand);
15090
+ return path44.resolve(cwd, operand);
14951
15091
  }
14952
15092
  function isOptionToken(value) {
14953
15093
  return value.startsWith("-") || value.startsWith("+");
@@ -14959,7 +15099,7 @@ function pipeToShell(command) {
14959
15099
  return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
14960
15100
  }
14961
15101
  function executableBaseName(head) {
14962
- return path43.basename(head);
15102
+ return path44.basename(head);
14963
15103
  }
14964
15104
  function isMetadataOnlyArgv(argv) {
14965
15105
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
@@ -15078,15 +15218,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
15078
15218
  }
15079
15219
 
15080
15220
  // src/core/effect-ir/shell-lower/decode-process.ts
15081
- import path47 from "node:path";
15221
+ import path48 from "node:path";
15082
15222
 
15083
15223
  // src/core/effect-ir/shell-lower/decoders/belay.ts
15084
- import path44 from "node:path";
15224
+ import path45 from "node:path";
15085
15225
  function decodeBelay(args, repoRoot, segment) {
15086
15226
  const [section, operation, key] = args;
15087
15227
  const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
15088
15228
  const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
15089
15229
  const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
15230
+ const approvalAuthorityCommand = [
15231
+ "approval-token",
15232
+ "approve",
15233
+ "revoke",
15234
+ "standing-allow"
15235
+ ].includes(section ?? "");
15236
+ const configTrustMutation = section === "config" && operation === "trust";
15090
15237
  if (judgeCommand || configRead || configJudgeMutation) {
15091
15238
  return [
15092
15239
  processRequirement("belay", "inspect", segment, [
@@ -15095,12 +15242,23 @@ function decodeBelay(args, repoRoot, segment) {
15095
15242
  ])
15096
15243
  ];
15097
15244
  }
15245
+ if (approvalAuthorityCommand || configTrustMutation) {
15246
+ return [
15247
+ requirement2(
15248
+ "control_plane.write",
15249
+ "control_plane.write",
15250
+ { kind: "path", path: path45.join(repoRoot, ".belay-control-plane") },
15251
+ segment,
15252
+ [approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
15253
+ )
15254
+ ];
15255
+ }
15098
15256
  if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
15099
15257
  return [
15100
15258
  requirement2(
15101
15259
  "control_plane.write",
15102
15260
  "control_plane.write",
15103
- { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15261
+ { kind: "path", path: path45.join(repoRoot, ".belay-control-plane") },
15104
15262
  segment,
15105
15263
  ["belay.config_non_judge_mutation"]
15106
15264
  )
@@ -15341,7 +15499,7 @@ function validDockerInfo(args) {
15341
15499
  // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15342
15500
  init_git_resource_identity();
15343
15501
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15344
- import path45 from "node:path";
15502
+ import path46 from "node:path";
15345
15503
  function decodeCopyMove(head, args, cwd, segment) {
15346
15504
  const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15347
15505
  const operands = [];
@@ -15469,11 +15627,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
15469
15627
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15470
15628
  try {
15471
15629
  if (finalOperandIsSymlink) {
15472
- return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
15630
+ return path46.join(realpathSync4.native(path46.dirname(targetPath)), path46.basename(targetPath));
15473
15631
  }
15474
15632
  return realpathSync4.native(targetPath);
15475
15633
  } catch {
15476
- return path45.resolve(targetPath);
15634
+ return path46.resolve(targetPath);
15477
15635
  }
15478
15636
  }
15479
15637
  function isSymbolicLink(targetPath) {
@@ -15484,8 +15642,8 @@ function isSymbolicLink(targetPath) {
15484
15642
  }
15485
15643
  }
15486
15644
  function pathContains(ancestor, candidate) {
15487
- const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
15488
- return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
15645
+ const relative = path46.relative(path46.resolve(ancestor), path46.resolve(candidate));
15646
+ return relative === "" || !relative.startsWith("..") && !path46.isAbsolute(relative);
15489
15647
  }
15490
15648
  function filesystemReadOperands(head, args) {
15491
15649
  switch (head) {
@@ -15625,7 +15783,7 @@ function decodePrisma(args, env, repoRoot, segment) {
15625
15783
 
15626
15784
  // src/core/effect-ir/shell-lower/decoders/ruby.ts
15627
15785
  init_path_utils();
15628
- import path46 from "node:path";
15786
+ import path47 from "node:path";
15629
15787
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15630
15788
  function railsReadOnlySubcommand(args) {
15631
15789
  const subcommand = args[0];
@@ -15635,7 +15793,7 @@ function railsReadOnlySubcommand(args) {
15635
15793
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15636
15794
  }
15637
15795
  function isRubyTestScript(scriptPath) {
15638
- const base = path46.basename(scriptPath);
15796
+ const base = path47.basename(scriptPath);
15639
15797
  return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15640
15798
  }
15641
15799
  function parseRubyTestInvocation(args) {
@@ -16049,7 +16207,7 @@ function decodeProcessOrFilesystem(params) {
16049
16207
  requirement2(
16050
16208
  "fs.read",
16051
16209
  "fs.read",
16052
- { kind: "path", path: path47.resolve(cwd, syntax) },
16210
+ { kind: "path", path: path48.resolve(cwd, syntax) },
16053
16211
  segment,
16054
16212
  ["shell.syntax_source_read"]
16055
16213
  )
@@ -16255,7 +16413,7 @@ function packageExecInnerIsMetadata(peel) {
16255
16413
 
16256
16414
  // src/core/effect-ir/shell-lower/segment.ts
16257
16415
  init_shell_tokenizer();
16258
- import path48 from "node:path";
16416
+ import path49 from "node:path";
16259
16417
  var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16260
16418
  var SHELL_GLOB_PATTERN = /[*?[]/;
16261
16419
  function requiresKnownCwd(requirementValue) {
@@ -16270,11 +16428,11 @@ function joinNestedOpacity(outer, nested) {
16270
16428
  }
16271
16429
  function startsLocalPostgresService(command) {
16272
16430
  const tokens = tokenizeShell(command);
16273
- return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16431
+ return path49.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16274
16432
  }
16275
16433
  function resolveCdTransition(command, currentCwd) {
16276
16434
  const tokens = tokenizeShell(command);
16277
- if (path48.basename(tokens[0] ?? "") !== "cd") {
16435
+ if (path49.basename(tokens[0] ?? "") !== "cd") {
16278
16436
  return null;
16279
16437
  }
16280
16438
  const target = tokens[1] ?? "~";
@@ -16437,7 +16595,7 @@ function lowerSegment(command, context) {
16437
16595
  stripStructuredRedirects(lexed.tokens),
16438
16596
  stripRedirects(parsedTokens)
16439
16597
  );
16440
- const head = path49.basename(tokens[0] ?? parsed.head);
16598
+ const head = path50.basename(tokens[0] ?? parsed.head);
16441
16599
  let opacity = segmentOpacity(command);
16442
16600
  const signals = /* @__PURE__ */ new Set();
16443
16601
  const requirements = [];
@@ -17020,17 +17178,41 @@ function verdictToClassifyResult(result, config, options = {}) {
17020
17178
 
17021
17179
  // src/core/classify-tool.ts
17022
17180
  var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
17023
- var FILE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write"]);
17024
- var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
17025
- "edit",
17026
- "multiedit",
17027
- "multi_edit",
17028
- "patch",
17029
- "strreplace",
17030
- "str_replace"
17031
- ]);
17032
- var FILE_DELETE_TOOL_NAMES = /* @__PURE__ */ new Set(["delete"]);
17033
- var APPLY_PATCH_TOOL_NAMES = /* @__PURE__ */ new Set(["apply_patch", "applypatch"]);
17181
+ function toolInputRecord(payload) {
17182
+ const toolInput = payload.tool_input;
17183
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
17184
+ return null;
17185
+ }
17186
+ return toolInput;
17187
+ }
17188
+ function hasMutationFields(input) {
17189
+ return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
17190
+ }
17191
+ function inferToolPayloadEffect(payload, toolName) {
17192
+ if (extractShellCommand(payload)) {
17193
+ return "shell";
17194
+ }
17195
+ const input = toolInputRecord(payload);
17196
+ if (!input) {
17197
+ return "indeterminate";
17198
+ }
17199
+ if (hasMutationFields(input)) {
17200
+ return "file_mutation";
17201
+ }
17202
+ if (extractPatch(payload)) {
17203
+ return "apply_patch";
17204
+ }
17205
+ if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
17206
+ return "search_read";
17207
+ }
17208
+ if (typeof input.file_path === "string") {
17209
+ return "file_read";
17210
+ }
17211
+ if (typeof input.path === "string" || typeof input.target_file === "string") {
17212
+ return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
17213
+ }
17214
+ return "indeterminate";
17215
+ }
17034
17216
  function policyAuth(options) {
17035
17217
  if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
17036
17218
  return void 0;
@@ -17210,56 +17392,365 @@ function classifyFileMutationWithPolicy(params) {
17210
17392
  boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17211
17393
  };
17212
17394
  }
17213
- async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17214
- const toolName = String(payload.tool_name ?? "");
17215
- const toolKind = normalizedToolName(toolName);
17216
- const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17217
- const protectedRoots = [
17218
- ...options.protectedArtifactRoots ?? [],
17219
- ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17220
- ];
17221
- if (toolKind === "shell") {
17222
- const command = extractShellCommand(payload);
17223
- if (!command) {
17224
- if (options.unknownLocalEffect === "deny") {
17225
- return {
17226
- verdict: "deny_pending_approval",
17227
- reason: "tool_shell_missing_command",
17228
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17229
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17230
- assessment: {
17231
- reversibility: "irreversible",
17232
- external: false,
17233
- blastRadius: "tool shell",
17234
- confidence: 0.85,
17235
- signals: ["missing_command"]
17236
- }
17237
- };
17238
- }
17239
- return {
17240
- verdict: "allow_flagged",
17241
- reason: "tool_shell_missing_command",
17242
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17243
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17244
- assessment: {
17245
- reversibility: "recoverable_with_cost",
17246
- external: false,
17247
- blastRadius: "tool shell",
17248
- confidence: 0.5,
17249
- signals: ["missing_command"]
17250
- }
17251
- };
17252
- }
17253
- const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17254
- return {
17255
- ...shellResult,
17256
- summary: command
17257
- };
17258
- }
17259
- if (FILE_WRITE_TOOL_NAMES.has(toolKind) || FILE_EDIT_TOOL_NAMES.has(toolKind) || FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17260
- const filePath = extractFilePath(payload);
17261
- if (!filePath) {
17262
- if (options.unknownLocalEffect === "deny") {
17395
+ function classifyFileReadWithPolicy(params) {
17396
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
17397
+ const { request, decision } = evaluateFileReadPolicy(
17398
+ {
17399
+ hookKind: "tool",
17400
+ toolKind: params.toolKind,
17401
+ filePath: params.filePath,
17402
+ resolvedPath: params.resolvedPath,
17403
+ repoRoot: params.repoRoot,
17404
+ cwd: params.cwd,
17405
+ inputFingerprint: fingerprint,
17406
+ signals: params.signals,
17407
+ locationLabel: params.locationLabel,
17408
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
17409
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
17410
+ },
17411
+ params.config,
17412
+ policyAuth(params.options)
17413
+ );
17414
+ if (policyDecisionRequiresAsk(decision)) {
17415
+ return {
17416
+ verdict: "deny_pending_approval",
17417
+ reason: policyReasonToLegacyReason(decision),
17418
+ summary: params.filePath,
17419
+ fingerprint,
17420
+ assessment: {
17421
+ reversibility: "reversible",
17422
+ external: params.locationLabel === "outside_repo",
17423
+ blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
17424
+ confidence: 0.85,
17425
+ signals: [...params.signals, ...decision.signals]
17426
+ },
17427
+ capabilityRequests: [request],
17428
+ authorizationDecision: decision,
17429
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17430
+ };
17431
+ }
17432
+ return {
17433
+ verdict: "allow",
17434
+ reason: "effect.fs_read",
17435
+ summary: params.filePath,
17436
+ fingerprint,
17437
+ assessment: {
17438
+ reversibility: "reversible",
17439
+ external: false,
17440
+ blastRadius: "tool scope",
17441
+ confidence: 0.88,
17442
+ signals: [...params.signals, ...decision.signals]
17443
+ },
17444
+ capabilityRequests: [request],
17445
+ authorizationDecision: decision,
17446
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17447
+ };
17448
+ }
17449
+ function classifyFilePathMutation(params) {
17450
+ const {
17451
+ toolName,
17452
+ toolKind,
17453
+ filePath,
17454
+ repoRoot,
17455
+ cwd,
17456
+ config,
17457
+ options,
17458
+ sensitivePaths,
17459
+ protectedRoots,
17460
+ isDelete
17461
+ } = params;
17462
+ const signals = [];
17463
+ const resolvedPath = path51.isAbsolute(filePath) ? filePath : path51.resolve(cwd, filePath);
17464
+ const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17465
+ if (hitsProtectedRoot) {
17466
+ signals.push("control_plane_path");
17467
+ return classifyFileMutationWithPolicy({
17468
+ toolName,
17469
+ toolKind,
17470
+ filePath,
17471
+ resolvedPath,
17472
+ repoRoot,
17473
+ cwd,
17474
+ config,
17475
+ options,
17476
+ signals,
17477
+ isDelete,
17478
+ locationLabel: "control_plane"
17479
+ });
17480
+ }
17481
+ const workspaceMatch = resolveWorkspaceRootMatch(
17482
+ repoRoot,
17483
+ options.trustedWorkspaceRoots,
17484
+ resolvedPath
17485
+ );
17486
+ if (workspaceMatch === null) {
17487
+ signals.push("outside_repo_path");
17488
+ return classifyFileMutationWithPolicy({
17489
+ toolName,
17490
+ toolKind,
17491
+ filePath,
17492
+ resolvedPath,
17493
+ repoRoot,
17494
+ cwd,
17495
+ config,
17496
+ options,
17497
+ signals,
17498
+ isDelete,
17499
+ locationLabel: "outside_repo"
17500
+ });
17501
+ }
17502
+ if (workspaceMatch.kind === "trusted") {
17503
+ signals.push("trusted_workspace_root");
17504
+ }
17505
+ const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17506
+ const workspacePrescan = mutationPrescanRequiresAsk({
17507
+ targets: [filePath],
17508
+ cwd,
17509
+ repoRoot,
17510
+ trustedCwd,
17511
+ trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17512
+ sensitivePaths
17513
+ });
17514
+ if (workspacePrescan) {
17515
+ return classifyFileMutationWithPolicy({
17516
+ toolName,
17517
+ toolKind,
17518
+ filePath,
17519
+ resolvedPath,
17520
+ repoRoot,
17521
+ cwd,
17522
+ config,
17523
+ options,
17524
+ signals,
17525
+ isDelete,
17526
+ locationLabel: "sensitive_path"
17527
+ });
17528
+ }
17529
+ const relativePath = workspaceMatch.relativePath;
17530
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17531
+ signals.push("sensitive_path");
17532
+ return classifyFileMutationWithPolicy({
17533
+ toolName,
17534
+ toolKind,
17535
+ filePath,
17536
+ resolvedPath,
17537
+ repoRoot,
17538
+ cwd,
17539
+ config,
17540
+ options,
17541
+ signals,
17542
+ isDelete,
17543
+ locationLabel: "sensitive_path"
17544
+ });
17545
+ }
17546
+ if (isDelete) {
17547
+ if (isGitPath(resolvedPath, repoRoot)) {
17548
+ signals.push("protected_artifact");
17549
+ return classifyFileMutationWithPolicy({
17550
+ toolName,
17551
+ toolKind,
17552
+ filePath,
17553
+ resolvedPath,
17554
+ repoRoot,
17555
+ cwd,
17556
+ config,
17557
+ options,
17558
+ signals,
17559
+ isDelete: true,
17560
+ locationLabel: "sensitive_path"
17561
+ });
17562
+ }
17563
+ signals.push("file_delete");
17564
+ return classifyFileMutationWithPolicy({
17565
+ toolName,
17566
+ toolKind,
17567
+ filePath,
17568
+ resolvedPath,
17569
+ repoRoot,
17570
+ cwd,
17571
+ config,
17572
+ options,
17573
+ signals,
17574
+ isDelete: true,
17575
+ locationLabel: "repo_local"
17576
+ });
17577
+ }
17578
+ signals.push("file_mutation");
17579
+ return classifyFileMutationWithPolicy({
17580
+ toolName,
17581
+ toolKind,
17582
+ filePath,
17583
+ resolvedPath,
17584
+ repoRoot,
17585
+ cwd,
17586
+ config,
17587
+ options,
17588
+ signals,
17589
+ isDelete: false,
17590
+ locationLabel: "repo_local"
17591
+ });
17592
+ }
17593
+ function classifyFilePathRead(params) {
17594
+ const {
17595
+ toolName,
17596
+ toolKind,
17597
+ filePath,
17598
+ repoRoot,
17599
+ cwd,
17600
+ config,
17601
+ options,
17602
+ sensitivePaths,
17603
+ protectedRoots
17604
+ } = params;
17605
+ const signals = ["effect.fs_read"];
17606
+ const resolvedPath = path51.isAbsolute(filePath) ? filePath : path51.resolve(cwd, filePath);
17607
+ if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
17608
+ signals.push("control_plane_path");
17609
+ return classifyFileReadWithPolicy({
17610
+ toolName,
17611
+ toolKind,
17612
+ filePath,
17613
+ resolvedPath,
17614
+ repoRoot,
17615
+ cwd,
17616
+ config,
17617
+ options,
17618
+ signals,
17619
+ locationLabel: "control_plane"
17620
+ });
17621
+ }
17622
+ const workspaceMatch = resolveWorkspaceRootMatch(
17623
+ repoRoot,
17624
+ options.trustedWorkspaceRoots,
17625
+ resolvedPath
17626
+ );
17627
+ if (workspaceMatch === null) {
17628
+ signals.push("outside_repo_path");
17629
+ return classifyFileReadWithPolicy({
17630
+ toolName,
17631
+ toolKind,
17632
+ filePath,
17633
+ resolvedPath,
17634
+ repoRoot,
17635
+ cwd,
17636
+ config,
17637
+ options,
17638
+ signals,
17639
+ locationLabel: "outside_repo"
17640
+ });
17641
+ }
17642
+ const relativePath = workspaceMatch.relativePath;
17643
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17644
+ signals.push("sensitive_path");
17645
+ return classifyFileReadWithPolicy({
17646
+ toolName,
17647
+ toolKind,
17648
+ filePath,
17649
+ resolvedPath,
17650
+ repoRoot,
17651
+ cwd,
17652
+ config,
17653
+ options,
17654
+ signals,
17655
+ locationLabel: "sensitive_path"
17656
+ });
17657
+ }
17658
+ return classifyFileReadWithPolicy({
17659
+ toolName,
17660
+ toolKind,
17661
+ filePath,
17662
+ resolvedPath,
17663
+ repoRoot,
17664
+ cwd,
17665
+ config,
17666
+ options,
17667
+ signals,
17668
+ locationLabel: "repo_local"
17669
+ });
17670
+ }
17671
+ function indeterminateToolResult(toolName, payload, repoRoot, options) {
17672
+ const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
17673
+ const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
17674
+ if (options.unknownLocalEffect === "deny") {
17675
+ return {
17676
+ verdict: "deny_pending_approval",
17677
+ reason: "indeterminate_tool_effect",
17678
+ summary,
17679
+ fingerprint,
17680
+ assessment: {
17681
+ reversibility: "irreversible",
17682
+ external: false,
17683
+ blastRadius: "unknown tool action",
17684
+ confidence: 0.5,
17685
+ signals: ["indeterminate_tool_effect"]
17686
+ }
17687
+ };
17688
+ }
17689
+ return {
17690
+ verdict: "allow_flagged",
17691
+ reason: "indeterminate_tool_effect",
17692
+ summary,
17693
+ fingerprint,
17694
+ assessment: {
17695
+ reversibility: "recoverable_with_cost",
17696
+ external: false,
17697
+ blastRadius: "tool scope",
17698
+ confidence: 0.5,
17699
+ signals: ["indeterminate_tool_effect"]
17700
+ }
17701
+ };
17702
+ }
17703
+ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17704
+ const toolName = String(payload.tool_name ?? "");
17705
+ const toolKind = normalizedToolName(toolName);
17706
+ const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17707
+ const protectedRoots = [
17708
+ ...options.protectedArtifactRoots ?? [],
17709
+ ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17710
+ ];
17711
+ if (toolKind === "shell") {
17712
+ const command = extractShellCommand(payload);
17713
+ if (!command) {
17714
+ if (options.unknownLocalEffect === "deny") {
17715
+ return {
17716
+ verdict: "deny_pending_approval",
17717
+ reason: "tool_shell_missing_command",
17718
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17719
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17720
+ assessment: {
17721
+ reversibility: "irreversible",
17722
+ external: false,
17723
+ blastRadius: "tool shell",
17724
+ confidence: 0.85,
17725
+ signals: ["missing_command"]
17726
+ }
17727
+ };
17728
+ }
17729
+ return {
17730
+ verdict: "allow_flagged",
17731
+ reason: "tool_shell_missing_command",
17732
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17733
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17734
+ assessment: {
17735
+ reversibility: "recoverable_with_cost",
17736
+ external: false,
17737
+ blastRadius: "tool shell",
17738
+ confidence: 0.5,
17739
+ signals: ["missing_command"]
17740
+ }
17741
+ };
17742
+ }
17743
+ const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17744
+ return {
17745
+ ...shellResult,
17746
+ summary: command
17747
+ };
17748
+ }
17749
+ const effect = inferToolPayloadEffect(payload, toolName);
17750
+ if (effect === "file_mutation" || effect === "file_delete") {
17751
+ const filePath2 = extractFilePath(payload);
17752
+ if (!filePath2) {
17753
+ if (options.unknownLocalEffect === "deny") {
17263
17754
  return {
17264
17755
  verdict: "deny_pending_approval",
17265
17756
  reason: "file_mutation_missing_path",
@@ -17288,138 +17779,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17288
17779
  }
17289
17780
  };
17290
17781
  }
17291
- const signals = [];
17292
- const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17293
- const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17294
- if (hitsProtectedRoot) {
17295
- signals.push("control_plane_path");
17296
- return classifyFileMutationWithPolicy({
17297
- toolName,
17298
- toolKind,
17299
- filePath,
17300
- resolvedPath,
17301
- repoRoot,
17302
- cwd,
17303
- config,
17304
- options,
17305
- signals,
17306
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17307
- locationLabel: "control_plane"
17308
- });
17309
- }
17310
- const workspaceMatch = resolveWorkspaceRootMatch(
17311
- repoRoot,
17312
- options.trustedWorkspaceRoots,
17313
- resolvedPath
17314
- );
17315
- if (workspaceMatch === null) {
17316
- signals.push("outside_repo_path");
17317
- return classifyFileMutationWithPolicy({
17318
- toolName,
17319
- toolKind,
17320
- filePath,
17321
- resolvedPath,
17322
- repoRoot,
17323
- cwd,
17324
- config,
17325
- options,
17326
- signals,
17327
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17328
- locationLabel: "outside_repo"
17329
- });
17330
- }
17331
- if (workspaceMatch.kind === "trusted") {
17332
- signals.push("trusted_workspace_root");
17333
- }
17334
- const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17335
- const workspacePrescan = mutationPrescanRequiresAsk({
17336
- targets: [filePath],
17337
- cwd,
17338
- repoRoot,
17339
- trustedCwd,
17340
- trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17341
- sensitivePaths
17342
- });
17343
- if (workspacePrescan) {
17344
- return classifyFileMutationWithPolicy({
17345
- toolName,
17346
- toolKind,
17347
- filePath,
17348
- resolvedPath,
17349
- repoRoot,
17350
- cwd,
17351
- config,
17352
- options,
17353
- signals,
17354
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17355
- locationLabel: "sensitive_path"
17356
- });
17357
- }
17358
- const relativePath = workspaceMatch.relativePath;
17359
- if (matchesSensitivePath(relativePath, sensitivePaths)) {
17360
- signals.push("sensitive_path");
17361
- return classifyFileMutationWithPolicy({
17362
- toolName,
17363
- toolKind,
17364
- filePath,
17365
- resolvedPath,
17366
- repoRoot,
17367
- cwd,
17368
- config,
17369
- options,
17370
- signals,
17371
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17372
- locationLabel: "sensitive_path"
17373
- });
17374
- }
17375
- if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17376
- if (isGitPath(resolvedPath, repoRoot)) {
17377
- signals.push("protected_artifact");
17378
- return classifyFileMutationWithPolicy({
17379
- toolName,
17380
- toolKind,
17381
- filePath,
17382
- resolvedPath,
17383
- repoRoot,
17384
- cwd,
17385
- config,
17386
- options,
17387
- signals,
17388
- isDelete: true,
17389
- locationLabel: "sensitive_path"
17390
- });
17391
- }
17392
- signals.push("file_delete");
17393
- return classifyFileMutationWithPolicy({
17394
- toolName,
17395
- toolKind,
17396
- filePath,
17397
- resolvedPath,
17398
- repoRoot,
17399
- cwd,
17400
- config,
17401
- options,
17402
- signals,
17403
- isDelete: true,
17404
- locationLabel: "repo_local"
17405
- });
17406
- }
17407
- signals.push("file_mutation");
17408
- return classifyFileMutationWithPolicy({
17782
+ return classifyFilePathMutation({
17409
17783
  toolName,
17410
17784
  toolKind,
17411
- filePath,
17412
- resolvedPath,
17785
+ filePath: filePath2,
17413
17786
  repoRoot,
17414
17787
  cwd,
17415
17788
  config,
17416
17789
  options,
17417
- signals,
17418
- isDelete: false,
17419
- locationLabel: "repo_local"
17790
+ sensitivePaths,
17791
+ protectedRoots,
17792
+ isDelete: effect === "file_delete"
17420
17793
  });
17421
17794
  }
17422
- if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
17795
+ if (effect === "apply_patch") {
17423
17796
  const patch = extractPatch(payload);
17424
17797
  const targets = patch ? applyPatchTargets2(patch) : [];
17425
17798
  if (targets.length === 0) {
@@ -17457,7 +17830,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17457
17830
  const result = await classifyToolUse(
17458
17831
  {
17459
17832
  tool_name: target.delete ? "Delete" : "Write",
17460
- tool_input: { path: target.path }
17833
+ tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
17461
17834
  },
17462
17835
  repoRoot,
17463
17836
  cwd,
@@ -17483,19 +17856,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17483
17856
  }
17484
17857
  };
17485
17858
  }
17486
- return {
17487
- verdict: "allow",
17488
- reason: "unclassified_tool",
17489
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17490
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17491
- assessment: {
17492
- reversibility: "reversible",
17493
- external: false,
17494
- blastRadius: "tool scope",
17495
- confidence: 0.5,
17496
- signals: ["unclassified_tool"]
17859
+ if (effect === "file_read") {
17860
+ const filePath2 = extractFilePath(payload);
17861
+ if (!filePath2) {
17862
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17497
17863
  }
17498
- };
17864
+ return classifyFilePathRead({
17865
+ toolName,
17866
+ toolKind,
17867
+ filePath: filePath2,
17868
+ repoRoot,
17869
+ cwd,
17870
+ config,
17871
+ options,
17872
+ sensitivePaths,
17873
+ protectedRoots
17874
+ });
17875
+ }
17876
+ if (effect === "search_read") {
17877
+ return {
17878
+ verdict: "allow",
17879
+ reason: "effect.search_read",
17880
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17881
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17882
+ assessment: {
17883
+ reversibility: "reversible",
17884
+ external: false,
17885
+ blastRadius: "tool scope",
17886
+ confidence: 0.88,
17887
+ signals: ["effect.search_read"]
17888
+ }
17889
+ };
17890
+ }
17891
+ const filePath = extractFilePath(payload);
17892
+ if (filePath) {
17893
+ return classifyFilePathMutation({
17894
+ toolName,
17895
+ toolKind,
17896
+ filePath,
17897
+ repoRoot,
17898
+ cwd,
17899
+ config,
17900
+ options,
17901
+ sensitivePaths,
17902
+ protectedRoots,
17903
+ isDelete: toolKind === "delete"
17904
+ });
17905
+ }
17906
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17499
17907
  }
17500
17908
 
17501
17909
  // src/core/gate-engine.ts
@@ -17830,15 +18238,8 @@ function gateEnabledForAction(config, action) {
17830
18238
  if (action.kind === "subagent") {
17831
18239
  return config.gates.subagent;
17832
18240
  }
17833
- const toolName = action.toolName ?? String(action.payload?.tool_name ?? "");
17834
- if (toolName === "Shell") {
17835
- return config.gates.toolShell;
17836
- }
17837
- if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
17838
- return config.gates.fileMutation;
17839
- }
17840
- if (toolName === "Task") {
17841
- return config.gates.subagent;
18241
+ if (action.kind === "tool") {
18242
+ return config.gates.fileMutation || config.gates.toolShell;
17842
18243
  }
17843
18244
  return true;
17844
18245
  }
@@ -17875,7 +18276,7 @@ function hashDecisionConfig(config) {
17875
18276
  init_fingerprint2();
17876
18277
 
17877
18278
  // src/version.ts
17878
- var PACKAGE_VERSION = "0.9.4";
18279
+ var PACKAGE_VERSION = "0.10.1";
17879
18280
 
17880
18281
  // src/runtime-provenance.ts
17881
18282
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17948,56 +18349,56 @@ function capabilityRequestsBlockRecovery(requests) {
17948
18349
  init_fingerprint2();
17949
18350
  init_path_utils();
17950
18351
  import { randomUUID as randomUUID5 } from "node:crypto";
17951
- import { existsSync as existsSync15 } from "node:fs";
17952
- import { mkdir as mkdir11, readdir as readdir3, readFile as readFile13, rename as rename3, rm as rm6 } from "node:fs/promises";
17953
- import path55 from "node:path";
18352
+ import { existsSync as existsSync16 } from "node:fs";
18353
+ import { mkdir as mkdir12, readdir as readdir3, readFile as readFile14, rename as rename4, rm as rm6 } from "node:fs/promises";
18354
+ import path56 from "node:path";
17954
18355
 
17955
18356
  // src/core/recovery/artifact-store.ts
17956
18357
  init_fingerprint2();
17957
18358
  init_path_utils();
17958
18359
  import { randomUUID as randomUUID4 } from "node:crypto";
17959
- import { existsSync as existsSync13 } from "node:fs";
17960
- import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile10, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17961
- import path52 from "node:path";
18360
+ import { existsSync as existsSync14 } from "node:fs";
18361
+ import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile11, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
18362
+ import path53 from "node:path";
17962
18363
 
17963
18364
  // src/core/recovery/snapshot-node.ts
17964
18365
  init_fingerprint2();
17965
18366
  init_path_utils();
17966
18367
  import { createHash as createHash12 } from "node:crypto";
17967
- import { existsSync as existsSync12 } from "node:fs";
18368
+ import { existsSync as existsSync13 } from "node:fs";
17968
18369
  import {
17969
- chmod as chmod3,
18370
+ chmod as chmod4,
17970
18371
  copyFile as copyFile2,
17971
18372
  lstat as lstat6,
17972
- mkdir as mkdir9,
17973
- open as open4,
17974
- readFile as readFile9,
18373
+ mkdir as mkdir10,
18374
+ open as open5,
18375
+ readFile as readFile10,
17975
18376
  readlink as readlink4,
17976
18377
  rm as rm4,
17977
18378
  rmdir as rmdir2,
17978
18379
  symlink as symlink3,
17979
18380
  writeFile as writeFile6
17980
18381
  } from "node:fs/promises";
17981
- import path51 from "node:path";
18382
+ import path52 from "node:path";
17982
18383
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17983
18384
  function validRecoveryRelativePath(relativePath) {
17984
- if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
17985
- const normalized = path51.normalize(relativePath);
17986
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
18385
+ if (!relativePath || relativePath.includes("\0") || path52.isAbsolute(relativePath)) return false;
18386
+ const normalized = path52.normalize(relativePath);
18387
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path52.sep}`);
17987
18388
  }
17988
18389
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17989
18390
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17990
18391
  const root = canonicalPath(resourceRoot);
17991
- const target = path51.resolve(root, relativePath);
17992
- const relative = path51.relative(root, target);
17993
- if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
18392
+ const target = path52.resolve(root, relativePath);
18393
+ const relative = path52.relative(root, target);
18394
+ if (relative === ".." || relative.startsWith(`..${path52.sep}`) || path52.isAbsolute(relative)) {
17994
18395
  throw new Error("recovery_path_escape");
17995
18396
  }
17996
18397
  let current = root;
17997
- const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
18398
+ const parentParts = path52.relative(root, path52.dirname(target)).split(path52.sep).filter(Boolean);
17998
18399
  for (const part of parentParts) {
17999
- current = path51.join(current, part);
18000
- if (!existsSync12(current)) break;
18400
+ current = path52.join(current, part);
18401
+ if (!existsSync13(current)) break;
18001
18402
  const info = await lstat6(current);
18002
18403
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
18003
18404
  if (!info.isDirectory()) break;
@@ -18005,7 +18406,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
18005
18406
  return target;
18006
18407
  }
18007
18408
  async function fsyncPath(filePath) {
18008
- const handle = await open4(filePath, "r");
18409
+ const handle = await open5(filePath, "r");
18009
18410
  try {
18010
18411
  await handle.sync();
18011
18412
  } finally {
@@ -18047,13 +18448,13 @@ async function captureRecoverySnapshot(filePath, options) {
18047
18448
  return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
18048
18449
  }
18049
18450
  if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
18050
- const content = await readFile9(filePath);
18451
+ const content = await readFile10(filePath);
18051
18452
  const hash = createHash12("sha256").update(content).digest("hex");
18052
18453
  let blob;
18053
18454
  if (options?.blobDir) {
18054
- await mkdir9(options.blobDir, { recursive: true, mode: 448 });
18055
- const blobPath = path51.join(options.blobDir, hash);
18056
- if (!existsSync12(blobPath)) {
18455
+ await mkdir10(options.blobDir, { recursive: true, mode: 448 });
18456
+ const blobPath = path52.join(options.blobDir, hash);
18457
+ if (!existsSync13(blobPath)) {
18057
18458
  await writeFile6(blobPath, content, { mode: 384 });
18058
18459
  await fsyncPath(blobPath);
18059
18460
  }
@@ -18107,7 +18508,7 @@ async function validateRecoverySnapshot(params) {
18107
18508
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
18108
18509
  let content;
18109
18510
  try {
18110
- content = await readFile9(path51.join(params.artifactDir, record.blob));
18511
+ content = await readFile10(path52.join(params.artifactDir, record.blob));
18111
18512
  } catch {
18112
18513
  throw new Error(params.corruptReason);
18113
18514
  }
@@ -18135,16 +18536,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
18135
18536
  ]);
18136
18537
  var STAGING_STALE_MS = 5 * 6e4;
18137
18538
  function checkpointsRoot(stateDir) {
18138
- return path52.join(stateDir, "recovery", "checkpoints");
18539
+ return path53.join(stateDir, "recovery", "checkpoints");
18139
18540
  }
18140
18541
  function checkpointDir(stateDir, checkpointId) {
18141
18542
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
18142
18543
  throw new Error("invalid_recovery_checkpoint_id");
18143
18544
  }
18144
- return path52.join(checkpointsRoot(stateDir), checkpointId);
18545
+ return path53.join(checkpointsRoot(stateDir), checkpointId);
18145
18546
  }
18146
18547
  async function fsyncPath2(filePath) {
18147
- const handle = await open5(filePath, "r");
18548
+ const handle = await open6(filePath, "r");
18148
18549
  try {
18149
18550
  await handle.sync();
18150
18551
  } finally {
@@ -18152,13 +18553,13 @@ async function fsyncPath2(filePath) {
18152
18553
  }
18153
18554
  }
18154
18555
  async function atomicWriteJson(filePath, value) {
18155
- await mkdir10(path52.dirname(filePath), { recursive: true, mode: 448 });
18556
+ await mkdir11(path53.dirname(filePath), { recursive: true, mode: 448 });
18156
18557
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
18157
18558
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
18158
18559
  `, { mode: 384 });
18159
18560
  await fsyncPath2(temporary);
18160
- await rename2(temporary, filePath);
18161
- await fsyncPath2(path52.dirname(filePath));
18561
+ await rename3(temporary, filePath);
18562
+ await fsyncPath2(path53.dirname(filePath));
18162
18563
  }
18163
18564
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18164
18565
  const value = {
@@ -18168,13 +18569,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18168
18569
  manifestHash,
18169
18570
  ...detail ? { detail } : {}
18170
18571
  };
18171
- await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
18572
+ await atomicWriteJson(path53.join(artifactDir, "state.json"), value);
18172
18573
  }
18173
18574
  async function directorySize(root) {
18174
- if (!existsSync13(root)) return 0;
18575
+ if (!existsSync14(root)) return 0;
18175
18576
  let total = 0;
18176
18577
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18177
- const entryPath = path52.join(root, entry.name);
18578
+ const entryPath = path53.join(root, entry.name);
18178
18579
  if (entry.isDirectory()) total += await directorySize(entryPath);
18179
18580
  else total += (await lstat7(entryPath)).size;
18180
18581
  }
@@ -18219,9 +18620,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18219
18620
  let rawManifest;
18220
18621
  let state;
18221
18622
  try {
18222
- rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
18623
+ rawManifest = JSON.parse(await readFile11(path53.join(artifactDir, "manifest.json"), "utf8"));
18223
18624
  state = JSON.parse(
18224
- await readFile10(path52.join(artifactDir, "state.json"), "utf8")
18625
+ await readFile11(path53.join(artifactDir, "state.json"), "utf8")
18225
18626
  );
18226
18627
  } catch {
18227
18628
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -18237,10 +18638,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18237
18638
  }
18238
18639
  const entryPaths = /* @__PURE__ */ new Set();
18239
18640
  for (const entry of manifest.entries) {
18240
- 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))) {
18641
+ 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(path53.normalize(entry.path))) {
18241
18642
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18242
18643
  }
18243
- entryPaths.add(path52.normalize(entry.path));
18644
+ entryPaths.add(path53.normalize(entry.path));
18244
18645
  for (const [side, snapshot] of [
18245
18646
  ["before", entry.before],
18246
18647
  ["after", entry.after]
@@ -18254,9 +18655,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18254
18655
  });
18255
18656
  }
18256
18657
  }
18257
- const receiptPath = path52.join(artifactDir, "receipt.json");
18658
+ const receiptPath = path53.join(artifactDir, "receipt.json");
18258
18659
  let receipt;
18259
- if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18660
+ if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
18260
18661
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18261
18662
  }
18262
18663
  return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
@@ -18264,7 +18665,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18264
18665
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18265
18666
  let rawReceipt;
18266
18667
  try {
18267
- rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
18668
+ rawReceipt = JSON.parse(await readFile11(path53.join(artifactDir, "receipt.json"), "utf8"));
18268
18669
  } catch {
18269
18670
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18270
18671
  }
@@ -18287,8 +18688,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18287
18688
  return receipt;
18288
18689
  }
18289
18690
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18290
- const receiptPath = path52.join(artifactDir, "receipt.json");
18291
- if (existsSync13(receiptPath)) {
18691
+ const receiptPath = path53.join(artifactDir, "receipt.json");
18692
+ if (existsSync14(receiptPath)) {
18292
18693
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18293
18694
  }
18294
18695
  const receipt = {
@@ -18305,14 +18706,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18305
18706
  }
18306
18707
  async function checkpointIds(stateDir) {
18307
18708
  const root = checkpointsRoot(stateDir);
18308
- if (!existsSync13(root)) return [];
18709
+ if (!existsSync14(root)) return [];
18309
18710
  return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
18310
18711
  }
18311
18712
  async function artifactRepoRoot(stateDir, checkpointId) {
18312
18713
  const artifactDir = checkpointDir(stateDir, checkpointId);
18313
18714
  try {
18314
18715
  const manifest = JSON.parse(
18315
- await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
18716
+ await readFile11(path53.join(artifactDir, "manifest.json"), "utf8")
18316
18717
  );
18317
18718
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18318
18719
  return canonicalPath(manifest.repoRoot);
@@ -18320,7 +18721,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18320
18721
  } catch {
18321
18722
  }
18322
18723
  try {
18323
- const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
18724
+ const owner = JSON.parse(await readFile11(path53.join(artifactDir, "owner.json"), "utf8"));
18324
18725
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18325
18726
  } catch {
18326
18727
  return null;
@@ -18336,14 +18737,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
18336
18737
  }
18337
18738
  async function cleanupOrphanedStaging(stateDir) {
18338
18739
  const root = checkpointsRoot(stateDir);
18339
- if (!existsSync13(root)) return;
18740
+ if (!existsSync14(root)) return;
18340
18741
  const now = Date.now();
18341
18742
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18342
18743
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18343
- const stagingPath = path52.join(root, entry.name);
18744
+ const stagingPath = path53.join(root, entry.name);
18344
18745
  let stale = false;
18345
18746
  try {
18346
- const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
18747
+ const owner = JSON.parse(await readFile11(path53.join(stagingPath, "owner.json"), "utf8"));
18347
18748
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18348
18749
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18349
18750
  let alive = false;
@@ -18383,9 +18784,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18383
18784
 
18384
18785
  // src/core/recovery/reconcile.ts
18385
18786
  init_fingerprint2();
18386
- import { existsSync as existsSync14 } from "node:fs";
18387
- import { readFile as readFile11 } from "node:fs/promises";
18388
- import path53 from "node:path";
18787
+ import { existsSync as existsSync15 } from "node:fs";
18788
+ import { readFile as readFile12 } from "node:fs/promises";
18789
+ import path54 from "node:path";
18389
18790
  async function matchRecoverySide(resourceRoot, entries, side) {
18390
18791
  for (const entry of entries) {
18391
18792
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18399,9 +18800,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18399
18800
  loaded = await readRecoveryArtifact(stateDir, checkpointId);
18400
18801
  } catch {
18401
18802
  const artifactDir = checkpointDir(stateDir, checkpointId);
18402
- if (existsSync14(artifactDir)) {
18403
- const manifestPath = path53.join(artifactDir, "manifest.json");
18404
- const hash = existsSync14(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
18803
+ if (existsSync15(artifactDir)) {
18804
+ const manifestPath = path54.join(artifactDir, "manifest.json");
18805
+ const hash = existsSync15(manifestPath) ? hashValue(await readFile12(manifestPath, "utf8")) : "unavailable";
18405
18806
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18406
18807
  }
18407
18808
  return "corrupt";
@@ -18436,8 +18837,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18436
18837
 
18437
18838
  // src/core/recovery/resource-identity.ts
18438
18839
  init_fingerprint2();
18439
- import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
18440
- import path54 from "node:path";
18840
+ import { lstat as lstat8, readFile as readFile13, realpath as realpath3 } from "node:fs/promises";
18841
+ import path55 from "node:path";
18441
18842
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18442
18843
  const resolvedRoot = await realpath3(resourceRoot);
18443
18844
  if (resourceKind === "directory") {
@@ -18445,13 +18846,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18445
18846
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18446
18847
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18447
18848
  }
18448
- const dotGit = path54.join(resolvedRoot, ".git");
18849
+ const dotGit = path55.join(resolvedRoot, ".git");
18449
18850
  const gitInfo = await lstat8(dotGit);
18450
18851
  let gitMetadataPath = dotGit;
18451
18852
  if (gitInfo.isFile()) {
18452
- const marker = (await readFile12(dotGit, "utf8")).trim();
18853
+ const marker = (await readFile13(dotGit, "utf8")).trim();
18453
18854
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18454
- gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18855
+ gitMetadataPath = path55.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18455
18856
  } else if (!gitInfo.isDirectory()) {
18456
18857
  throw new Error("recovery_repo_identity_unavailable");
18457
18858
  }
@@ -18516,7 +18917,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
18516
18917
  async function prepareRecoveryCheckpoint(params) {
18517
18918
  const backend = params.backend ?? "git_worktree";
18518
18919
  const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
18519
- await mkdir11(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18920
+ await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18520
18921
  await cleanupOrphanedStaging(params.stateDir);
18521
18922
  await garbageCollect(params.stateDir, params.config, params.repoRoot);
18522
18923
  const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
@@ -18524,16 +18925,16 @@ async function prepareRecoveryCheckpoint(params) {
18524
18925
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18525
18926
  }
18526
18927
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18527
- const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18928
+ const temporary = path56.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18528
18929
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18529
- await mkdir11(temporary, { recursive: true, mode: 448 });
18530
- await atomicWriteJson(path55.join(temporary, "owner.json"), {
18930
+ await mkdir12(temporary, { recursive: true, mode: 448 });
18931
+ await atomicWriteJson(path56.join(temporary, "owner.json"), {
18531
18932
  version: 1,
18532
18933
  pid: process.pid,
18533
18934
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18534
18935
  repoRoot: canonicalPath(params.repoRoot)
18535
18936
  });
18536
- await mkdir11(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
18937
+ await mkdir12(path56.join(temporary, "blobs"), { recursive: true, mode: 448 });
18537
18938
  try {
18538
18939
  const entries = [];
18539
18940
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18542,8 +18943,8 @@ async function prepareRecoveryCheckpoint(params) {
18542
18943
  )) {
18543
18944
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18544
18945
  if (protectedRoots.some((root) => {
18545
- const relative = path55.relative(root, target);
18546
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
18946
+ const relative = path56.relative(root, target);
18947
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path56.sep}`) && !path56.isAbsolute(relative);
18547
18948
  })) {
18548
18949
  throw new Error("recovery_protected_path");
18549
18950
  }
@@ -18555,7 +18956,7 @@ async function prepareRecoveryCheckpoint(params) {
18555
18956
  entries.push({
18556
18957
  path: change.relativePath,
18557
18958
  before: await captureRecoverySnapshot(baseline, {
18558
- blobDir: path55.join(temporary, "blobs")
18959
+ blobDir: path56.join(temporary, "blobs")
18559
18960
  }),
18560
18961
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18561
18962
  });
@@ -18592,12 +18993,12 @@ async function prepareRecoveryCheckpoint(params) {
18592
18993
  entries
18593
18994
  };
18594
18995
  const manifestHash = hashValue(canonicalStringify(manifest));
18595
- await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
18996
+ await atomicWriteJson(path56.join(temporary, "manifest.json"), manifest);
18596
18997
  await writeRecoveryState(temporary, "prepared", manifestHash);
18597
18998
  await fsyncPath2(temporary);
18598
18999
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
18599
19000
  if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18600
- await rename3(temporary, finalDir);
19001
+ await rename4(temporary, finalDir);
18601
19002
  await fsyncPath2(checkpointsRoot(params.stateDir));
18602
19003
  return {
18603
19004
  checkpointId,
@@ -18635,7 +19036,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18635
19036
  } catch {
18636
19037
  try {
18637
19038
  const raw = JSON.parse(
18638
- await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
19039
+ await readFile14(path56.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18639
19040
  );
18640
19041
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18641
19042
  } catch {
@@ -18669,7 +19070,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18669
19070
  } catch {
18670
19071
  try {
18671
19072
  const manifest = JSON.parse(
18672
- await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
19073
+ await readFile14(path56.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18673
19074
  );
18674
19075
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18675
19076
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18694,12 +19095,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18694
19095
  async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18695
19096
  const root = checkpointsRoot(stateDir);
18696
19097
  if (!repoRoot) return directorySize(root);
18697
- if (!existsSync15(root)) return 0;
19098
+ if (!existsSync16(root)) return 0;
18698
19099
  const expected = canonicalPath(repoRoot);
18699
19100
  let total = 0;
18700
19101
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18701
19102
  if (!entry.isDirectory()) continue;
18702
- const entryPath = path55.join(root, entry.name);
19103
+ const entryPath = path56.join(root, entry.name);
18703
19104
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18704
19105
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18705
19106
  total += await directorySize(entryPath);
@@ -18708,7 +19109,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18708
19109
  }
18709
19110
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18710
19111
  try {
18711
- const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
19112
+ const owner = JSON.parse(await readFile14(path56.join(entryPath, "owner.json"), "utf8"));
18712
19113
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18713
19114
  total += await directorySize(entryPath);
18714
19115
  }
@@ -18750,16 +19151,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
18750
19151
  init_scrub();
18751
19152
 
18752
19153
  // src/core/transactional/file-checkpoint-backend.ts
18753
- import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
19154
+ import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18754
19155
  import os5 from "node:os";
18755
- import path59 from "node:path";
19156
+ import path60 from "node:path";
18756
19157
 
18757
19158
  // src/core/transactional/file-checkpoint-git.ts
18758
19159
  init_path_utils();
18759
19160
  import { spawn as spawn8 } from "node:child_process";
18760
19161
  import { createHash as createHash13 } from "node:crypto";
18761
- import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
18762
- import path56 from "node:path";
19162
+ import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile15 } from "node:fs/promises";
19163
+ import path57 from "node:path";
18763
19164
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18764
19165
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18765
19166
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18793,7 +19194,7 @@ function rethrowStableFileCheckpointError(error) {
18793
19194
  }
18794
19195
  async function rootGitMetadataPresent(repoRoot) {
18795
19196
  try {
18796
- await lstat9(path56.join(repoRoot, ".git"));
19197
+ await lstat9(path57.join(repoRoot, ".git"));
18797
19198
  return true;
18798
19199
  } catch {
18799
19200
  return false;
@@ -18842,10 +19243,10 @@ function execGit2(repoRoot, args) {
18842
19243
  }
18843
19244
  async function resolveGitPath(repoRoot, gitPath) {
18844
19245
  const trimmed = gitPath.trim();
18845
- if (path56.isAbsolute(trimmed)) {
19246
+ if (path57.isAbsolute(trimmed)) {
18846
19247
  return trimmed;
18847
19248
  }
18848
- return path56.join(repoRoot, trimmed);
19249
+ return path57.join(repoRoot, trimmed);
18849
19250
  }
18850
19251
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18851
19252
  await execGit2(sourceRoot, [
@@ -18887,7 +19288,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18887
19288
  destinationRoot,
18888
19289
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18889
19290
  );
18890
- const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
19291
+ const destinationShared = path57.join(destinationGitDir, path57.basename(sourceShared));
18891
19292
  try {
18892
19293
  await copyFile3(sourceShared, destinationShared);
18893
19294
  } catch (error) {
@@ -18896,14 +19297,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18896
19297
  }
18897
19298
  async function readGitFile(gitDir, relativePath) {
18898
19299
  try {
18899
- return await readFile14(path56.join(gitDir, relativePath));
19300
+ return await readFile15(path57.join(gitDir, relativePath));
18900
19301
  } catch {
18901
19302
  return null;
18902
19303
  }
18903
19304
  }
18904
19305
  async function readAbsoluteGitFile(absolutePath) {
18905
19306
  try {
18906
- return await readFile14(absolutePath);
19307
+ return await readFile15(absolutePath);
18907
19308
  } catch {
18908
19309
  return null;
18909
19310
  }
@@ -18920,8 +19321,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18920
19321
  repoRoot,
18921
19322
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18922
19323
  );
18923
- const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
18924
- const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19324
+ const relative = path57.resolve(resolved).startsWith(path57.resolve(gitDir)) ? path57.relative(gitDir, resolved) : resolved;
19325
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path57.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18925
19326
  if (content !== null) {
18926
19327
  hashGitFileContent(hash, gitPath, content);
18927
19328
  }
@@ -18931,13 +19332,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18931
19332
  async function hashGitTree(gitDir, relativeDir, hash) {
18932
19333
  let names;
18933
19334
  try {
18934
- names = await readdir4(path56.join(gitDir, relativeDir));
19335
+ names = await readdir4(path57.join(gitDir, relativeDir));
18935
19336
  } catch {
18936
19337
  return;
18937
19338
  }
18938
19339
  for (const name of names.sort()) {
18939
- const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
18940
- const absolutePath = path56.join(gitDir, relativePath);
19340
+ const relativePath = relativeDir ? path57.join(relativeDir, name) : name;
19341
+ const absolutePath = path57.join(gitDir, relativePath);
18941
19342
  let childNames = null;
18942
19343
  try {
18943
19344
  childNames = await readdir4(absolutePath);
@@ -18959,7 +19360,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18959
19360
  }
18960
19361
  async function computeGitMetadataFingerprint(repoRoot) {
18961
19362
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18962
- const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
19363
+ const gitDir = path57.isAbsolute(gitDirRel) ? gitDirRel : path57.join(repoRoot, gitDirRel);
18963
19364
  const hash = createHash13("sha256");
18964
19365
  for (const file of [
18965
19366
  "HEAD",
@@ -18982,8 +19383,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18982
19383
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18983
19384
  if (sharedIndex) {
18984
19385
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18985
- const relative = path56.relative(gitDir, resolved);
18986
- const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19386
+ const relative = path57.relative(gitDir, resolved);
19387
+ const content = relative && !relative.startsWith("..") && !path57.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18987
19388
  if (content !== null) {
18988
19389
  hashGitFileContent(hash, "shared-index", content);
18989
19390
  }
@@ -18994,7 +19395,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18994
19395
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18995
19396
  }
18996
19397
  try {
18997
- const rootGitPath = path56.join(repoRoot, ".git");
19398
+ const rootGitPath = path57.join(repoRoot, ".git");
18998
19399
  const rootGitInfo = await lstat9(rootGitPath);
18999
19400
  if (rootGitInfo.isFile()) {
19000
19401
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -19010,14 +19411,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
19010
19411
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
19011
19412
  const resolvedCwd = canonicalPath(cwd);
19012
19413
  const resourceCanonical = canonicalPath(resourceRoot);
19013
- const relative = path56.relative(resourceCanonical, resolvedCwd);
19414
+ const relative = path57.relative(resourceCanonical, resolvedCwd);
19014
19415
  if (relative === "" || relative === ".") {
19015
19416
  return "";
19016
19417
  }
19017
- if (relative.startsWith("..") || path56.isAbsolute(relative)) {
19418
+ if (relative.startsWith("..") || path57.isAbsolute(relative)) {
19018
19419
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
19019
19420
  }
19020
- return relative.split(path56.sep).join("/");
19421
+ return relative.split(path57.sep).join("/");
19021
19422
  }
19022
19423
 
19023
19424
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -19038,8 +19439,8 @@ function fileCheckpointIsolationReason(context) {
19038
19439
  }
19039
19440
 
19040
19441
  // src/core/transactional/file-checkpoint-staging.ts
19041
- import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
19042
- import path57 from "node:path";
19442
+ import { readdir as readdir5, readFile as readFile16, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
19443
+ import path58 from "node:path";
19043
19444
  function isOwnerProcessAlive(pid) {
19044
19445
  try {
19045
19446
  process.kill(pid, 0);
@@ -19049,12 +19450,12 @@ function isOwnerProcessAlive(pid) {
19049
19450
  }
19050
19451
  }
19051
19452
  async function writeOwnerMarker(stagingRoot, marker) {
19052
- await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19453
+ await writeFile8(path58.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19053
19454
  `, "utf8");
19054
19455
  }
19055
19456
  async function readOwnerMarker(stagingRoot) {
19056
19457
  try {
19057
- const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
19458
+ const raw = await readFile16(path58.join(stagingRoot, "owner.json"), "utf8");
19058
19459
  return JSON.parse(raw.trim());
19059
19460
  } catch {
19060
19461
  return null;
@@ -19072,7 +19473,7 @@ async function collectDeadOwnerStaging(parentDir) {
19072
19473
  if (!name.startsWith("belay-file-checkpoint-")) {
19073
19474
  continue;
19074
19475
  }
19075
- const stagingRoot = path57.join(parentDir, name);
19476
+ const stagingRoot = path58.join(parentDir, name);
19076
19477
  const marker = await readOwnerMarker(stagingRoot);
19077
19478
  if (!marker) {
19078
19479
  dead.push(stagingRoot);
@@ -19095,7 +19496,7 @@ import { constants as fsConstants2 } from "node:fs";
19095
19496
  import {
19096
19497
  copyFile as copyFile4,
19097
19498
  lstat as lstat10,
19098
- mkdir as mkdir12,
19499
+ mkdir as mkdir13,
19099
19500
  mkdtemp as mkdtemp4,
19100
19501
  readlink as readlink5,
19101
19502
  rm as rm8,
@@ -19104,17 +19505,17 @@ import {
19104
19505
  writeFile as writeFile9
19105
19506
  } from "node:fs/promises";
19106
19507
  import os4 from "node:os";
19107
- import path58 from "node:path";
19508
+ import path59 from "node:path";
19108
19509
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
19109
19510
  async function chmodSafe2(target, mode) {
19110
19511
  try {
19111
- const { chmod: chmod4 } = await import("node:fs/promises");
19112
- await chmod4(target, mode & 511);
19512
+ const { chmod: chmod5 } = await import("node:fs/promises");
19513
+ await chmod5(target, mode & 511);
19113
19514
  } catch {
19114
19515
  }
19115
19516
  }
19116
19517
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
19117
- await mkdir12(path58.dirname(destinationPath), { recursive: true });
19518
+ await mkdir13(path59.dirname(destinationPath), { recursive: true });
19118
19519
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
19119
19520
  try {
19120
19521
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -19136,11 +19537,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
19136
19537
  const info = await lstat10(sourcePath);
19137
19538
  await rm8(destinationPath, { force: true, recursive: false });
19138
19539
  if (info.isDirectory() && !info.isSymbolicLink()) {
19139
- await mkdir12(destinationPath, { recursive: true, mode: info.mode & 511 });
19540
+ await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
19140
19541
  return strategy;
19141
19542
  }
19142
19543
  if (info.isSymbolicLink()) {
19143
- await mkdir12(path58.dirname(destinationPath), { recursive: true });
19544
+ await mkdir13(path59.dirname(destinationPath), { recursive: true });
19144
19545
  await symlink4(await readlink5(sourcePath), destinationPath);
19145
19546
  return strategy;
19146
19547
  }
@@ -19196,9 +19597,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
19196
19597
  async function probeFileCloneStrategy() {
19197
19598
  let tempDir = null;
19198
19599
  try {
19199
- tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
19200
- const source = path58.join(tempDir, "source.txt");
19201
- const destination = path58.join(tempDir, "dest.txt");
19600
+ tempDir = await mkdtemp4(path59.join(os4.tmpdir(), "belay-clone-probe-"));
19601
+ const source = path59.join(tempDir, "source.txt");
19602
+ const destination = path59.join(tempDir, "dest.txt");
19202
19603
  await writeFile9(source, "probe\n");
19203
19604
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
19204
19605
  try {
@@ -19351,11 +19752,11 @@ async function protectedRootState(root) {
19351
19752
  return `directory:${node.hash}:${index.treeHash}`;
19352
19753
  }
19353
19754
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19354
- const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
19355
- if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
19755
+ const relative = path60.relative(path60.resolve(resourceRoot), path60.resolve(protectedRoot));
19756
+ if (relative === "" || relative.startsWith("..") || path60.isAbsolute(relative)) {
19356
19757
  return null;
19357
19758
  }
19358
- return path59.join(executionRoot, relative);
19759
+ return path60.join(executionRoot, relative);
19359
19760
  }
19360
19761
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19361
19762
  const states = /* @__PURE__ */ new Map();
@@ -19380,15 +19781,15 @@ async function directoryByteSize(root, deadlineMs) {
19380
19781
  }
19381
19782
  let total = 0;
19382
19783
  for (const name of await readdir6(root)) {
19383
- total += await directoryByteSize(path59.join(root, name), deadlineMs);
19784
+ total += await directoryByteSize(path60.join(root, name), deadlineMs);
19384
19785
  }
19385
19786
  return total;
19386
19787
  }
19387
19788
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19388
19789
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
19389
- const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
19390
- const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
19391
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
19790
+ const sourceGitDir = path60.isAbsolute(gitDirRel) ? gitDirRel : path60.join(sourceRoot, gitDirRel);
19791
+ const relativeGitDir = path60.relative(path60.resolve(sourceRoot), path60.resolve(sourceGitDir));
19792
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path60.join(destinationRoot, relativeGitDir) : path60.join(destinationRoot, ".git");
19392
19793
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19393
19794
  }
19394
19795
  async function prepareDirtyGitSnapshot(context) {
@@ -19397,7 +19798,7 @@ async function prepareDirtyGitSnapshot(context) {
19397
19798
  const quotas = context.fileCheckpoint;
19398
19799
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19399
19800
  await removeDeadOwnerStaging(os5.tmpdir());
19400
- const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19801
+ const stagingRoot = await mkdtemp5(path60.join(os5.tmpdir(), "belay-file-checkpoint-"));
19401
19802
  await writeOwnerMarker(stagingRoot, {
19402
19803
  version: 1,
19403
19804
  pid: process.pid,
@@ -19405,8 +19806,8 @@ async function prepareDirtyGitSnapshot(context) {
19405
19806
  resourceRoot: context.repoRoot,
19406
19807
  backend: "file_checkpoint"
19407
19808
  });
19408
- const baselineRoot = path59.join(stagingRoot, "baseline");
19409
- const executionRoot = path59.join(stagingRoot, "execution");
19809
+ const baselineRoot = path60.join(stagingRoot, "baseline");
19810
+ const executionRoot = path60.join(stagingRoot, "execution");
19410
19811
  try {
19411
19812
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19412
19813
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19440,7 +19841,7 @@ async function prepareDirtyGitSnapshot(context) {
19440
19841
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19441
19842
  }
19442
19843
  await writeFile10(
19443
- path59.join(stagingRoot, "baseline-index.json"),
19844
+ path60.join(stagingRoot, "baseline-index.json"),
19444
19845
  `${JSON.stringify(baselineIndex)}
19445
19846
  `,
19446
19847
  "utf8"
@@ -19490,7 +19891,7 @@ async function prepareNonGitSnapshot(context) {
19490
19891
  const quotas = context.fileCheckpoint;
19491
19892
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19492
19893
  await removeDeadOwnerStaging(os5.tmpdir());
19493
- const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19894
+ const stagingRoot = await mkdtemp5(path60.join(os5.tmpdir(), "belay-file-checkpoint-"));
19494
19895
  await writeOwnerMarker(stagingRoot, {
19495
19896
  version: 1,
19496
19897
  pid: process.pid,
@@ -19498,8 +19899,8 @@ async function prepareNonGitSnapshot(context) {
19498
19899
  resourceRoot: context.repoRoot,
19499
19900
  backend: "file_checkpoint"
19500
19901
  });
19501
- const baselineRoot = path59.join(stagingRoot, "baseline");
19502
- const executionRoot = path59.join(stagingRoot, "execution");
19902
+ const baselineRoot = path60.join(stagingRoot, "baseline");
19903
+ const executionRoot = path60.join(stagingRoot, "execution");
19503
19904
  try {
19504
19905
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19505
19906
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19508,7 +19909,7 @@ async function prepareNonGitSnapshot(context) {
19508
19909
  quotas,
19509
19910
  deadlineMs
19510
19911
  });
19511
- await mkdir13(baselineRoot, { recursive: true });
19912
+ await mkdir14(baselineRoot, { recursive: true });
19512
19913
  const baselineIndex = await buildFileTreeIndex({
19513
19914
  resourceRoot: baselineRoot,
19514
19915
  excludedRoots,
@@ -19528,7 +19929,7 @@ async function prepareNonGitSnapshot(context) {
19528
19929
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19529
19930
  }
19530
19931
  await writeFile10(
19531
- path59.join(stagingRoot, "baseline-index.json"),
19932
+ path60.join(stagingRoot, "baseline-index.json"),
19532
19933
  `${JSON.stringify(baselineIndex)}
19533
19934
  `,
19534
19935
  "utf8"
@@ -19538,7 +19939,7 @@ async function prepareNonGitSnapshot(context) {
19538
19939
  quotas,
19539
19940
  deadlineMs
19540
19941
  });
19541
- await mkdir13(executionRoot, { recursive: true });
19942
+ await mkdir14(executionRoot, { recursive: true });
19542
19943
  const finalSourceIndex = await buildFileTreeIndex({
19543
19944
  resourceRoot: context.repoRoot,
19544
19945
  excludedRoots,
@@ -19881,10 +20282,10 @@ async function selectTransactionalBackend(context) {
19881
20282
  }
19882
20283
 
19883
20284
  // src/core/transactional/diff-evaluator.ts
19884
- import path60 from "node:path";
20285
+ import path61 from "node:path";
19885
20286
  init_path_utils();
19886
20287
  function categorizeChange(change, ctx) {
19887
- const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
20288
+ const absolutePath = canonicalPath(path61.join(ctx.repoRoot, change.relativePath));
19888
20289
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19889
20290
  return "repo_outside";
19890
20291
  }
@@ -20434,17 +20835,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
20434
20835
  }
20435
20836
 
20436
20837
  // src/core/notify.ts
20838
+ init_path_utils();
20437
20839
  import { execFile } from "node:child_process";
20840
+ import path62 from "node:path";
20438
20841
  import { promisify } from "node:util";
20439
20842
  var execFileAsync = promisify(execFile);
20440
- async function notifyDeny(config, event) {
20441
- const payload = JSON.stringify(event);
20442
- if (config.webhookUrl) {
20843
+ var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
20844
+ function webhookConfigIssue(url) {
20845
+ let parsed;
20846
+ try {
20847
+ parsed = new URL(url);
20848
+ } catch {
20849
+ return `notifications.webhookUrl is invalid: ${url}`;
20850
+ }
20851
+ if (parsed.protocol === "https:") {
20852
+ return null;
20853
+ }
20854
+ const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
20855
+ if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
20856
+ return null;
20857
+ }
20858
+ return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
20859
+ }
20860
+ function commandHookConfigIssue(commandHook, repoRoot) {
20861
+ if (!path62.isAbsolute(commandHook)) {
20862
+ return `notifications.commandHook must be an absolute path: ${commandHook}`;
20863
+ }
20864
+ if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
20865
+ return `notifications.commandHook must not be inside the repository: ${commandHook}`;
20866
+ }
20867
+ return null;
20868
+ }
20869
+ async function notifyDeny(config, event, deps = {
20870
+ fetch: globalThis.fetch.bind(globalThis),
20871
+ execFile: (file, args, options) => execFileAsync(file, [...args], options)
20872
+ }) {
20873
+ const payload = JSON.stringify({
20874
+ approvalId: event.approvalId,
20875
+ reason: event.reason,
20876
+ summary: event.summary,
20877
+ repoRoot: event.repoRoot,
20878
+ fingerprint: event.fingerprint
20879
+ });
20880
+ const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
20881
+ if (config.webhookUrl && !webhookIssue) {
20443
20882
  try {
20444
20883
  const controller = new AbortController();
20445
20884
  const timeout = setTimeout(() => controller.abort(), 5e3);
20446
20885
  try {
20447
- await fetch(config.webhookUrl, {
20886
+ await deps.fetch(config.webhookUrl, {
20448
20887
  method: "POST",
20449
20888
  headers: { "content-type": "application/json" },
20450
20889
  body: payload,
@@ -20456,17 +20895,16 @@ async function notifyDeny(config, event) {
20456
20895
  } catch {
20457
20896
  }
20458
20897
  }
20459
- if (config.commandHook) {
20898
+ const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
20899
+ if (config.commandHook && !commandHookIssue) {
20460
20900
  try {
20461
- await execFileAsync(config.commandHook, [], {
20901
+ await deps.execFile(config.commandHook, [], {
20462
20902
  env: {
20463
- ...process.env,
20464
20903
  BELAY_APPROVAL_ID: event.approvalId,
20465
20904
  BELAY_REASON: event.reason,
20466
20905
  BELAY_SUMMARY: event.summary,
20467
20906
  BELAY_REPO_ROOT: event.repoRoot,
20468
- BELAY_FINGERPRINT: event.fingerprint,
20469
- BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
20907
+ BELAY_FINGERPRINT: event.fingerprint
20470
20908
  }
20471
20909
  });
20472
20910
  } catch {
@@ -20476,9 +20914,10 @@ async function notifyDeny(config, event) {
20476
20914
 
20477
20915
  // src/adapters/shared/gate-runtime.ts
20478
20916
  init_path_utils();
20917
+ init_repo_config_trust();
20479
20918
 
20480
20919
  // src/adapters/layouts/protected-paths.ts
20481
- import path61 from "node:path";
20920
+ import path63 from "node:path";
20482
20921
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20483
20922
  const roots = [
20484
20923
  layout.configPath(repoRoot),
@@ -20490,7 +20929,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20490
20929
  if (controlPlaneDir) {
20491
20930
  roots.push(controlPlaneDir);
20492
20931
  }
20493
- return roots.map((entry) => path61.resolve(entry));
20932
+ return roots.map((entry) => path63.resolve(entry));
20494
20933
  }
20495
20934
 
20496
20935
  // src/adapters/shared/gate-runtime.ts
@@ -20531,7 +20970,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
20531
20970
  }
20532
20971
  async function loadJsonFile(filePath, fallback) {
20533
20972
  try {
20534
- const raw = await readFile16(filePath, "utf8");
20973
+ const raw = await readFile17(filePath, "utf8");
20535
20974
  return JSON.parse(raw);
20536
20975
  } catch {
20537
20976
  return fallback;
@@ -20540,11 +20979,18 @@ async function loadJsonFile(filePath, fallback) {
20540
20979
  function createDefaultGateRuntimeDeps() {
20541
20980
  return {
20542
20981
  async readConfig(configPath) {
20543
- return loadJsonFile(configPath, {});
20982
+ try {
20983
+ return JSON.parse(await readFile17(configPath, "utf8"));
20984
+ } catch (error) {
20985
+ if (error.code === "ENOENT") {
20986
+ return {};
20987
+ }
20988
+ throw error;
20989
+ }
20544
20990
  },
20545
20991
  async appendAudit(ctx, event) {
20546
- const auditPath = path62.join(ctx.repoRoot, ctx.config.audit.logPath);
20547
- await mkdir14(path62.dirname(auditPath), { recursive: true });
20992
+ const auditPath = path64.join(ctx.repoRoot, ctx.config.audit.logPath);
20993
+ await mkdir15(path64.dirname(auditPath), { recursive: true });
20548
20994
  const provenance = auditProvenance(ctx.config);
20549
20995
  const record = {
20550
20996
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20577,7 +21023,7 @@ function createDefaultGateRuntimeDeps() {
20577
21023
  };
20578
21024
  },
20579
21025
  async writeApprovals(filePath, state) {
20580
- await mkdir14(path62.dirname(filePath), { recursive: true });
21026
+ await mkdir15(path64.dirname(filePath), { recursive: true });
20581
21027
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20582
21028
  `, "utf8");
20583
21029
  },
@@ -20598,10 +21044,11 @@ function createDefaultGateRuntimeDeps() {
20598
21044
  }
20599
21045
  async function resolveGateConfig(ctx, deps) {
20600
21046
  const loaded = await deps.readConfig(ctx.configPath);
21047
+ await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
20601
21048
  let teamConfig = null;
20602
21049
  const teamPath = teamConfigPath();
20603
- if (existsSync16(teamPath)) {
20604
- teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
21050
+ if (existsSync17(teamPath)) {
21051
+ teamConfig = JSON.parse(await readFile17(teamPath, "utf8"));
20605
21052
  }
20606
21053
  return resolveLayeredConfig({
20607
21054
  repoConfig: loaded,
@@ -20739,7 +21186,7 @@ function deriveWorkspaceRootScopeHint(params) {
20739
21186
  if (!targetPath) {
20740
21187
  return void 0;
20741
21188
  }
20742
- const candidateRoot = canonicalPath(path62.dirname(targetPath));
21189
+ const candidateRoot = canonicalPath(path64.dirname(targetPath));
20743
21190
  const validation = validateTrustedWorkspaceRootCandidate({
20744
21191
  candidatePath: candidateRoot,
20745
21192
  repoRoot: action.repoRoot,
@@ -21315,7 +21762,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21315
21762
  event: auditEvent,
21316
21763
  sourceEvent,
21317
21764
  kind,
21318
- repoLabel: path62.basename(ctx.repoRoot),
21765
+ repoLabel: path64.basename(ctx.repoRoot),
21319
21766
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21320
21767
  fingerprint: result.fingerprint,
21321
21768
  summary: result.normalizedCommand ?? result.summary ?? "",
@@ -21347,21 +21794,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21347
21794
  if (created) {
21348
21795
  await recordGateApprovalAsk(stateDir, result.reason, false);
21349
21796
  }
21350
- let approvalToken;
21351
- try {
21352
- approvalToken = await issueApprovalToken(
21353
- {
21354
- approvalId: approval.approvalId,
21355
- fingerprint: approval.fingerprint,
21356
- repoRoot: approval.repoRoot,
21357
- issuedAt: approval.createdAt,
21358
- expiresAt: approval.expiresAt
21359
- },
21360
- configuredControlPlaneDir(ctx.config)
21361
- );
21362
- } catch {
21363
- approvalToken = void 0;
21364
- }
21365
21797
  const denialReason = failure?.reason ?? result.reason;
21366
21798
  if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
21367
21799
  await notifyDeny(ctx.config.notifications, {
@@ -21369,8 +21801,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21369
21801
  reason: denialReason,
21370
21802
  summary: result.normalizedCommand ?? result.summary ?? "",
21371
21803
  repoRoot: ctx.repoRoot,
21372
- fingerprint: result.fingerprint,
21373
- approvalToken
21804
+ fingerprint: result.fingerprint
21374
21805
  });
21375
21806
  }
21376
21807
  const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
@@ -21762,38 +22193,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
21762
22193
  }
21763
22194
 
21764
22195
  // src/adapters/shared/repo-root.ts
21765
- import { existsSync as existsSync17 } from "node:fs";
21766
- import path63 from "node:path";
22196
+ import { existsSync as existsSync18 } from "node:fs";
22197
+ import path65 from "node:path";
21767
22198
  function belayConfigPath(current, adapterName) {
21768
22199
  if (adapterName === "cursor") {
21769
- return path63.join(current, ".cursor", "belay.config.json");
22200
+ return path65.join(current, ".cursor", "belay.config.json");
21770
22201
  }
21771
22202
  if (adapterName === "claude") {
21772
- return path63.join(current, ".claude", "belay.config.json");
22203
+ return path65.join(current, ".claude", "belay.config.json");
21773
22204
  }
21774
- return path63.join(current, ".codex", "belay.config.json");
22205
+ return path65.join(current, ".codex", "belay.config.json");
21775
22206
  }
21776
22207
  function markerMatches(current, marker, layout) {
21777
- const markerPath = path63.join(current, marker);
21778
- if (!existsSync17(markerPath)) {
22208
+ const markerPath = path65.join(current, marker);
22209
+ if (!existsSync18(markerPath)) {
21779
22210
  return false;
21780
22211
  }
21781
22212
  if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
21782
- return existsSync17(belayConfigPath(current, layout.name));
22213
+ return existsSync18(belayConfigPath(current, layout.name));
21783
22214
  }
21784
22215
  return true;
21785
22216
  }
21786
22217
  function findRepoRoot(startPath, layout) {
21787
- let current = path63.resolve(startPath);
22218
+ let current = path65.resolve(startPath);
21788
22219
  while (true) {
21789
22220
  for (const marker of layout.repoRootMarkers) {
21790
22221
  if (markerMatches(current, marker, layout)) {
21791
22222
  return current;
21792
22223
  }
21793
22224
  }
21794
- const parent = path63.dirname(current);
22225
+ const parent = path65.dirname(current);
21795
22226
  if (parent === current) {
21796
- return path63.resolve(startPath);
22227
+ return path65.resolve(startPath);
21797
22228
  }
21798
22229
  current = parent;
21799
22230
  }
@@ -21801,7 +22232,7 @@ function findRepoRoot(startPath, layout) {
21801
22232
 
21802
22233
  // src/adapters/cursor/cwd-resolution.ts
21803
22234
  import os6 from "node:os";
21804
- import path64 from "node:path";
22235
+ import path66 from "node:path";
21805
22236
  import { fileURLToPath as fileURLToPath2 } from "node:url";
21806
22237
  function nonEmptyPathString(value) {
21807
22238
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
@@ -21813,36 +22244,36 @@ function resolveCursorActionCwdDetails(payload, fallback = process.cwd()) {
21813
22244
  const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
21814
22245
  if (toolInputWorkingDirectory) {
21815
22246
  return {
21816
- cwd: path64.resolve(toolInputWorkingDirectory),
22247
+ cwd: path66.resolve(toolInputWorkingDirectory),
21817
22248
  source: "tool_input.working_directory",
21818
22249
  fromPayload: true
21819
22250
  };
21820
22251
  }
21821
22252
  if (topLevelCwd) {
21822
22253
  return {
21823
- cwd: path64.resolve(topLevelCwd),
22254
+ cwd: path66.resolve(topLevelCwd),
21824
22255
  source: "cwd",
21825
22256
  fromPayload: true
21826
22257
  };
21827
22258
  }
21828
22259
  if (workspaceRoot) {
21829
22260
  return {
21830
- cwd: path64.resolve(workspaceRoot),
22261
+ cwd: path66.resolve(workspaceRoot),
21831
22262
  source: "workspace_roots",
21832
22263
  fromPayload: true
21833
22264
  };
21834
22265
  }
21835
22266
  return {
21836
- cwd: path64.resolve(fallback),
22267
+ cwd: path66.resolve(fallback),
21837
22268
  source: "fallback",
21838
22269
  fromPayload: false
21839
22270
  };
21840
22271
  }
21841
22272
  function isGlobalCursorHookRuntime() {
21842
22273
  try {
21843
- const runtimePath = path64.resolve(fileURLToPath2(import.meta.url));
21844
- const globalRuntimeDir = path64.resolve(path64.join(os6.homedir(), ".cursor", "belay", "runtime"));
21845
- return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${path64.sep}`);
22274
+ const runtimePath = path66.resolve(fileURLToPath2(import.meta.url));
22275
+ const globalRuntimeDir = path66.resolve(path66.join(os6.homedir(), ".cursor", "belay", "runtime"));
22276
+ return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${path66.sep}`);
21846
22277
  } catch {
21847
22278
  return false;
21848
22279
  }
@@ -21892,7 +22323,7 @@ function resolveCursorToolActionCwdDetails(payload, fallback, eventName, toolNam
21892
22323
  function collectCandidateRepoRoots(payload, resolution) {
21893
22324
  const roots = [];
21894
22325
  const add = (value) => {
21895
- const resolved = path65.resolve(value);
22326
+ const resolved = path67.resolve(value);
21896
22327
  if (!roots.includes(resolved)) {
21897
22328
  roots.push(resolved);
21898
22329
  }
@@ -21954,6 +22385,9 @@ function isSubagentEvent(payload, eventName) {
21954
22385
  function isFileMutationTool(toolName) {
21955
22386
  return toolName === "Write" || toolName === "StrReplace" || toolName === "Delete";
21956
22387
  }
22388
+ function isCursorPreToolUseEvent(eventName) {
22389
+ return eventName === "preToolUse" || eventName === "PreToolUse";
22390
+ }
21957
22391
  async function handleBeforeSubmitPromptHook(payload) {
21958
22392
  try {
21959
22393
  const prompt = String(payload.prompt ?? "");
@@ -21978,7 +22412,13 @@ async function handleBeforeSubmitPromptHook(payload) {
21978
22412
  ...result.user_message ? { user_message: result.user_message } : {},
21979
22413
  ...result.replay ? { replay: result.replay } : {}
21980
22414
  };
21981
- } catch {
22415
+ } catch (error) {
22416
+ if (isRepoConfigTrustError(error)) {
22417
+ return {
22418
+ continue: false,
22419
+ user_message: error.message
22420
+ };
22421
+ }
21982
22422
  return {
21983
22423
  continue: false,
21984
22424
  user_message: "belay failed while processing approval state. Run belay doctor, then retry."
@@ -22009,7 +22449,13 @@ async function handleShellGateHook(payload) {
22009
22449
  sourceEvent: "beforeShellExecution"
22010
22450
  });
22011
22451
  return gateVerdictToCursorResponse(verdict2);
22012
- } catch {
22452
+ } catch (error) {
22453
+ if (isRepoConfigTrustError(error)) {
22454
+ return {
22455
+ permission: "deny",
22456
+ user_message: error.message
22457
+ };
22458
+ }
22013
22459
  return {
22014
22460
  permission: "deny",
22015
22461
  user_message: "belay failed while classifying this shell command. Run belay doctor, then retry."
@@ -22022,6 +22468,9 @@ async function runShellGateHook() {
22022
22468
  async function handleToolGateHook(eventName, payload) {
22023
22469
  try {
22024
22470
  const toolName = String(payload.tool_name ?? "");
22471
+ if (isCursorPreToolUseEvent(eventName) && toolName === "Shell") {
22472
+ return { permission: "allow" };
22473
+ }
22025
22474
  const resolution = resolveCursorToolActionCwdDetails(
22026
22475
  payload,
22027
22476
  process2.cwd(),
@@ -22046,9 +22495,9 @@ async function handleToolGateHook(eventName, payload) {
22046
22495
  });
22047
22496
  return gateVerdictToCursorResponse(verdict2);
22048
22497
  }
22049
- if (toolName === "Shell") {
22498
+ if (isFileMutationTool(toolName)) {
22050
22499
  const verdict2 = await evaluateGatedAction(ctx, deps, {
22051
- kind: "shell",
22500
+ kind: "tool",
22052
22501
  cwd,
22053
22502
  payload,
22054
22503
  toolName,
@@ -22056,27 +22505,33 @@ async function handleToolGateHook(eventName, payload) {
22056
22505
  });
22057
22506
  return gateVerdictToCursorResponse(verdict2);
22058
22507
  }
22059
- if (isFileMutationTool(toolName)) {
22508
+ if (payload.tool_name === "Task") {
22060
22509
  const verdict2 = await evaluateGatedAction(ctx, deps, {
22061
- kind: "tool",
22510
+ kind: "subagent",
22062
22511
  cwd,
22063
22512
  payload,
22064
- toolName,
22065
22513
  sourceEvent: eventName
22066
22514
  });
22067
22515
  return gateVerdictToCursorResponse(verdict2);
22068
22516
  }
22069
- if (payload.tool_name === "Task") {
22517
+ if (isCursorPreToolUseEvent(eventName)) {
22070
22518
  const verdict2 = await evaluateGatedAction(ctx, deps, {
22071
- kind: "subagent",
22519
+ kind: "tool",
22072
22520
  cwd,
22073
22521
  payload,
22522
+ toolName,
22074
22523
  sourceEvent: eventName
22075
22524
  });
22076
22525
  return gateVerdictToCursorResponse(verdict2);
22077
22526
  }
22078
22527
  return { permission: "allow" };
22079
- } catch {
22528
+ } catch (error) {
22529
+ if (isRepoConfigTrustError(error)) {
22530
+ return {
22531
+ permission: "deny",
22532
+ user_message: error.message
22533
+ };
22534
+ }
22080
22535
  return {
22081
22536
  permission: "deny",
22082
22537
  user_message: "belay failed while classifying this tool action. Run belay doctor, then retry."