@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
@@ -1504,7 +1504,7 @@ function normalizeConfig(config) {
1504
1504
  policy: {
1505
1505
  unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
1506
1506
  unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
1507
- codexUnmappedTool: v4.policy?.codexUnmappedTool === "allow" ? "allow" : "deny",
1507
+ codexUnmappedTool: "allow",
1508
1508
  fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
1509
1509
  confidenceThresholds: {
1510
1510
  allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
@@ -1806,7 +1806,7 @@ var init_config = __esm({
1806
1806
  DEFAULT_POLICY_V3 = {
1807
1807
  unknownLocalEffect: "allow_flagged",
1808
1808
  unparseableShell: "deny",
1809
- codexUnmappedTool: "deny",
1809
+ codexUnmappedTool: "allow",
1810
1810
  confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
1811
1811
  modelAssist: { ...DEFAULT_MODEL_ASSIST },
1812
1812
  transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
@@ -3510,9 +3510,122 @@ var init_config_layers = __esm({
3510
3510
  }
3511
3511
  });
3512
3512
 
3513
- // src/config-io.ts
3513
+ // src/core/repo-config-trust.ts
3514
3514
  import { existsSync as 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
+ async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
3601
+ const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
3602
+ if (status.trusted) {
3603
+ return;
3604
+ }
3605
+ throw new RepoConfigTrustError(status);
3606
+ }
3607
+ var TRUST_MESSAGE, RepoConfigTrustError;
3608
+ var init_repo_config_trust = __esm({
3609
+ "src/core/repo-config-trust.ts"() {
3610
+ "use strict";
3611
+ init_config();
3612
+ init_fingerprint2();
3613
+ init_path_utils();
3614
+ TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
3615
+ RepoConfigTrustError = class extends Error {
3616
+ status;
3617
+ constructor(status) {
3618
+ super(TRUST_MESSAGE);
3619
+ this.name = "RepoConfigTrustError";
3620
+ this.status = status;
3621
+ }
3622
+ };
3623
+ }
3624
+ });
3625
+
3626
+ // src/config-io.ts
3627
+ import { existsSync as existsSync3 } from "node:fs";
3628
+ import { chmod as chmod2, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, unlink as unlink3, writeFile } from "node:fs/promises";
3516
3629
  function resolveAdapterName(config) {
3517
3630
  if (config.adapter === "claude") {
3518
3631
  return "claude";
@@ -3523,10 +3636,10 @@ function resolveAdapterName(config) {
3523
3636
  return "cursor";
3524
3637
  }
3525
3638
  function detectAdapterName(repoRoot) {
3526
- if (existsSync2(configPathFor(repoRoot, "claude"))) {
3639
+ if (existsSync3(configPathFor(repoRoot, "claude"))) {
3527
3640
  return "claude";
3528
3641
  }
3529
- if (existsSync2(configPathFor(repoRoot, "codex"))) {
3642
+ if (existsSync3(configPathFor(repoRoot, "codex"))) {
3530
3643
  return "codex";
3531
3644
  }
3532
3645
  return "cursor";
@@ -3541,20 +3654,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
3541
3654
  const layout = getAdapterLayout(adapter);
3542
3655
  const configPath = configPathFor(repoRoot, adapter);
3543
3656
  let repoConfig = {};
3544
- if (existsSync2(configPath)) {
3545
- repoConfig = JSON.parse(await readFile2(configPath, "utf8"));
3657
+ if (existsSync3(configPath)) {
3658
+ repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
3546
3659
  }
3547
3660
  let teamConfig = null;
3548
3661
  const teamPath = teamConfigPath();
3549
- if (existsSync2(teamPath)) {
3550
- teamConfig = JSON.parse(await readFile2(teamPath, "utf8"));
3662
+ if (existsSync3(teamPath)) {
3663
+ teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
3551
3664
  }
3552
3665
  return resolveLayeredConfig({
3553
3666
  repoConfig,
3554
3667
  adapterDefaults: layout.defaultConfig(repoRoot),
3555
3668
  teamConfig,
3556
3669
  teamConfigPath: teamPath,
3557
- repoConfigPath: existsSync2(configPath) ? configPath : void 0
3670
+ repoConfigPath: existsSync3(configPath) ? configPath : void 0
3558
3671
  });
3559
3672
  }
3560
3673
  async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
@@ -3569,21 +3682,22 @@ var init_config_io = __esm({
3569
3682
  init_approval_state_mutation();
3570
3683
  init_config();
3571
3684
  init_config_layers();
3685
+ init_repo_config_trust();
3572
3686
  }
3573
3687
  });
3574
3688
 
3575
3689
  // src/adapters/codex/runtime-entry.ts
3576
3690
  init_codex();
3577
- import path63 from "node:path";
3691
+ import path65 from "node:path";
3578
3692
  import process2 from "node:process";
3579
3693
 
3580
3694
  // src/adapters/shared/gate-runtime.ts
3581
3695
  init_approval();
3582
3696
  init_approval_replay();
3583
3697
  import { randomUUID as randomUUID6 } from "node:crypto";
3584
- import { existsSync as existsSync16 } from "node:fs";
3585
- import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
3586
- import path61 from "node:path";
3698
+ import { existsSync as existsSync17 } from "node:fs";
3699
+ import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
3700
+ import path63 from "node:path";
3587
3701
 
3588
3702
  // src/core/approval-service.ts
3589
3703
  init_config_io();
@@ -3593,47 +3707,35 @@ init_approval_replay();
3593
3707
  // src/core/approval-token.ts
3594
3708
  init_config();
3595
3709
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
3596
- import { existsSync as existsSync3 } from "node:fs";
3597
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
3598
- import path11 from "node:path";
3599
- function base64UrlEncode(value) {
3600
- return Buffer.from(value, "utf8").toString("base64url");
3601
- }
3710
+ import { existsSync as existsSync4 } from "node:fs";
3711
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
3712
+ import path12 from "node:path";
3602
3713
  function base64UrlDecode(value) {
3603
3714
  return Buffer.from(value, "base64url").toString("utf8");
3604
3715
  }
3605
3716
  function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
3606
- return path11.join(controlPlaneDir, "approval-signing.key");
3717
+ return path12.join(controlPlaneDir, "approval-signing.key");
3607
3718
  }
3608
3719
  async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
3609
3720
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
3610
- if (existsSync3(keyPath)) {
3611
- return readFile3(keyPath);
3721
+ if (existsSync4(keyPath)) {
3722
+ return readFile4(keyPath);
3612
3723
  }
3613
- await mkdir3(controlPlaneDir, { recursive: true });
3724
+ await mkdir4(controlPlaneDir, { recursive: true });
3614
3725
  const key = randomBytes(32);
3615
3726
  await writeFile2(keyPath, key, { mode: 384 });
3616
3727
  return key;
3617
3728
  }
3618
- function signPayload(payload, key) {
3619
- const body = base64UrlEncode(JSON.stringify(payload));
3620
- const signature = createHmac("sha256", key).update(body).digest("base64url");
3621
- return `${body}.${signature}`;
3622
- }
3623
- async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
3624
- const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
3625
- return signPayload(payload, key);
3626
- }
3627
3729
  async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
3628
3730
  const [body, signature] = token.split(".");
3629
3731
  if (!body || !signature) {
3630
3732
  return null;
3631
3733
  }
3632
3734
  const keyPath = approvalSigningKeyPath(controlPlaneDir);
3633
- if (!existsSync3(keyPath)) {
3735
+ if (!existsSync4(keyPath)) {
3634
3736
  return null;
3635
3737
  }
3636
- const key = await readFile3(keyPath);
3738
+ const key = await readFile4(keyPath);
3637
3739
  const expected = createHmac("sha256", key).update(body).digest("base64url");
3638
3740
  const actualBuffer = Buffer.from(signature);
3639
3741
  const expectedBuffer = Buffer.from(expected);
@@ -3840,11 +3942,11 @@ init_approval_v3();
3840
3942
 
3841
3943
  // src/core/capability/boundary-attestation-sign.ts
3842
3944
  import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
3843
- import { access, readFile as readFile4 } from "node:fs/promises";
3945
+ import { access, readFile as readFile5 } from "node:fs/promises";
3844
3946
  init_fingerprint2();
3845
3947
 
3846
3948
  // src/core/capability/attestation.ts
3847
- import path12 from "node:path";
3949
+ import path13 from "node:path";
3848
3950
  var BOUNDARY_ATTESTATION_VERSION = 1;
3849
3951
  var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
3850
3952
  var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
@@ -3912,7 +4014,7 @@ function validateContainedExecutionAttestation(value) {
3912
4014
  const record = value;
3913
4015
  const dockerSubstrate = record.dockerSubstrate;
3914
4016
  const dockerConfiguration = record.dockerConfiguration;
3915
- 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" || !path12.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:///") || !path12.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" || !path12.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path12.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") {
4017
+ 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") {
3916
4018
  return false;
3917
4019
  }
3918
4020
  const probedAt = Date.parse(record.probedAt);
@@ -3982,7 +4084,7 @@ async function verifySignedBoundaryAttestation(params) {
3982
4084
  return record.attestation;
3983
4085
  }
3984
4086
  async function readSignedAttestationFile(filePath) {
3985
- return JSON.parse(await readFile4(filePath, "utf8"));
4087
+ return JSON.parse(await readFile5(filePath, "utf8"));
3986
4088
  }
3987
4089
 
3988
4090
  // src/core/capability/boundary-egress.ts
@@ -4151,13 +4253,13 @@ async function runWithBoundaryRunnable(target, params) {
4151
4253
  }
4152
4254
 
4153
4255
  // src/core/capability/boundary-session.ts
4154
- import path23 from "node:path";
4256
+ import path24 from "node:path";
4155
4257
 
4156
4258
  // src/services/egress-service.ts
4157
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
4158
- import { mkdir as mkdir4, readFile as readFile5, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
4259
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
4260
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
4159
4261
  import net from "node:net";
4160
- import path13 from "node:path";
4262
+ import path14 from "node:path";
4161
4263
  init_config_io();
4162
4264
  init_config();
4163
4265
 
@@ -4170,16 +4272,16 @@ function egressStatePaths(repoRoot, config) {
4170
4272
  const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
4171
4273
  return {
4172
4274
  stateDir,
4173
- pidPath: path13.join(stateDir, "egress-proxy.pid"),
4174
- statusPath: path13.join(stateDir, "egress-proxy.json")
4275
+ pidPath: path14.join(stateDir, "egress-proxy.pid"),
4276
+ statusPath: path14.join(stateDir, "egress-proxy.json")
4175
4277
  };
4176
4278
  }
4177
4279
  async function readStatusFile(statusPath) {
4178
- if (!existsSync4(statusPath)) {
4280
+ if (!existsSync5(statusPath)) {
4179
4281
  return null;
4180
4282
  }
4181
4283
  try {
4182
- const raw = JSON.parse(await readFile5(statusPath, "utf8"));
4284
+ const raw = JSON.parse(await readFile6(statusPath, "utf8"));
4183
4285
  if (typeof raw.pid !== "number") {
4184
4286
  return null;
4185
4287
  }
@@ -4197,9 +4299,9 @@ async function readStatusFile(statusPath) {
4197
4299
  async function isPortOpen(host, port) {
4198
4300
  return new Promise((resolve) => {
4199
4301
  const socket = net.createConnection({ host, port });
4200
- const finish = (open6) => {
4302
+ const finish = (open7) => {
4201
4303
  socket.destroy();
4202
- resolve(open6);
4304
+ resolve(open7);
4203
4305
  };
4204
4306
  socket.setTimeout(300);
4205
4307
  socket.on("connect", () => finish(true));
@@ -4210,7 +4312,7 @@ async function isPortOpen(host, port) {
4210
4312
  async function resolveLiveEgressStatus(repoRoot, config) {
4211
4313
  const { statusPath } = egressStatePaths(repoRoot, config);
4212
4314
  const statusCandidates = [statusPath];
4213
- const controlPlaneStatus = path13.join(configuredControlPlaneDir(config), "egress-proxy.json");
4315
+ const controlPlaneStatus = path14.join(configuredControlPlaneDir(config), "egress-proxy.json");
4214
4316
  if (!statusCandidates.includes(controlPlaneStatus)) {
4215
4317
  statusCandidates.push(controlPlaneStatus);
4216
4318
  }
@@ -4236,7 +4338,7 @@ function isProcessAlive(pid) {
4236
4338
  }
4237
4339
  }
4238
4340
  async function egressStatus(options = {}) {
4239
- const repoRoot = path13.resolve(options.targetDir ?? process.cwd());
4341
+ const repoRoot = path14.resolve(options.targetDir ?? process.cwd());
4240
4342
  const config = await loadConfigFile(repoRoot);
4241
4343
  const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
4242
4344
  const ownedRunning = Boolean(status);
@@ -4267,7 +4369,7 @@ init_config();
4267
4369
  import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
4268
4370
  import { createReadStream as createReadStream2 } from "node:fs";
4269
4371
  import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
4270
- import path18 from "node:path";
4372
+ import path19 from "node:path";
4271
4373
  init_fingerprint2();
4272
4374
  init_path_utils();
4273
4375
 
@@ -4446,7 +4548,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
4446
4548
  }
4447
4549
 
4448
4550
  // src/core/contained-execution/docker-policy.ts
4449
- import path14 from "node:path";
4551
+ import path15 from "node:path";
4450
4552
 
4451
4553
  // src/core/contained-execution/policy.ts
4452
4554
  var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
@@ -4575,7 +4677,7 @@ var PROXY_ENV_NAMES = [
4575
4677
  var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
4576
4678
  var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
4577
4679
  function assertSafeDockerPath(value, code) {
4578
- if (!path14.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
4680
+ if (!path15.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
4579
4681
  throw new ContainedExecutionFailureError(code);
4580
4682
  }
4581
4683
  }
@@ -4654,11 +4756,11 @@ init_path_utils();
4654
4756
  import { createHash as createHash7 } from "node:crypto";
4655
4757
  import { constants as fsConstants } from "node:fs";
4656
4758
  import {
4657
- chmod as chmod2,
4759
+ chmod as chmod3,
4658
4760
  lstat as lstat3,
4659
- mkdir as mkdir5,
4761
+ mkdir as mkdir6,
4660
4762
  mkdtemp,
4661
- open as open3,
4763
+ open as open4,
4662
4764
  opendir,
4663
4765
  readlink as readlink2,
4664
4766
  realpath,
@@ -4666,41 +4768,41 @@ import {
4666
4768
  symlink
4667
4769
  } from "node:fs/promises";
4668
4770
  import os from "node:os";
4669
- import path17 from "node:path";
4771
+ import path18 from "node:path";
4670
4772
 
4671
4773
  // src/core/transactional/file-tree.ts
4672
4774
  import { createHash as createHash6 } from "node:crypto";
4673
4775
  import { lstat as lstat2, readdir } from "node:fs/promises";
4674
- import path16 from "node:path";
4776
+ import path17 from "node:path";
4675
4777
 
4676
4778
  // src/core/transactional/file-tree-path.ts
4677
4779
  init_path_utils();
4678
- import path15 from "node:path";
4780
+ import path16 from "node:path";
4679
4781
  var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
4680
4782
  function validateRelativePath(relativePath) {
4681
4783
  if (!relativePath || relativePath.includes("\0")) {
4682
4784
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4683
4785
  }
4684
- if (path15.isAbsolute(relativePath)) {
4786
+ if (path16.isAbsolute(relativePath)) {
4685
4787
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4686
4788
  }
4687
4789
  if (isPathOutsideRoot(relativePath)) {
4688
4790
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4689
4791
  }
4690
- const normalized = path15.normalize(relativePath);
4691
- if (normalized === ".." || normalized.startsWith(`..${path15.sep}`)) {
4792
+ const normalized = path16.normalize(relativePath);
4793
+ if (normalized === ".." || normalized.startsWith(`..${path16.sep}`)) {
4692
4794
  throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
4693
4795
  }
4694
4796
  }
4695
4797
  function joinRelativePath(root, relativePath) {
4696
4798
  validateRelativePath(relativePath);
4697
- return path15.join(canonicalPath(root), relativePath);
4799
+ return path16.join(canonicalPath(root), relativePath);
4698
4800
  }
4699
4801
  function isRootGitMetadataPath(relativePath) {
4700
- return relativePath === ".git" || relativePath.startsWith(`.git${path15.sep}`);
4802
+ return relativePath === ".git" || relativePath.startsWith(`.git${path16.sep}`);
4701
4803
  }
4702
4804
  function isNestedGitPath(relativePath) {
4703
- const segments = relativePath.split(path15.sep).filter(Boolean);
4805
+ const segments = relativePath.split(path16.sep).filter(Boolean);
4704
4806
  if (segments.length === 0) {
4705
4807
  return false;
4706
4808
  }
@@ -4867,7 +4969,7 @@ async function readPresentNode(absolutePath, counters) {
4867
4969
  async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
4868
4970
  assertWithinDeadline(deadlineMs);
4869
4971
  assertWithinQuotas(counters, quotas);
4870
- const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path16.resolve(resourceRoot);
4972
+ const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path17.resolve(resourceRoot);
4871
4973
  const dirInfo = await lstat2(absoluteDir);
4872
4974
  if (!dirInfo.isDirectory()) {
4873
4975
  throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
@@ -4893,7 +4995,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
4893
4995
  const names = await readdir(absoluteDir);
4894
4996
  for (const name of names) {
4895
4997
  assertWithinDeadline(deadlineMs);
4896
- const childRelative = relativeDir ? path16.join(relativeDir, name) : name;
4998
+ const childRelative = relativeDir ? path17.join(relativeDir, name) : name;
4897
4999
  validateRelativePath(childRelative);
4898
5000
  if (isNestedGitPath(childRelative)) {
4899
5001
  throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
@@ -4901,7 +5003,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
4901
5003
  if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
4902
5004
  continue;
4903
5005
  }
4904
- const childAbsolute = path16.join(absoluteDir, name);
5006
+ const childAbsolute = path17.join(absoluteDir, name);
4905
5007
  const childInfo = await lstat2(childAbsolute);
4906
5008
  if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
4907
5009
  await walkDirectory(
@@ -5012,16 +5114,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
5012
5114
  );
5013
5115
  }
5014
5116
  var productionDependencies = {
5015
- makeTempRoot: () => mkdtemp(path17.join(os.tmpdir(), "belay-contained-mirror-")),
5117
+ makeTempRoot: () => mkdtemp(path18.join(os.tmpdir(), "belay-contained-mirror-")),
5016
5118
  removeRoot: (root) => rm(root, { recursive: true, force: true }),
5017
5119
  now: () => Date.now()
5018
5120
  };
5019
5121
  function isAtOrWithin(root, target) {
5020
- const relative = path17.relative(root, target);
5021
- return relative === "" || !path17.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path17.sep}`);
5122
+ const relative = path18.relative(root, target);
5123
+ return relative === "" || !path18.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path18.sep}`);
5022
5124
  }
5023
5125
  function isGitMetadataRelativePath(relativePath) {
5024
- return relativePath.split(path17.sep).some((segment) => segment.toLowerCase() === ".git");
5126
+ return relativePath.split(path18.sep).some((segment) => segment.toLowerCase() === ".git");
5025
5127
  }
5026
5128
  function identityFromStats(stats) {
5027
5129
  return {
@@ -5103,7 +5205,7 @@ async function readStableFile(absolutePath, context) {
5103
5205
  if (before.nlink > 1n) {
5104
5206
  throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
5105
5207
  }
5106
- const source = await open3(absolutePath, safeReadFlags());
5208
+ const source = await open4(absolutePath, safeReadFlags());
5107
5209
  try {
5108
5210
  const opened = await source.stat({ bigint: true });
5109
5211
  assertRegularSingleLinkBigInt(opened);
@@ -5158,7 +5260,7 @@ function addMetadataRoots(context, directoryPath) {
5158
5260
  }
5159
5261
  }
5160
5262
  function pathMatchesRoots(absolutePath, roots) {
5161
- const lexical = path17.resolve(absolutePath);
5263
+ const lexical = path18.resolve(absolutePath);
5162
5264
  const canonical = canonicalPath(absolutePath);
5163
5265
  for (const root of roots) {
5164
5266
  if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
@@ -5168,7 +5270,7 @@ function pathMatchesRoots(absolutePath, roots) {
5168
5270
  return false;
5169
5271
  }
5170
5272
  function pathLexicallyMatchesRoots(absolutePath, roots) {
5171
- const lexical = path17.resolve(absolutePath);
5273
+ const lexical = path18.resolve(absolutePath);
5172
5274
  for (const root of roots) {
5173
5275
  if (isAtOrWithin(root, lexical)) {
5174
5276
  return true;
@@ -5189,11 +5291,11 @@ async function readSafeSymlink(absolutePath, context) {
5189
5291
  }
5190
5292
  const identity = identityFromStats(before);
5191
5293
  const target = await readlink2(absolutePath);
5192
- if (path17.isAbsolute(target)) {
5294
+ if (path18.isAbsolute(target)) {
5193
5295
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5194
5296
  }
5195
- const lexicalTarget = path17.resolve(path17.dirname(absolutePath), target);
5196
- if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path17.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5297
+ const lexicalTarget = path18.resolve(path18.dirname(absolutePath), target);
5298
+ if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path18.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
5197
5299
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
5198
5300
  }
5199
5301
  let resolvedTarget;
@@ -5229,7 +5331,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5229
5331
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5230
5332
  }
5231
5333
  const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
5232
- const directory = await open3(absoluteDirectory, directoryFlags);
5334
+ const directory = await open4(absoluteDirectory, directoryFlags);
5233
5335
  try {
5234
5336
  const opened = await directory.stat({ bigint: true });
5235
5337
  if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
@@ -5238,7 +5340,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
5238
5340
  const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
5239
5341
  for await (const directoryEntry of entriesDirectory) {
5240
5342
  assertDeadline(context.deadlineMs, context.now);
5241
- const relativePath = relativeDirectory ? path17.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5343
+ const relativePath = relativeDirectory ? path18.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
5242
5344
  const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
5243
5345
  const info = await lstat3(absolutePath);
5244
5346
  if (info.isSymbolicLink()) {
@@ -5322,7 +5424,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5322
5424
  }
5323
5425
  assertEntryIdentity(entry, identityFromStats(before));
5324
5426
  await context.beforeCopyOpen?.(sourcePath);
5325
- const source = await open3(sourcePath, safeReadFlags());
5427
+ const source = await open4(sourcePath, safeReadFlags());
5326
5428
  let destination;
5327
5429
  try {
5328
5430
  const opened = await source.stat({ bigint: true });
@@ -5332,8 +5434,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5332
5434
  if (entry.node.kind !== "file") {
5333
5435
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5334
5436
  }
5335
- await mkdir5(path17.dirname(destinationPath), { recursive: true, mode: 448 });
5336
- destination = await open3(
5437
+ await mkdir6(path18.dirname(destinationPath), { recursive: true, mode: 448 });
5438
+ destination = await open4(
5337
5439
  destinationPath,
5338
5440
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
5339
5441
  safePermissionMode(opened.mode)
@@ -5375,7 +5477,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
5375
5477
  if (entry.node.kind !== "file") {
5376
5478
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5377
5479
  }
5378
- await chmod2(destinationPath, safePermissionMode(entry.node.mode));
5480
+ await chmod3(destinationPath, safePermissionMode(entry.node.mode));
5379
5481
  }
5380
5482
  async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
5381
5483
  const directoryEntries = [];
@@ -5384,7 +5486,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5384
5486
  const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
5385
5487
  const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
5386
5488
  if (entry.node.kind === "directory") {
5387
- await mkdir5(destinationPath, { recursive: true, mode: 448 });
5489
+ await mkdir6(destinationPath, { recursive: true, mode: 448 });
5388
5490
  directoryEntries.push(entry);
5389
5491
  continue;
5390
5492
  }
@@ -5396,7 +5498,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5396
5498
  if (current !== entry.node.target) {
5397
5499
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5398
5500
  }
5399
- await mkdir5(path17.dirname(destinationPath), { recursive: true, mode: 448 });
5501
+ await mkdir6(path18.dirname(destinationPath), { recursive: true, mode: 448 });
5400
5502
  await symlink(entry.node.target, destinationPath);
5401
5503
  continue;
5402
5504
  }
@@ -5406,12 +5508,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
5406
5508
  if (entry.node.kind !== "directory") {
5407
5509
  throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
5408
5510
  }
5409
- await chmod2(
5511
+ await chmod3(
5410
5512
  joinRelativePath(destinationRoot, entry.relativePath),
5411
5513
  safePermissionMode(entry.node.mode)
5412
5514
  );
5413
5515
  }
5414
- await chmod2(destinationRoot, 448);
5516
+ await chmod3(destinationRoot, 448);
5415
5517
  }
5416
5518
  async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
5417
5519
  const [after, copied] = await Promise.all([
@@ -5456,7 +5558,7 @@ async function cleanupOwnedRoot(root, dependencies) {
5456
5558
  }
5457
5559
  async function prepareWithDependencies(options, dependencies) {
5458
5560
  validateOptions(options);
5459
- const guestWorkspacePath = path17.resolve(options.sourceRoot);
5561
+ const guestWorkspacePath = path18.resolve(options.sourceRoot);
5460
5562
  const sourceRoot = canonicalPath(options.sourceRoot);
5461
5563
  const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
5462
5564
  if (pathMatchesRoots(sourceRoot, protectedRoots)) {
@@ -5464,7 +5566,7 @@ async function prepareWithDependencies(options, dependencies) {
5464
5566
  }
5465
5567
  const guestRoot = await dependencies.makeTempRoot();
5466
5568
  try {
5467
- await chmod2(guestRoot, 448);
5569
+ await chmod3(guestRoot, 448);
5468
5570
  const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
5469
5571
  const before = await buildSafeMirrorSnapshot(
5470
5572
  sourceRoot,
@@ -5492,7 +5594,7 @@ async function prepareWithDependencies(options, dependencies) {
5492
5594
  dependencies.now,
5493
5595
  before
5494
5596
  );
5495
- await chmod2(guestRoot, 448);
5597
+ await chmod3(guestRoot, 448);
5496
5598
  const lease = {
5497
5599
  sourceRoot,
5498
5600
  hostMirrorRoot: guestRoot,
@@ -5626,14 +5728,14 @@ async function digestFile(file) {
5626
5728
  return hash.digest("hex");
5627
5729
  }
5628
5730
  async function resolveConfiguredDockerSubstrate(params) {
5629
- if (!path18.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
5731
+ if (!path19.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
5630
5732
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
5631
5733
  }
5632
5734
  if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
5633
5735
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
5634
5736
  }
5635
5737
  const configuredSocket = params.host.slice("unix://".length);
5636
- if (!path18.isAbsolute(configuredSocket)) {
5738
+ if (!path19.isAbsolute(configuredSocket)) {
5637
5739
  throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
5638
5740
  }
5639
5741
  let binaryPath;
@@ -5894,7 +5996,7 @@ async function validatedGuestCwd(params) {
5894
5996
  const requiredExclusions = [
5895
5997
  ...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
5896
5998
  ];
5897
- if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path18.resolve(params.repoRoot) || !path18.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
5999
+ if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path19.resolve(params.repoRoot) || !path19.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
5898
6000
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5899
6001
  const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
5900
6002
  const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
@@ -5906,10 +6008,10 @@ async function validatedGuestCwd(params) {
5906
6008
  protectedRoots: requiredExclusions
5907
6009
  }))
5908
6010
  throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
5909
- const relative = path18.relative(params.mirror.guestWorkspacePath, path18.resolve(params.guestCwd));
6011
+ const relative = path19.relative(params.mirror.guestWorkspacePath, path19.resolve(params.guestCwd));
5910
6012
  let resolvedCwd;
5911
6013
  try {
5912
- resolvedCwd = await realpath2(path18.join(resolvedRoot, relative));
6014
+ resolvedCwd = await realpath2(path19.join(resolvedRoot, relative));
5913
6015
  } catch {
5914
6016
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5915
6017
  }
@@ -5917,7 +6019,7 @@ async function validatedGuestCwd(params) {
5917
6019
  if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
5918
6020
  throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
5919
6021
  }
5920
- return path18.join(params.mirror.guestWorkspacePath, path18.relative(resolvedRoot, resolvedCwd));
6022
+ return path19.join(params.mirror.guestWorkspacePath, path19.relative(resolvedRoot, resolvedCwd));
5921
6023
  }
5922
6024
  function exactKeys(value, allowed) {
5923
6025
  const names = new Set(allowed);
@@ -6144,19 +6246,19 @@ async function executeContainedDocker(params) {
6144
6246
  init_path_utils();
6145
6247
  import { spawn as spawn3 } from "node:child_process";
6146
6248
  import os3 from "node:os";
6147
- import path20 from "node:path";
6249
+ import path21 from "node:path";
6148
6250
 
6149
6251
  // src/core/transactional/apply-observed-changes.ts
6150
- 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";
6252
+ 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";
6151
6253
  import os2 from "node:os";
6152
- import path19 from "node:path";
6254
+ import path20 from "node:path";
6153
6255
  var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
6154
6256
  var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
6155
6257
  var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
6156
6258
  async function chmodSafe(target, mode) {
6157
6259
  try {
6158
- const { chmod: chmod4 } = await import("node:fs/promises");
6159
- await chmod4(target, mode & 511);
6260
+ const { chmod: chmod5 } = await import("node:fs/promises");
6261
+ await chmod5(target, mode & 511);
6160
6262
  } catch {
6161
6263
  }
6162
6264
  }
@@ -6190,13 +6292,13 @@ async function copyPathPreservingType(source, target) {
6190
6292
  }
6191
6293
  }
6192
6294
  await removePathIfExists(target);
6193
- await mkdir6(path19.dirname(target), { recursive: true });
6295
+ await mkdir7(path20.dirname(target), { recursive: true });
6194
6296
  if (info.isSymbolicLink()) {
6195
6297
  await symlink2(await readlink3(source), target);
6196
6298
  return;
6197
6299
  }
6198
6300
  if (info.isDirectory() && !info.isSymbolicLink()) {
6199
- await mkdir6(target, { recursive: true, mode: info.mode & 511 });
6301
+ await mkdir7(target, { recursive: true, mode: info.mode & 511 });
6200
6302
  return;
6201
6303
  }
6202
6304
  if (!info.isFile()) {
@@ -6206,12 +6308,12 @@ async function copyPathPreservingType(source, target) {
6206
6308
  await chmodSafe(target, info.mode);
6207
6309
  }
6208
6310
  async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
6209
- const segments = relativePath.split(path19.sep).filter(Boolean);
6311
+ const segments = relativePath.split(path20.sep).filter(Boolean);
6210
6312
  if (segments.length <= 1) {
6211
6313
  return;
6212
6314
  }
6213
6315
  for (let index = 1; index < segments.length; index++) {
6214
- const prefix = segments.slice(0, index).join(path19.sep);
6316
+ const prefix = segments.slice(0, index).join(path20.sep);
6215
6317
  const absolute = joinRelativePath(targetRoot, prefix);
6216
6318
  let info;
6217
6319
  try {
@@ -6231,8 +6333,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
6231
6333
  }
6232
6334
  }
6233
6335
  async function restorePathFromBackup(backupPath, target, rollbackRoot) {
6234
- const stagingRoot = await mkdtemp3(path19.join(rollbackRoot, "restore-"));
6235
- const staged = path19.join(stagingRoot, "node");
6336
+ const stagingRoot = await mkdtemp3(path20.join(rollbackRoot, "restore-"));
6337
+ const staged = path20.join(stagingRoot, "node");
6236
6338
  try {
6237
6339
  await copyPathPreservingType(backupPath, staged);
6238
6340
  await copyPathPreservingType(staged, target);
@@ -6316,7 +6418,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
6316
6418
  if (change.before.kind !== "directory") {
6317
6419
  await removePathIfExists(target);
6318
6420
  }
6319
- await mkdir6(target, { recursive: true, mode: change.after.mode & 511 });
6421
+ await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
6320
6422
  await chmodSafe(target, change.after.mode);
6321
6423
  return;
6322
6424
  }
@@ -6335,7 +6437,7 @@ async function applyObservedChanges(params) {
6335
6437
  await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
6336
6438
  await assertTargetMatches(targetRoot, change);
6337
6439
  }
6338
- const backupRoot = await mkdtemp3(path19.join(os2.tmpdir(), "belay-tx-rollback-"));
6440
+ const backupRoot = await mkdtemp3(path20.join(os2.tmpdir(), "belay-tx-rollback-"));
6339
6441
  const rollbackActions = [];
6340
6442
  let mutationAttempted = false;
6341
6443
  let resourceIdentityChanged = false;
@@ -6354,12 +6456,12 @@ async function applyObservedChanges(params) {
6354
6456
  targetExists = false;
6355
6457
  }
6356
6458
  if (targetExists) {
6357
- const backupPath = path19.join(
6459
+ const backupPath = path20.join(
6358
6460
  backupRoot,
6359
6461
  String(rollbackActions.length),
6360
6462
  change.relativePath
6361
6463
  );
6362
- await mkdir6(path19.dirname(backupPath), { recursive: true });
6464
+ await mkdir7(path20.dirname(backupPath), { recursive: true });
6363
6465
  await copyPathPreservingType(target, backupPath);
6364
6466
  await assertParentChainSafe(targetRoot, change.relativePath);
6365
6467
  rollbackActions.push({
@@ -6458,7 +6560,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
6458
6560
  if (ignoreRoots.length === 0) {
6459
6561
  return false;
6460
6562
  }
6461
- const absolutePath = canonicalPath(path20.join(repoRoot, relativePath));
6563
+ const absolutePath = canonicalPath(path21.join(repoRoot, relativePath));
6462
6564
  return ignoreRoots.some(
6463
6565
  (root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
6464
6566
  );
@@ -6496,7 +6598,7 @@ async function isDirtyWorktree(repoRoot, options) {
6496
6598
  }
6497
6599
  async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
6498
6600
  const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
6499
- const worktreePath = await mkdtemp6(path20.join(os3.tmpdir(), "belay-tx-"));
6601
+ const worktreePath = await mkdtemp6(path21.join(os3.tmpdir(), "belay-tx-"));
6500
6602
  await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
6501
6603
  return {
6502
6604
  worktreePath,
@@ -6515,14 +6617,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
6515
6617
  }
6516
6618
  function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
6517
6619
  const resolvedCwd = canonicalPath(cwd);
6518
- const relative = path20.relative(canonicalPath(repoRoot), resolvedCwd);
6519
- if (isPathOutsideRoot(relative) || path20.isAbsolute(relative)) {
6620
+ const relative = path21.relative(canonicalPath(repoRoot), resolvedCwd);
6621
+ if (isPathOutsideRoot(relative) || path21.isAbsolute(relative)) {
6520
6622
  return worktreePath;
6521
6623
  }
6522
6624
  if (relative === "") {
6523
6625
  return worktreePath;
6524
6626
  }
6525
- return path20.join(worktreePath, relative);
6627
+ return path21.join(worktreePath, relative);
6526
6628
  }
6527
6629
  function runShellCommand(command, cwd, timeoutMs) {
6528
6630
  return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
@@ -6587,7 +6689,7 @@ import { spawn as spawn4 } from "node:child_process";
6587
6689
  import { randomUUID as randomUUID3 } from "node:crypto";
6588
6690
 
6589
6691
  // src/core/capability/boundary-grant-materialize.ts
6590
- import path21 from "node:path";
6692
+ import path22 from "node:path";
6591
6693
 
6592
6694
  // src/core/glob.ts
6593
6695
  init_approval();
@@ -6747,7 +6849,7 @@ function grantMatchesRequest(grant, request) {
6747
6849
  var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
6748
6850
  var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
6749
6851
  function resolveCapabilityPath(targetPath, cwd) {
6750
- const joined = path21.isAbsolute(targetPath) ? targetPath : path21.join(cwd, targetPath);
6852
+ const joined = path22.isAbsolute(targetPath) ? targetPath : path22.join(cwd, targetPath);
6751
6853
  return canonicalPath(joined);
6752
6854
  }
6753
6855
  function isPathWithinBoundaryMount(request) {
@@ -6822,7 +6924,7 @@ function materializeContainerBoundaryGrant(request, params) {
6822
6924
 
6823
6925
  // src/core/capability/boundary-workspace-mount.ts
6824
6926
  init_path_utils();
6825
- import path22 from "node:path";
6927
+ import path23 from "node:path";
6826
6928
  var HOST_PATH_ENV_VARS = [
6827
6929
  "BELAY_EGRESS_REPO_ROOT",
6828
6930
  "BELAY_JUDGE_BROKER_REPO_ROOT",
@@ -6847,23 +6949,23 @@ function validateWorkspaceMount(mount) {
6847
6949
  if (mount.cwdRelative.includes("\0")) {
6848
6950
  throw new Error("boundary_workspace_mount_invalid_cwd");
6849
6951
  }
6850
- const normalizedRelative = path22.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6952
+ const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6851
6953
  if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
6852
6954
  throw new Error("boundary_workspace_mount_invalid_cwd");
6853
6955
  }
6854
- if (path22.posix.isAbsolute(normalizedRelative)) {
6956
+ if (path23.posix.isAbsolute(normalizedRelative)) {
6855
6957
  throw new Error("boundary_workspace_mount_invalid_cwd");
6856
6958
  }
6857
6959
  }
6858
6960
  function resolveGuestWorkdir(mount) {
6859
6961
  validateWorkspaceMount(mount);
6860
6962
  const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
6861
- const normalizedRelative = path22.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6963
+ const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
6862
6964
  if (normalizedRelative === "." || normalizedRelative === "") {
6863
6965
  return guestTargetRoot;
6864
6966
  }
6865
6967
  const segments = normalizedRelative.split("/").filter(Boolean);
6866
- return path22.posix.join(guestTargetRoot, ...segments);
6968
+ return path23.posix.join(guestTargetRoot, ...segments);
6867
6969
  }
6868
6970
  function buildWorkspaceMountSpec(mount) {
6869
6971
  validateWorkspaceMount(mount);
@@ -7154,7 +7256,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
7154
7256
  }
7155
7257
  function boundaryAttestationPath(repoRoot, config) {
7156
7258
  const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
7157
- return path23.isAbsolute(rel) ? rel : path23.join(repoRoot, rel);
7259
+ return path24.isAbsolute(rel) ? rel : path24.join(repoRoot, rel);
7158
7260
  }
7159
7261
  async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
7160
7262
  try {
@@ -7259,7 +7361,7 @@ init_capability_request_hash();
7259
7361
 
7260
7362
  // src/core/capability/gate-policy-shadow.ts
7261
7363
  init_config();
7262
- import path29 from "node:path";
7364
+ import path30 from "node:path";
7263
7365
 
7264
7366
  // src/core/effect-ir/audit.ts
7265
7367
  init_fingerprint2();
@@ -7353,8 +7455,8 @@ function evidenceRank(level) {
7353
7455
  }
7354
7456
 
7355
7457
  // src/core/effect-ir/package-exec.ts
7356
- import { existsSync as existsSync5, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
7357
- import path24 from "node:path";
7458
+ import { existsSync as existsSync6, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
7459
+ import path25 from "node:path";
7358
7460
 
7359
7461
  // src/core/network-endpoint.ts
7360
7462
  var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "ssh:", "git:"]);
@@ -7645,18 +7747,18 @@ function classifyPackageAcquisitionSpec(spec) {
7645
7747
  return { kind: "registry" };
7646
7748
  }
7647
7749
  function resolveLocalBin(binName, cwd, repoRoot) {
7648
- const base = path24.basename(binName);
7750
+ const base = path25.basename(binName);
7649
7751
  if (!base || base === "." || base === "..") {
7650
7752
  return null;
7651
7753
  }
7652
- let current = path24.resolve(cwd);
7653
- const stop = path24.resolve(repoRoot);
7754
+ let current = path25.resolve(cwd);
7755
+ const stop = path25.resolve(repoRoot);
7654
7756
  if (!pathWithinRoot(stop, current)) {
7655
7757
  return null;
7656
7758
  }
7657
7759
  while (true) {
7658
- const candidate = path24.join(current, "node_modules", ".bin", base);
7659
- if (existsSync5(candidate)) {
7760
+ const candidate = path25.join(current, "node_modules", ".bin", base);
7761
+ if (existsSync6(candidate)) {
7660
7762
  try {
7661
7763
  const resolvedCandidate = realpathSync3.native(candidate);
7662
7764
  if (!pathWithinRoot(stop, resolvedCandidate) || !statSync2(resolvedCandidate).isFile()) {
@@ -7670,7 +7772,7 @@ function resolveLocalBin(binName, cwd, repoRoot) {
7670
7772
  if (current === stop) {
7671
7773
  break;
7672
7774
  }
7673
- const parent = path24.dirname(current);
7775
+ const parent = path25.dirname(current);
7674
7776
  if (parent === current) {
7675
7777
  break;
7676
7778
  }
@@ -7865,7 +7967,7 @@ init_path_utils();
7865
7967
 
7866
7968
  // src/core/verdict/containment.ts
7867
7969
  init_git_resource_identity();
7868
- import path25 from "node:path";
7970
+ import path26 from "node:path";
7869
7971
  init_path_utils();
7870
7972
 
7871
7973
  // src/core/verdict/persistent-paths.ts
@@ -7885,7 +7987,7 @@ function expandHome(token) {
7885
7987
  if (!home) {
7886
7988
  return token;
7887
7989
  }
7888
- return token === "~" ? home : path25.join(home, token.slice(2));
7990
+ return token === "~" ? home : path26.join(home, token.slice(2));
7889
7991
  }
7890
7992
  return token;
7891
7993
  }
@@ -7897,10 +7999,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
7897
7999
  return null;
7898
8000
  }
7899
8001
  const expanded = expandHome(token);
7900
- if (path25.isAbsolute(expanded)) {
8002
+ if (path26.isAbsolute(expanded)) {
7901
8003
  return canonicalPath(expanded);
7902
8004
  }
7903
- return canonicalPath(path25.resolve(trustedCwd, expanded));
8005
+ return canonicalPath(path26.resolve(trustedCwd, expanded));
7904
8006
  }
7905
8007
  function isGitPath(resolvedPath, repoRoot) {
7906
8008
  if (isGitMetadataPath(resolvedPath, repoRoot)) {
@@ -8017,20 +8119,20 @@ function parseTier1Json(raw) {
8017
8119
  init_judge_runtime_config();
8018
8120
 
8019
8121
  // src/core/verdict/judge-session-kill-switch.ts
8020
- import { existsSync as existsSync6 } from "node:fs";
8021
- import { mkdir as mkdir7, readFile as readFile6, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
8022
- import path26 from "node:path";
8122
+ import { existsSync as existsSync7 } from "node:fs";
8123
+ import { mkdir as mkdir8, readFile as readFile7, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
8124
+ import path27 from "node:path";
8023
8125
  var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
8024
8126
  function judgeSessionKillSwitchPath(stateDir) {
8025
- return path26.join(stateDir, JUDGE_SESSION_KILL_FILE);
8127
+ return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
8026
8128
  }
8027
8129
  async function readJudgeSessionKillSwitch(stateDir) {
8028
8130
  const filePath = judgeSessionKillSwitchPath(stateDir);
8029
- if (!existsSync6(filePath)) {
8131
+ if (!existsSync7(filePath)) {
8030
8132
  return null;
8031
8133
  }
8032
8134
  try {
8033
- const raw = JSON.parse(await readFile6(filePath, "utf8"));
8135
+ const raw = JSON.parse(await readFile7(filePath, "utf8"));
8034
8136
  return raw.triggered === true ? raw : null;
8035
8137
  } catch {
8036
8138
  return null;
@@ -8041,7 +8143,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
8041
8143
  return record?.triggered === true;
8042
8144
  }
8043
8145
  async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
8044
- await mkdir7(stateDir, { recursive: true, mode: 448 });
8146
+ await mkdir8(stateDir, { recursive: true, mode: 448 });
8045
8147
  const record = {
8046
8148
  triggered: true,
8047
8149
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8202,10 +8304,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
8202
8304
 
8203
8305
  // src/core/verdict/judge-broker-service.ts
8204
8306
  import { spawn as spawn6 } from "node:child_process";
8205
- import { existsSync as existsSync7 } from "node:fs";
8206
- import { unlink as unlink5 } from "node:fs/promises";
8307
+ import { existsSync as existsSync8 } from "node:fs";
8308
+ import { unlink as unlink6 } from "node:fs/promises";
8207
8309
  import net2 from "node:net";
8208
- import path27 from "node:path";
8310
+ import path28 from "node:path";
8209
8311
  import { fileURLToPath } from "node:url";
8210
8312
 
8211
8313
  // src/core/verdict/judge-cli.ts
@@ -8751,10 +8853,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
8751
8853
  function judgeBrokerPaths(stateDir) {
8752
8854
  return {
8753
8855
  stateDir,
8754
- socketPath: path27.join(stateDir, JUDGE_BROKER_SOCKET),
8755
- statusPath: path27.join(stateDir, JUDGE_BROKER_STATUS),
8756
- pidPath: path27.join(stateDir, JUDGE_BROKER_PID),
8757
- sessionConfigPath: path27.join(stateDir, JUDGE_BROKER_SESSION)
8856
+ socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
8857
+ statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
8858
+ pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
8859
+ sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
8758
8860
  };
8759
8861
  }
8760
8862
  function daemonScriptPath() {
@@ -8772,12 +8874,12 @@ function useInProcessBroker(env = process.env) {
8772
8874
  return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
8773
8875
  }
8774
8876
  async function readBrokerStatus(statusPath) {
8775
- if (!existsSync7(statusPath)) {
8877
+ if (!existsSync8(statusPath)) {
8776
8878
  return null;
8777
8879
  }
8778
8880
  try {
8779
- const { readFile: readFile16 } = await import("node:fs/promises");
8780
- const raw = JSON.parse(await readFile16(statusPath, "utf8"));
8881
+ const { readFile: readFile17 } = await import("node:fs/promises");
8882
+ const raw = JSON.parse(await readFile17(statusPath, "utf8"));
8781
8883
  if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
8782
8884
  return null;
8783
8885
  }
@@ -8787,13 +8889,13 @@ async function readBrokerStatus(statusPath) {
8787
8889
  }
8788
8890
  }
8789
8891
  async function readBrokerSessionConfig(sessionConfigPath) {
8790
- if (!existsSync7(sessionConfigPath)) {
8892
+ if (!existsSync8(sessionConfigPath)) {
8791
8893
  return null;
8792
8894
  }
8793
8895
  try {
8794
- const { readFile: readFile16 } = await import("node:fs/promises");
8896
+ const { readFile: readFile17 } = await import("node:fs/promises");
8795
8897
  const raw = JSON.parse(
8796
- await readFile16(sessionConfigPath, "utf8")
8898
+ await readFile17(sessionConfigPath, "utf8")
8797
8899
  );
8798
8900
  return normalizeJudgeSessionConfig({ ...raw, enabled: true });
8799
8901
  } catch {
@@ -8884,7 +8986,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8884
8986
  const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
8885
8987
  const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
8886
8988
  const status = await readBrokerStatus(paths.statusPath);
8887
- if (status && isProcessAlive2(status.pid) && existsSync7(paths.socketPath) && !configChanged) {
8989
+ if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
8888
8990
  await writeBrokerSessionConfig(paths, brokerSessionConfig);
8889
8991
  return paths;
8890
8992
  }
@@ -8906,7 +9008,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
8906
9008
  child.unref();
8907
9009
  const deadline = Date.now() + sessionConfig.connectTimeoutMs;
8908
9010
  while (Date.now() < deadline) {
8909
- if (existsSync7(paths.socketPath)) {
9011
+ if (existsSync8(paths.socketPath)) {
8910
9012
  const live = await readBrokerStatus(paths.statusPath);
8911
9013
  if (live && isProcessAlive2(live.pid)) {
8912
9014
  return paths;
@@ -8923,8 +9025,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
8923
9025
  paths.pidPath,
8924
9026
  paths.sessionConfigPath
8925
9027
  ]) {
8926
- if (existsSync7(artifact)) {
8927
- await unlink5(artifact).catch(() => void 0);
9028
+ if (existsSync8(artifact)) {
9029
+ await unlink6(artifact).catch(() => void 0);
8928
9030
  }
8929
9031
  }
8930
9032
  }
@@ -9420,9 +9522,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
9420
9522
  }
9421
9523
 
9422
9524
  // src/core/capability/gate-shadow-ratchet.ts
9423
- import { existsSync as existsSync8 } from "node:fs";
9424
- import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
9425
- import path28 from "node:path";
9525
+ import { existsSync as existsSync9 } from "node:fs";
9526
+ import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
9527
+ import path29 from "node:path";
9426
9528
  var DEFAULT_STATE = {
9427
9529
  version: 1,
9428
9530
  policyJudgeComparisons: 0,
@@ -9431,15 +9533,15 @@ var DEFAULT_STATE = {
9431
9533
  updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
9432
9534
  };
9433
9535
  function ratchetPath(stateDir) {
9434
- return path28.join(stateDir, "gate-shadow-ratchet.json");
9536
+ return path29.join(stateDir, "gate-shadow-ratchet.json");
9435
9537
  }
9436
9538
  async function loadState(stateDir) {
9437
9539
  const filePath = ratchetPath(stateDir);
9438
- if (!existsSync8(filePath)) {
9540
+ if (!existsSync9(filePath)) {
9439
9541
  return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
9440
9542
  }
9441
9543
  try {
9442
- const raw = JSON.parse(await readFile7(filePath, "utf8"));
9544
+ const raw = JSON.parse(await readFile8(filePath, "utf8"));
9443
9545
  return {
9444
9546
  ...DEFAULT_STATE,
9445
9547
  ...raw,
@@ -9450,7 +9552,7 @@ async function loadState(stateDir) {
9450
9552
  }
9451
9553
  }
9452
9554
  async function saveState(stateDir, state) {
9453
- await mkdir8(stateDir, { recursive: true, mode: 448 });
9555
+ await mkdir9(stateDir, { recursive: true, mode: 448 });
9454
9556
  await writeFile5(
9455
9557
  ratchetPath(stateDir),
9456
9558
  `${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
@@ -9573,7 +9675,7 @@ function scheduleGateShadowAudit(params) {
9573
9675
  async function runGatePolicyShadowComparison(params) {
9574
9676
  const judge = params.config.judge;
9575
9677
  const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
9576
- const stateDir = params.stateDir ?? belayStateDir(params.config, path29.join(params.repoRoot, ".belay"));
9678
+ const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
9577
9679
  const transport = await evaluateWithJudgeTransport(
9578
9680
  {
9579
9681
  prompt: buildTier1Prompt(params.command),
@@ -9863,15 +9965,15 @@ async function loadClassifierAuthorization(params) {
9863
9965
  }
9864
9966
 
9865
9967
  // src/core/capability/allowlist.ts
9866
- import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
9968
+ import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
9867
9969
  init_config();
9868
9970
  init_path_utils();
9869
- import path30 from "node:path";
9971
+ import path31 from "node:path";
9870
9972
  function fsScopeAllowlistPath(config, repoLocalStateDir) {
9871
- return path30.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
9973
+ return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
9872
9974
  }
9873
9975
  function loadFsScopeAllowlistSync(filePath) {
9874
- if (!existsSync9(filePath)) {
9976
+ if (!existsSync10(filePath)) {
9875
9977
  return { version: 1, paths: [] };
9876
9978
  }
9877
9979
  const raw = JSON.parse(readFileSync3(filePath, "utf8"));
@@ -9963,7 +10065,7 @@ function checkGatedActionLimits(action) {
9963
10065
  // src/core/capability/paths.ts
9964
10066
  init_path_utils();
9965
10067
  init_shell_tokenizer();
9966
- import path31 from "node:path";
10068
+ import path32 from "node:path";
9967
10069
  function applyPatchTargets(patch) {
9968
10070
  const targets = [];
9969
10071
  for (const line of patch.split("\n")) {
@@ -10028,7 +10130,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10028
10130
  const paths = /* @__PURE__ */ new Set();
10029
10131
  const filePath = extractToolFilePath(payload);
10030
10132
  if (filePath) {
10031
- const resolved = path31.isAbsolute(filePath) ? filePath : path31.resolve(cwd, filePath);
10133
+ const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
10032
10134
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10033
10135
  paths.add(resolved);
10034
10136
  }
@@ -10038,7 +10140,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
10038
10140
  const patch = extractToolPatch(payload);
10039
10141
  if (patch) {
10040
10142
  for (const target of applyPatchTargets(patch)) {
10041
- const resolved = path31.isAbsolute(target) ? target : path31.resolve(cwd, target);
10143
+ const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
10042
10144
  if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
10043
10145
  paths.add(resolved);
10044
10146
  }
@@ -10092,7 +10194,7 @@ function policyReasonToLegacyReason(decision) {
10092
10194
  // src/core/capability/policy-engine.ts
10093
10195
  init_git_resource_identity();
10094
10196
  import { createHash as createHash10 } from "node:crypto";
10095
- import path32 from "node:path";
10197
+ import path33 from "node:path";
10096
10198
  init_path_utils();
10097
10199
  init_shell_tokenizer();
10098
10200
  init_grant();
@@ -10384,7 +10486,7 @@ function hashSession(value) {
10384
10486
  return createHash10("sha256").update(value).digest("hex").slice(0, 16);
10385
10487
  }
10386
10488
  function resolveCapabilityPath2(targetPath, cwd) {
10387
- const joined = path32.isAbsolute(targetPath) ? targetPath : path32.join(cwd, targetPath);
10489
+ const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
10388
10490
  return canonicalPath(joined);
10389
10491
  }
10390
10492
  function actionForFileMutation(analysis) {
@@ -10463,7 +10565,7 @@ function isRepoLocalPackageExec(request) {
10463
10565
  return false;
10464
10566
  }
10465
10567
  const commandPath = canonicalPath(request.resource.command);
10466
- return path32.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10568
+ return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
10467
10569
  }
10468
10570
  function isRepoLocalRoutineWrite(request, sensitivePaths) {
10469
10571
  if (!isRepoLocalShellLocation(request)) {
@@ -10711,6 +10813,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
10711
10813
  );
10712
10814
  return { request, decision };
10713
10815
  }
10816
+ function buildFileReadCapabilityRequest(analysis) {
10817
+ return {
10818
+ version: CAPABILITY_REQUEST_VERSION,
10819
+ principal: {
10820
+ adapter: analysis.adapter,
10821
+ repoRoot: analysis.repoRoot,
10822
+ sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
10823
+ },
10824
+ action: "fs.read",
10825
+ resource: { kind: "path", path: analysis.resolvedPath },
10826
+ context: {
10827
+ cwd: analysis.cwd,
10828
+ inputFingerprint: analysis.inputFingerprint,
10829
+ hookKind: analysis.hookKind,
10830
+ analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
10831
+ },
10832
+ evidence: {
10833
+ level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
10834
+ signals: [...analysis.signals, "effect.fs_read"]
10835
+ }
10836
+ };
10837
+ }
10838
+ function evaluateFileReadPolicy(analysis, config, auth) {
10839
+ const request = buildFileReadCapabilityRequest(analysis);
10840
+ const enriched = enrichAuthWithMaterializedGrants(request, config, {
10841
+ ...auth,
10842
+ sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
10843
+ });
10844
+ const decision = getDefaultPolicyEngine().evaluate(
10845
+ request,
10846
+ buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
10847
+ );
10848
+ return { request, decision };
10849
+ }
10714
10850
 
10715
10851
  // src/core/capability/reasons.ts
10716
10852
  var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
@@ -10727,15 +10863,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
10727
10863
  }
10728
10864
 
10729
10865
  // src/core/capability/trusted-workspace-roots.ts
10730
- import { existsSync as existsSync10, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10866
+ import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
10731
10867
  init_config();
10732
10868
  init_path_utils();
10733
- import path33 from "node:path";
10869
+ import path34 from "node:path";
10734
10870
  function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
10735
- return path33.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10871
+ return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
10736
10872
  }
10737
10873
  function loadTrustedWorkspaceRootsSync(filePath) {
10738
- if (!existsSync10(filePath)) {
10874
+ if (!existsSync11(filePath)) {
10739
10875
  return { version: 1, roots: [] };
10740
10876
  }
10741
10877
  const raw = JSON.parse(readFileSync4(filePath, "utf8"));
@@ -10761,7 +10897,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
10761
10897
  const source = record.source === "approval" ? "approval" : void 0;
10762
10898
  return [
10763
10899
  {
10764
- path: path33.resolve(record.path),
10900
+ path: path34.resolve(record.path),
10765
10901
  approvedAt,
10766
10902
  approvalId,
10767
10903
  ...source ? { source } : {}
@@ -10790,7 +10926,7 @@ function isDirectoryPath(targetPath) {
10790
10926
  function isBroadTrustedWorkspaceRoot(targetPath) {
10791
10927
  const home = process.env.HOME ?? process.env.USERPROFILE;
10792
10928
  const root = normalizeTrustedWorkspaceRootPath(targetPath);
10793
- if (root === normalizeTrustedWorkspaceRootPath(path33.parse(root).root)) {
10929
+ if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
10794
10930
  return true;
10795
10931
  }
10796
10932
  if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
@@ -10814,7 +10950,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
10814
10950
  return false;
10815
10951
  }
10816
10952
  return HOME_HIGH_STAKES_SEGMENTS.some(
10817
- (segment) => pathWithinRoot(path33.join(homeRoot, segment), normalized)
10953
+ (segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
10818
10954
  );
10819
10955
  }
10820
10956
  function validateTrustedWorkspaceRootCandidate(params) {
@@ -10851,7 +10987,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
10851
10987
  init_config_layers();
10852
10988
 
10853
10989
  // src/core/contained-execution/eligibility.ts
10854
- import path34 from "node:path";
10990
+ import path35 from "node:path";
10855
10991
  init_path_utils();
10856
10992
  var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10857
10993
  "command_substitution",
@@ -10868,7 +11004,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
10868
11004
  ]);
10869
11005
  function isContainedUnknownExecutionEligible(config, action, result) {
10870
11006
  const contained = config.sandbox.containedExecution;
10871
- if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path34.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
11007
+ 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) {
10872
11008
  return false;
10873
11009
  }
10874
11010
  const plan = result.effectPlan;
@@ -11148,7 +11284,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
11148
11284
  }
11149
11285
 
11150
11286
  // src/core/classify-tool.ts
11151
- import path49 from "node:path";
11287
+ import path50 from "node:path";
11152
11288
  init_fingerprint2();
11153
11289
  init_path_utils();
11154
11290
  init_scrub();
@@ -11268,13 +11404,13 @@ function worstEffectDecision(decisions) {
11268
11404
 
11269
11405
  // src/core/effect-ir/shell-lower.ts
11270
11406
  init_shell_tokenizer();
11271
- import path48 from "node:path";
11407
+ import path49 from "node:path";
11272
11408
 
11273
11409
  // src/core/verdict/docker-compose-run.ts
11274
- import path36 from "node:path";
11410
+ import path37 from "node:path";
11275
11411
 
11276
11412
  // src/core/verdict/recursive-invocation.ts
11277
- import path35 from "node:path";
11413
+ import path36 from "node:path";
11278
11414
  var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
11279
11415
  var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
11280
11416
  var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
@@ -11336,7 +11472,7 @@ var OSASCRIPT_PROFILE = {
11336
11472
  attachedValuePrefixes: []
11337
11473
  };
11338
11474
  function normalizeInterpreter(value) {
11339
- return path35.basename(value);
11475
+ return path36.basename(value);
11340
11476
  }
11341
11477
  function scriptResult(interpreter, token) {
11342
11478
  if (!token) {
@@ -11566,7 +11702,7 @@ function parseOptions(words, start, options) {
11566
11702
  function decodeDockerComposeRun(tokens) {
11567
11703
  if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
11568
11704
  const words = tokens.filter((token) => token.kind === "word");
11569
- const head = path36.basename(words[0]?.value ?? "");
11705
+ const head = path37.basename(words[0]?.value ?? "");
11570
11706
  let index;
11571
11707
  if (head === "docker-compose") {
11572
11708
  index = 1;
@@ -11607,7 +11743,7 @@ function decodeDockerComposeRun(tokens) {
11607
11743
  }
11608
11744
 
11609
11745
  // src/core/verdict/egress-classify.ts
11610
- import path37 from "node:path";
11746
+ import path38 from "node:path";
11611
11747
  var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
11612
11748
  "aws",
11613
11749
  "curl",
@@ -11656,7 +11792,7 @@ function isEgressToolHead(head) {
11656
11792
  return EGRESS_TOOL_HEADS.has(head);
11657
11793
  }
11658
11794
  function decodeEgressEffects(params) {
11659
- const head = path37.basename(params.tokens[0] ?? "");
11795
+ const head = path38.basename(params.tokens[0] ?? "");
11660
11796
  if (head !== "curl" && head !== "wget" && head !== "gh") {
11661
11797
  return null;
11662
11798
  }
@@ -11664,7 +11800,7 @@ function decodeEgressEffects(params) {
11664
11800
  const provenance = { segment: params.segment };
11665
11801
  const requirements = [];
11666
11802
  for (const file of decoded.files) {
11667
- const resolved = path37.resolve(params.cwd, expandHome2(file));
11803
+ const resolved = path38.resolve(params.cwd, expandHome2(file));
11668
11804
  requirements.push(
11669
11805
  requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
11670
11806
  "egress.explicit_file_read"
@@ -11686,7 +11822,7 @@ function decodeEgressEffects(params) {
11686
11822
  if (file === "-") {
11687
11823
  continue;
11688
11824
  }
11689
- const resolved = path37.resolve(params.cwd, expandHome2(file));
11825
+ const resolved = path38.resolve(params.cwd, expandHome2(file));
11690
11826
  if (resolved === "/dev/null") {
11691
11827
  continue;
11692
11828
  }
@@ -12164,13 +12300,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12164
12300
  if (head === "wget" && !explicitOutput) {
12165
12301
  outputFiles.push(
12166
12302
  ...endpointOutputNames.map(
12167
- (name) => outputDirectory ? path37.join(outputDirectory, name) : name
12303
+ (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12168
12304
  )
12169
12305
  );
12170
12306
  } else if (head === "curl" && remoteNameOutput) {
12171
12307
  outputFiles.push(
12172
12308
  ...endpointOutputNames.map(
12173
- (name) => outputDirectory ? path37.join(outputDirectory, name) : name
12309
+ (name) => outputDirectory ? path38.join(outputDirectory, name) : name
12174
12310
  )
12175
12311
  );
12176
12312
  }
@@ -12184,7 +12320,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
12184
12320
  outputFiles: [
12185
12321
  ...new Set(
12186
12322
  outputFiles.map(
12187
- (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path37.isAbsolute(file) ? path37.join(outputDirectory, file) : file
12323
+ (file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
12188
12324
  )
12189
12325
  )
12190
12326
  ],
@@ -12199,7 +12335,7 @@ function remoteOutputName(spec) {
12199
12335
  } catch {
12200
12336
  pathname = spec.split(/[?#]/, 1)[0] ?? "";
12201
12337
  }
12202
- const name = path37.posix.basename(pathname);
12338
+ const name = path38.posix.basename(pathname);
12203
12339
  return name && name !== "/" ? name : "index.html";
12204
12340
  }
12205
12341
  function decodeGhGrammar(tokens) {
@@ -12364,7 +12500,7 @@ function expandHome2(value) {
12364
12500
  return process.env.HOME ?? value;
12365
12501
  }
12366
12502
  if (value.startsWith("~/")) {
12367
- return path37.join(process.env.HOME ?? "~", value.slice(2));
12503
+ return path38.join(process.env.HOME ?? "~", value.slice(2));
12368
12504
  }
12369
12505
  return value;
12370
12506
  }
@@ -12383,7 +12519,7 @@ function requirement(tag, action, resource, segment, signals) {
12383
12519
  }
12384
12520
 
12385
12521
  // src/core/verdict/git-classifier.ts
12386
- import path38 from "node:path";
12522
+ import path39 from "node:path";
12387
12523
  init_shell_tokenizer();
12388
12524
  var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
12389
12525
  "--copy",
@@ -12479,7 +12615,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
12479
12615
  var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
12480
12616
  var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
12481
12617
  function isGitExecutable(token) {
12482
- return path38.basename(token) === "git";
12618
+ return path39.basename(token) === "git";
12483
12619
  }
12484
12620
  function takesValue(flag) {
12485
12621
  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=");
@@ -12507,7 +12643,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12507
12643
  if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
12508
12644
  const value = tokens[index + 1];
12509
12645
  if (token === "-C" && value) {
12510
- effectiveCwd = path38.resolve(baseCwd, value);
12646
+ effectiveCwd = path39.resolve(baseCwd, value);
12511
12647
  } else if (token === "--work-tree" && value) {
12512
12648
  workTree = value;
12513
12649
  } else if (token === "--git-dir" && value) {
@@ -12517,7 +12653,7 @@ function peelGlobalOptions(tokens, baseCwd) {
12517
12653
  continue;
12518
12654
  }
12519
12655
  if (token.startsWith("-C") && token.length > 2) {
12520
- effectiveCwd = path38.resolve(baseCwd, token.slice(2));
12656
+ effectiveCwd = path39.resolve(baseCwd, token.slice(2));
12521
12657
  index += 1;
12522
12658
  continue;
12523
12659
  }
@@ -12660,7 +12796,7 @@ function looksLikeDiffPathOperand(token) {
12660
12796
  if (!looksLikeFileOperand(token)) {
12661
12797
  return false;
12662
12798
  }
12663
- if (token.startsWith(".") || path38.isAbsolute(token)) {
12799
+ if (token.startsWith(".") || path39.isAbsolute(token)) {
12664
12800
  return true;
12665
12801
  }
12666
12802
  return token.includes(".");
@@ -12668,12 +12804,12 @@ function looksLikeDiffPathOperand(token) {
12668
12804
  function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
12669
12805
  const resolveBase = effectiveCwd ?? baseCwd;
12670
12806
  if (workTree) {
12671
- return path38.resolve(resolveBase, workTree);
12807
+ return path39.resolve(resolveBase, workTree);
12672
12808
  }
12673
12809
  if (gitDir) {
12674
- const resolvedGitDir = path38.resolve(resolveBase, gitDir);
12675
- if (path38.basename(resolvedGitDir) === ".git") {
12676
- return path38.dirname(resolvedGitDir);
12810
+ const resolvedGitDir = path39.resolve(resolveBase, gitDir);
12811
+ if (path39.basename(resolvedGitDir) === ".git") {
12812
+ return path39.dirname(resolvedGitDir);
12677
12813
  }
12678
12814
  }
12679
12815
  return void 0;
@@ -12754,7 +12890,7 @@ function classifyGitCommand(tokens, baseCwd) {
12754
12890
  const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
12755
12891
  const normalizedKey = `git ${subcommand}`;
12756
12892
  const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
12757
- const effectiveGitDir = gitDir ? path38.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12893
+ const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
12758
12894
  const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
12759
12895
  (target, index, targets) => Boolean(target) && targets.indexOf(target) === index
12760
12896
  );
@@ -12906,9 +13042,9 @@ function decodeGitEffects(params) {
12906
13042
  ...subcommand === "push" ? ["tier0_external"] : []
12907
13043
  ];
12908
13044
  const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
12909
- const workTreeRoot = normalized.workTree ? path38.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
12910
- const gitRefRoot = normalized.gitDir ? path38.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
12911
- const gitControlRoot = normalized.gitDir ? gitRefRoot : path38.join(gitRefRoot, ".git");
13045
+ const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
13046
+ const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
13047
+ const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
12912
13048
  const requirements = [];
12913
13049
  if (subcommand === "fetch" || subcommand === "pull") {
12914
13050
  const positionals = gitRemotePositionals(args);
@@ -13067,7 +13203,7 @@ function decodeGitEffects(params) {
13067
13203
  gitRequirement(
13068
13204
  "control_plane.write",
13069
13205
  "control_plane.write",
13070
- { kind: "path", path: path38.join(gitControlRoot, "logs") },
13206
+ { kind: "path", path: path39.join(gitControlRoot, "logs") },
13071
13207
  params.segment,
13072
13208
  [...signals, "git_history_destructive", "git.reflog.mutate"]
13073
13209
  )
@@ -13125,7 +13261,7 @@ function decodeGitEffects(params) {
13125
13261
  gitRequirement(
13126
13262
  "fs.read",
13127
13263
  "fs.read",
13128
- { kind: "path", path: path38.resolve(workTreeRoot, operand) },
13264
+ { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13129
13265
  params.segment,
13130
13266
  [...signals, "git.path.read"]
13131
13267
  )
@@ -13160,7 +13296,7 @@ function decodeGitEffects(params) {
13160
13296
  gitRequirement(
13161
13297
  "fs.write",
13162
13298
  "fs.write",
13163
- { kind: "path", path: path38.resolve(workTreeRoot, operand) },
13299
+ { kind: "path", path: path39.resolve(workTreeRoot, operand) },
13164
13300
  params.segment,
13165
13301
  [...signals, "git.path.write"]
13166
13302
  )
@@ -13344,8 +13480,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
13344
13480
  }
13345
13481
 
13346
13482
  // src/core/verdict/launcher-resolve.ts
13347
- import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
13348
- import path39 from "node:path";
13483
+ import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
13484
+ import path40 from "node:path";
13349
13485
 
13350
13486
  // src/core/verdict/makefile-expand.ts
13351
13487
  var MAX_EXPAND_DEPTH = 16;
@@ -13518,8 +13654,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
13518
13654
  "why"
13519
13655
  ]);
13520
13656
  function readPackageJson(dir) {
13521
- const packagePath = path39.join(dir, "package.json");
13522
- if (!existsSync11(packagePath)) {
13657
+ const packagePath = path40.join(dir, "package.json");
13658
+ if (!existsSync12(packagePath)) {
13523
13659
  return null;
13524
13660
  }
13525
13661
  try {
@@ -13529,17 +13665,17 @@ function readPackageJson(dir) {
13529
13665
  }
13530
13666
  }
13531
13667
  function findPackageJson(startDir, stopDir) {
13532
- let current = path39.resolve(startDir);
13533
- const stop = path39.resolve(stopDir);
13668
+ let current = path40.resolve(startDir);
13669
+ const stop = path40.resolve(stopDir);
13534
13670
  while (true) {
13535
- const packagePath = path39.join(current, "package.json");
13536
- if (existsSync11(packagePath)) {
13671
+ const packagePath = path40.join(current, "package.json");
13672
+ if (existsSync12(packagePath)) {
13537
13673
  return packagePath;
13538
13674
  }
13539
- if (current === stop || current === path39.dirname(current)) {
13540
- return existsSync11(packagePath) ? packagePath : null;
13675
+ if (current === stop || current === path40.dirname(current)) {
13676
+ return existsSync12(packagePath) ? packagePath : null;
13541
13677
  }
13542
- const parent = path39.dirname(current);
13678
+ const parent = path40.dirname(current);
13543
13679
  if (!parent.startsWith(stop) && parent !== current) {
13544
13680
  }
13545
13681
  if (parent === current) {
@@ -13596,7 +13732,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
13596
13732
  }
13597
13733
  return { recipes: [], opaque: true, reason: "package_json_missing" };
13598
13734
  }
13599
- const pkg = readPackageJson(path39.dirname(packagePath));
13735
+ const pkg = readPackageJson(path40.dirname(packagePath));
13600
13736
  const scripts = pkg?.scripts;
13601
13737
  if (!scripts || typeof scripts !== "object") {
13602
13738
  return { recipes: [], opaque: true, reason: "package_scripts_missing" };
@@ -13687,20 +13823,20 @@ function parseMakefileRecipeContent(content) {
13687
13823
  function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13688
13824
  const candidates = ["Makefile", "makefile", "GNUmakefile"];
13689
13825
  let makefilePath = null;
13690
- let searchDir = path39.resolve(cwd);
13691
- const stop = path39.resolve(repoRoot);
13826
+ let searchDir = path40.resolve(cwd);
13827
+ const stop = path40.resolve(repoRoot);
13692
13828
  while (true) {
13693
13829
  for (const name of candidates) {
13694
- const candidate = path39.join(searchDir, name);
13695
- if (existsSync11(candidate)) {
13830
+ const candidate = path40.join(searchDir, name);
13831
+ if (existsSync12(candidate)) {
13696
13832
  makefilePath = candidate;
13697
13833
  break;
13698
13834
  }
13699
13835
  }
13700
- if (makefilePath || searchDir === stop || searchDir === path39.dirname(searchDir)) {
13836
+ if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
13701
13837
  break;
13702
13838
  }
13703
- searchDir = path39.dirname(searchDir);
13839
+ searchDir = path40.dirname(searchDir);
13704
13840
  }
13705
13841
  if (!makefilePath) {
13706
13842
  return { recipes: [], opaque: true, reason: "unknown_local_effect" };
@@ -13727,7 +13863,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
13727
13863
  }
13728
13864
  const entry = targets.get(name);
13729
13865
  if (!entry) {
13730
- if (!existsSync11(path39.resolve(path39.dirname(makefilePath), name))) {
13866
+ if (!existsSync12(path40.resolve(path40.dirname(makefilePath), name))) {
13731
13867
  hasUndefinedPrerequisite = true;
13732
13868
  }
13733
13869
  return;
@@ -13826,7 +13962,7 @@ function resolveLauncherRecipe(params) {
13826
13962
  }
13827
13963
 
13828
13964
  // src/core/verdict/parser.ts
13829
- import path40 from "node:path";
13965
+ import path41 from "node:path";
13830
13966
 
13831
13967
  // src/core/shell-substitution.ts
13832
13968
  function findStructuralCommandSubstitutions(command) {
@@ -14066,7 +14202,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
14066
14202
  ".sh"
14067
14203
  ]);
14068
14204
  function normalizeHead(token) {
14069
- const base = path40.basename(token);
14205
+ const base = path41.basename(token);
14070
14206
  if (base && base !== "." && base !== "..") {
14071
14207
  return base;
14072
14208
  }
@@ -14376,7 +14512,7 @@ function isBareInterpreter(tokens) {
14376
14512
  return false;
14377
14513
  }
14378
14514
  const scriptArg = args.find((token) => !token.startsWith("-"));
14379
- if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path40.extname(scriptArg))) {
14515
+ if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
14380
14516
  return false;
14381
14517
  }
14382
14518
  if (scriptArg) {
@@ -14616,7 +14752,7 @@ function mergeNodes(nodes) {
14616
14752
  }
14617
14753
 
14618
14754
  // src/core/effect-ir/shell-lower/argv-delegate-gate.ts
14619
- import path41 from "node:path";
14755
+ import path42 from "node:path";
14620
14756
  var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
14621
14757
  "sudo",
14622
14758
  "env",
@@ -14637,7 +14773,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
14637
14773
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
14638
14774
  return false;
14639
14775
  }
14640
- const innerHead = path41.basename(innerTokens[0] ?? "");
14776
+ const innerHead = path42.basename(innerTokens[0] ?? "");
14641
14777
  if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
14642
14778
  return false;
14643
14779
  }
@@ -14744,7 +14880,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
14744
14880
 
14745
14881
  // src/core/effect-ir/shell-lower/tokens.ts
14746
14882
  init_shell_tokenizer();
14747
- import path42 from "node:path";
14883
+ import path43 from "node:path";
14748
14884
  var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
14749
14885
  var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
14750
14886
  var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
@@ -14888,9 +15024,9 @@ function resolvePathOperand(operand, cwd) {
14888
15024
  return process.env.HOME ?? operand;
14889
15025
  }
14890
15026
  if (operand.startsWith("~/")) {
14891
- return path42.join(process.env.HOME ?? "~", operand.slice(2));
15027
+ return path43.join(process.env.HOME ?? "~", operand.slice(2));
14892
15028
  }
14893
- return path42.resolve(cwd, operand);
15029
+ return path43.resolve(cwd, operand);
14894
15030
  }
14895
15031
  function isOptionToken(value) {
14896
15032
  return value.startsWith("-") || value.startsWith("+");
@@ -14902,7 +15038,7 @@ function pipeToShell(command) {
14902
15038
  return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
14903
15039
  }
14904
15040
  function executableBaseName(head) {
14905
- return path42.basename(head);
15041
+ return path43.basename(head);
14906
15042
  }
14907
15043
  function isMetadataOnlyArgv(argv) {
14908
15044
  return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
@@ -15021,15 +15157,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
15021
15157
  }
15022
15158
 
15023
15159
  // src/core/effect-ir/shell-lower/decode-process.ts
15024
- import path46 from "node:path";
15160
+ import path47 from "node:path";
15025
15161
 
15026
15162
  // src/core/effect-ir/shell-lower/decoders/belay.ts
15027
- import path43 from "node:path";
15163
+ import path44 from "node:path";
15028
15164
  function decodeBelay(args, repoRoot, segment) {
15029
15165
  const [section, operation, key] = args;
15030
15166
  const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
15031
15167
  const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
15032
15168
  const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
15169
+ const approvalAuthorityCommand = [
15170
+ "approval-token",
15171
+ "approve",
15172
+ "revoke",
15173
+ "standing-allow"
15174
+ ].includes(section ?? "");
15175
+ const configTrustMutation = section === "config" && operation === "trust";
15033
15176
  if (judgeCommand || configRead || configJudgeMutation) {
15034
15177
  return [
15035
15178
  processRequirement("belay", "inspect", segment, [
@@ -15038,12 +15181,23 @@ function decodeBelay(args, repoRoot, segment) {
15038
15181
  ])
15039
15182
  ];
15040
15183
  }
15184
+ if (approvalAuthorityCommand || configTrustMutation) {
15185
+ return [
15186
+ requirement2(
15187
+ "control_plane.write",
15188
+ "control_plane.write",
15189
+ { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15190
+ segment,
15191
+ [approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
15192
+ )
15193
+ ];
15194
+ }
15041
15195
  if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
15042
15196
  return [
15043
15197
  requirement2(
15044
15198
  "control_plane.write",
15045
15199
  "control_plane.write",
15046
- { kind: "path", path: path43.join(repoRoot, ".belay-control-plane") },
15200
+ { kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
15047
15201
  segment,
15048
15202
  ["belay.config_non_judge_mutation"]
15049
15203
  )
@@ -15284,7 +15438,7 @@ function validDockerInfo(args) {
15284
15438
  // src/core/effect-ir/shell-lower/decoders/filesystem.ts
15285
15439
  init_git_resource_identity();
15286
15440
  import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
15287
- import path44 from "node:path";
15441
+ import path45 from "node:path";
15288
15442
  function decodeCopyMove(head, args, cwd, segment) {
15289
15443
  const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
15290
15444
  const operands = [];
@@ -15412,11 +15566,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
15412
15566
  function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
15413
15567
  try {
15414
15568
  if (finalOperandIsSymlink) {
15415
- return path44.join(realpathSync4.native(path44.dirname(targetPath)), path44.basename(targetPath));
15569
+ return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
15416
15570
  }
15417
15571
  return realpathSync4.native(targetPath);
15418
15572
  } catch {
15419
- return path44.resolve(targetPath);
15573
+ return path45.resolve(targetPath);
15420
15574
  }
15421
15575
  }
15422
15576
  function isSymbolicLink(targetPath) {
@@ -15427,8 +15581,8 @@ function isSymbolicLink(targetPath) {
15427
15581
  }
15428
15582
  }
15429
15583
  function pathContains(ancestor, candidate) {
15430
- const relative = path44.relative(path44.resolve(ancestor), path44.resolve(candidate));
15431
- return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
15584
+ const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
15585
+ return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
15432
15586
  }
15433
15587
  function filesystemReadOperands(head, args) {
15434
15588
  switch (head) {
@@ -15568,7 +15722,7 @@ function decodePrisma(args, env, repoRoot, segment) {
15568
15722
 
15569
15723
  // src/core/effect-ir/shell-lower/decoders/ruby.ts
15570
15724
  init_path_utils();
15571
- import path45 from "node:path";
15725
+ import path46 from "node:path";
15572
15726
  var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
15573
15727
  function railsReadOnlySubcommand(args) {
15574
15728
  const subcommand = args[0];
@@ -15578,7 +15732,7 @@ function railsReadOnlySubcommand(args) {
15578
15732
  return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
15579
15733
  }
15580
15734
  function isRubyTestScript(scriptPath) {
15581
- const base = path45.basename(scriptPath);
15735
+ const base = path46.basename(scriptPath);
15582
15736
  return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
15583
15737
  }
15584
15738
  function parseRubyTestInvocation(args) {
@@ -15992,7 +16146,7 @@ function decodeProcessOrFilesystem(params) {
15992
16146
  requirement2(
15993
16147
  "fs.read",
15994
16148
  "fs.read",
15995
- { kind: "path", path: path46.resolve(cwd, syntax) },
16149
+ { kind: "path", path: path47.resolve(cwd, syntax) },
15996
16150
  segment,
15997
16151
  ["shell.syntax_source_read"]
15998
16152
  )
@@ -16198,7 +16352,7 @@ function packageExecInnerIsMetadata(peel) {
16198
16352
 
16199
16353
  // src/core/effect-ir/shell-lower/segment.ts
16200
16354
  init_shell_tokenizer();
16201
- import path47 from "node:path";
16355
+ import path48 from "node:path";
16202
16356
  var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
16203
16357
  var SHELL_GLOB_PATTERN = /[*?[]/;
16204
16358
  function requiresKnownCwd(requirementValue) {
@@ -16213,11 +16367,11 @@ function joinNestedOpacity(outer, nested) {
16213
16367
  }
16214
16368
  function startsLocalPostgresService(command) {
16215
16369
  const tokens = tokenizeShell(command);
16216
- return path47.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16370
+ return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
16217
16371
  }
16218
16372
  function resolveCdTransition(command, currentCwd) {
16219
16373
  const tokens = tokenizeShell(command);
16220
- if (path47.basename(tokens[0] ?? "") !== "cd") {
16374
+ if (path48.basename(tokens[0] ?? "") !== "cd") {
16221
16375
  return null;
16222
16376
  }
16223
16377
  const target = tokens[1] ?? "~";
@@ -16380,7 +16534,7 @@ function lowerSegment(command, context) {
16380
16534
  stripStructuredRedirects(lexed.tokens),
16381
16535
  stripRedirects(parsedTokens)
16382
16536
  );
16383
- const head = path48.basename(tokens[0] ?? parsed.head);
16537
+ const head = path49.basename(tokens[0] ?? parsed.head);
16384
16538
  let opacity = segmentOpacity(command);
16385
16539
  const signals = /* @__PURE__ */ new Set();
16386
16540
  const requirements = [];
@@ -16963,17 +17117,41 @@ function verdictToClassifyResult(result, config, options = {}) {
16963
17117
 
16964
17118
  // src/core/classify-tool.ts
16965
17119
  var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
16966
- var FILE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write"]);
16967
- var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
16968
- "edit",
16969
- "multiedit",
16970
- "multi_edit",
16971
- "patch",
16972
- "strreplace",
16973
- "str_replace"
16974
- ]);
16975
- var FILE_DELETE_TOOL_NAMES = /* @__PURE__ */ new Set(["delete"]);
16976
- var APPLY_PATCH_TOOL_NAMES = /* @__PURE__ */ new Set(["apply_patch", "applypatch"]);
17120
+ function toolInputRecord(payload) {
17121
+ const toolInput = payload.tool_input;
17122
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
17123
+ return null;
17124
+ }
17125
+ return toolInput;
17126
+ }
17127
+ function hasMutationFields(input) {
17128
+ return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
17129
+ }
17130
+ function inferToolPayloadEffect(payload, toolName) {
17131
+ if (extractShellCommand(payload)) {
17132
+ return "shell";
17133
+ }
17134
+ const input = toolInputRecord(payload);
17135
+ if (!input) {
17136
+ return "indeterminate";
17137
+ }
17138
+ if (hasMutationFields(input)) {
17139
+ return "file_mutation";
17140
+ }
17141
+ if (extractPatch(payload)) {
17142
+ return "apply_patch";
17143
+ }
17144
+ if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
17145
+ return "search_read";
17146
+ }
17147
+ if (typeof input.file_path === "string") {
17148
+ return "file_read";
17149
+ }
17150
+ if (typeof input.path === "string" || typeof input.target_file === "string") {
17151
+ return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
17152
+ }
17153
+ return "indeterminate";
17154
+ }
16977
17155
  function policyAuth(options) {
16978
17156
  if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
16979
17157
  return void 0;
@@ -17153,58 +17331,367 @@ function classifyFileMutationWithPolicy(params) {
17153
17331
  boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17154
17332
  };
17155
17333
  }
17156
- async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17157
- const toolName = String(payload.tool_name ?? "");
17158
- const toolKind = normalizedToolName(toolName);
17159
- const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17160
- const protectedRoots = [
17161
- ...options.protectedArtifactRoots ?? [],
17162
- ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17163
- ];
17164
- if (toolKind === "shell") {
17165
- const command = extractShellCommand(payload);
17166
- if (!command) {
17167
- if (options.unknownLocalEffect === "deny") {
17168
- return {
17169
- verdict: "deny_pending_approval",
17170
- reason: "tool_shell_missing_command",
17171
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17172
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17173
- assessment: {
17174
- reversibility: "irreversible",
17175
- external: false,
17176
- blastRadius: "tool shell",
17177
- confidence: 0.85,
17178
- signals: ["missing_command"]
17179
- }
17180
- };
17181
- }
17182
- return {
17183
- verdict: "allow_flagged",
17184
- reason: "tool_shell_missing_command",
17185
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17186
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17187
- assessment: {
17188
- reversibility: "recoverable_with_cost",
17189
- external: false,
17190
- blastRadius: "tool shell",
17191
- confidence: 0.5,
17192
- signals: ["missing_command"]
17193
- }
17194
- };
17195
- }
17196
- const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17197
- return {
17198
- ...shellResult,
17199
- summary: command
17200
- };
17201
- }
17202
- if (FILE_WRITE_TOOL_NAMES.has(toolKind) || FILE_EDIT_TOOL_NAMES.has(toolKind) || FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17203
- const filePath = extractFilePath(payload);
17204
- if (!filePath) {
17205
- if (options.unknownLocalEffect === "deny") {
17206
- return {
17207
- verdict: "deny_pending_approval",
17334
+ function classifyFileReadWithPolicy(params) {
17335
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
17336
+ const { request, decision } = evaluateFileReadPolicy(
17337
+ {
17338
+ hookKind: "tool",
17339
+ toolKind: params.toolKind,
17340
+ filePath: params.filePath,
17341
+ resolvedPath: params.resolvedPath,
17342
+ repoRoot: params.repoRoot,
17343
+ cwd: params.cwd,
17344
+ inputFingerprint: fingerprint,
17345
+ signals: params.signals,
17346
+ locationLabel: params.locationLabel,
17347
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
17348
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
17349
+ },
17350
+ params.config,
17351
+ policyAuth(params.options)
17352
+ );
17353
+ if (policyDecisionRequiresAsk(decision)) {
17354
+ return {
17355
+ verdict: "deny_pending_approval",
17356
+ reason: policyReasonToLegacyReason(decision),
17357
+ summary: params.filePath,
17358
+ fingerprint,
17359
+ assessment: {
17360
+ reversibility: "reversible",
17361
+ external: params.locationLabel === "outside_repo",
17362
+ blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
17363
+ confidence: 0.85,
17364
+ signals: [...params.signals, ...decision.signals]
17365
+ },
17366
+ capabilityRequests: [request],
17367
+ authorizationDecision: decision,
17368
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17369
+ };
17370
+ }
17371
+ return {
17372
+ verdict: "allow",
17373
+ reason: "effect.fs_read",
17374
+ summary: params.filePath,
17375
+ fingerprint,
17376
+ assessment: {
17377
+ reversibility: "reversible",
17378
+ external: false,
17379
+ blastRadius: "tool scope",
17380
+ confidence: 0.88,
17381
+ signals: [...params.signals, ...decision.signals]
17382
+ },
17383
+ capabilityRequests: [request],
17384
+ authorizationDecision: decision,
17385
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17386
+ };
17387
+ }
17388
+ function classifyFilePathMutation(params) {
17389
+ const {
17390
+ toolName,
17391
+ toolKind,
17392
+ filePath,
17393
+ repoRoot,
17394
+ cwd,
17395
+ config,
17396
+ options,
17397
+ sensitivePaths,
17398
+ protectedRoots,
17399
+ isDelete
17400
+ } = params;
17401
+ const signals = [];
17402
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17403
+ const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17404
+ if (hitsProtectedRoot) {
17405
+ signals.push("control_plane_path");
17406
+ return classifyFileMutationWithPolicy({
17407
+ toolName,
17408
+ toolKind,
17409
+ filePath,
17410
+ resolvedPath,
17411
+ repoRoot,
17412
+ cwd,
17413
+ config,
17414
+ options,
17415
+ signals,
17416
+ isDelete,
17417
+ locationLabel: "control_plane"
17418
+ });
17419
+ }
17420
+ const workspaceMatch = resolveWorkspaceRootMatch(
17421
+ repoRoot,
17422
+ options.trustedWorkspaceRoots,
17423
+ resolvedPath
17424
+ );
17425
+ if (workspaceMatch === null) {
17426
+ signals.push("outside_repo_path");
17427
+ return classifyFileMutationWithPolicy({
17428
+ toolName,
17429
+ toolKind,
17430
+ filePath,
17431
+ resolvedPath,
17432
+ repoRoot,
17433
+ cwd,
17434
+ config,
17435
+ options,
17436
+ signals,
17437
+ isDelete,
17438
+ locationLabel: "outside_repo"
17439
+ });
17440
+ }
17441
+ if (workspaceMatch.kind === "trusted") {
17442
+ signals.push("trusted_workspace_root");
17443
+ }
17444
+ const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17445
+ const workspacePrescan = mutationPrescanRequiresAsk({
17446
+ targets: [filePath],
17447
+ cwd,
17448
+ repoRoot,
17449
+ trustedCwd,
17450
+ trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17451
+ sensitivePaths
17452
+ });
17453
+ if (workspacePrescan) {
17454
+ return classifyFileMutationWithPolicy({
17455
+ toolName,
17456
+ toolKind,
17457
+ filePath,
17458
+ resolvedPath,
17459
+ repoRoot,
17460
+ cwd,
17461
+ config,
17462
+ options,
17463
+ signals,
17464
+ isDelete,
17465
+ locationLabel: "sensitive_path"
17466
+ });
17467
+ }
17468
+ const relativePath = workspaceMatch.relativePath;
17469
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17470
+ signals.push("sensitive_path");
17471
+ return classifyFileMutationWithPolicy({
17472
+ toolName,
17473
+ toolKind,
17474
+ filePath,
17475
+ resolvedPath,
17476
+ repoRoot,
17477
+ cwd,
17478
+ config,
17479
+ options,
17480
+ signals,
17481
+ isDelete,
17482
+ locationLabel: "sensitive_path"
17483
+ });
17484
+ }
17485
+ if (isDelete) {
17486
+ if (isGitPath(resolvedPath, repoRoot)) {
17487
+ signals.push("protected_artifact");
17488
+ return classifyFileMutationWithPolicy({
17489
+ toolName,
17490
+ toolKind,
17491
+ filePath,
17492
+ resolvedPath,
17493
+ repoRoot,
17494
+ cwd,
17495
+ config,
17496
+ options,
17497
+ signals,
17498
+ isDelete: true,
17499
+ locationLabel: "sensitive_path"
17500
+ });
17501
+ }
17502
+ signals.push("file_delete");
17503
+ return classifyFileMutationWithPolicy({
17504
+ toolName,
17505
+ toolKind,
17506
+ filePath,
17507
+ resolvedPath,
17508
+ repoRoot,
17509
+ cwd,
17510
+ config,
17511
+ options,
17512
+ signals,
17513
+ isDelete: true,
17514
+ locationLabel: "repo_local"
17515
+ });
17516
+ }
17517
+ signals.push("file_mutation");
17518
+ return classifyFileMutationWithPolicy({
17519
+ toolName,
17520
+ toolKind,
17521
+ filePath,
17522
+ resolvedPath,
17523
+ repoRoot,
17524
+ cwd,
17525
+ config,
17526
+ options,
17527
+ signals,
17528
+ isDelete: false,
17529
+ locationLabel: "repo_local"
17530
+ });
17531
+ }
17532
+ function classifyFilePathRead(params) {
17533
+ const {
17534
+ toolName,
17535
+ toolKind,
17536
+ filePath,
17537
+ repoRoot,
17538
+ cwd,
17539
+ config,
17540
+ options,
17541
+ sensitivePaths,
17542
+ protectedRoots
17543
+ } = params;
17544
+ const signals = ["effect.fs_read"];
17545
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17546
+ if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
17547
+ signals.push("control_plane_path");
17548
+ return classifyFileReadWithPolicy({
17549
+ toolName,
17550
+ toolKind,
17551
+ filePath,
17552
+ resolvedPath,
17553
+ repoRoot,
17554
+ cwd,
17555
+ config,
17556
+ options,
17557
+ signals,
17558
+ locationLabel: "control_plane"
17559
+ });
17560
+ }
17561
+ const workspaceMatch = resolveWorkspaceRootMatch(
17562
+ repoRoot,
17563
+ options.trustedWorkspaceRoots,
17564
+ resolvedPath
17565
+ );
17566
+ if (workspaceMatch === null) {
17567
+ signals.push("outside_repo_path");
17568
+ return classifyFileReadWithPolicy({
17569
+ toolName,
17570
+ toolKind,
17571
+ filePath,
17572
+ resolvedPath,
17573
+ repoRoot,
17574
+ cwd,
17575
+ config,
17576
+ options,
17577
+ signals,
17578
+ locationLabel: "outside_repo"
17579
+ });
17580
+ }
17581
+ const relativePath = workspaceMatch.relativePath;
17582
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17583
+ signals.push("sensitive_path");
17584
+ return classifyFileReadWithPolicy({
17585
+ toolName,
17586
+ toolKind,
17587
+ filePath,
17588
+ resolvedPath,
17589
+ repoRoot,
17590
+ cwd,
17591
+ config,
17592
+ options,
17593
+ signals,
17594
+ locationLabel: "sensitive_path"
17595
+ });
17596
+ }
17597
+ return classifyFileReadWithPolicy({
17598
+ toolName,
17599
+ toolKind,
17600
+ filePath,
17601
+ resolvedPath,
17602
+ repoRoot,
17603
+ cwd,
17604
+ config,
17605
+ options,
17606
+ signals,
17607
+ locationLabel: "repo_local"
17608
+ });
17609
+ }
17610
+ function indeterminateToolResult(toolName, payload, repoRoot, options) {
17611
+ const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
17612
+ const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
17613
+ if (options.unknownLocalEffect === "deny") {
17614
+ return {
17615
+ verdict: "deny_pending_approval",
17616
+ reason: "indeterminate_tool_effect",
17617
+ summary,
17618
+ fingerprint,
17619
+ assessment: {
17620
+ reversibility: "irreversible",
17621
+ external: false,
17622
+ blastRadius: "unknown tool action",
17623
+ confidence: 0.5,
17624
+ signals: ["indeterminate_tool_effect"]
17625
+ }
17626
+ };
17627
+ }
17628
+ return {
17629
+ verdict: "allow_flagged",
17630
+ reason: "indeterminate_tool_effect",
17631
+ summary,
17632
+ fingerprint,
17633
+ assessment: {
17634
+ reversibility: "recoverable_with_cost",
17635
+ external: false,
17636
+ blastRadius: "tool scope",
17637
+ confidence: 0.5,
17638
+ signals: ["indeterminate_tool_effect"]
17639
+ }
17640
+ };
17641
+ }
17642
+ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17643
+ const toolName = String(payload.tool_name ?? "");
17644
+ const toolKind = normalizedToolName(toolName);
17645
+ const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
17646
+ const protectedRoots = [
17647
+ ...options.protectedArtifactRoots ?? [],
17648
+ ...options.controlPlaneDir ? [options.controlPlaneDir] : []
17649
+ ];
17650
+ if (toolKind === "shell") {
17651
+ const command = extractShellCommand(payload);
17652
+ if (!command) {
17653
+ if (options.unknownLocalEffect === "deny") {
17654
+ return {
17655
+ verdict: "deny_pending_approval",
17656
+ reason: "tool_shell_missing_command",
17657
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17658
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17659
+ assessment: {
17660
+ reversibility: "irreversible",
17661
+ external: false,
17662
+ blastRadius: "tool shell",
17663
+ confidence: 0.85,
17664
+ signals: ["missing_command"]
17665
+ }
17666
+ };
17667
+ }
17668
+ return {
17669
+ verdict: "allow_flagged",
17670
+ reason: "tool_shell_missing_command",
17671
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17672
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17673
+ assessment: {
17674
+ reversibility: "recoverable_with_cost",
17675
+ external: false,
17676
+ blastRadius: "tool shell",
17677
+ confidence: 0.5,
17678
+ signals: ["missing_command"]
17679
+ }
17680
+ };
17681
+ }
17682
+ const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
17683
+ return {
17684
+ ...shellResult,
17685
+ summary: command
17686
+ };
17687
+ }
17688
+ const effect = inferToolPayloadEffect(payload, toolName);
17689
+ if (effect === "file_mutation" || effect === "file_delete") {
17690
+ const filePath2 = extractFilePath(payload);
17691
+ if (!filePath2) {
17692
+ if (options.unknownLocalEffect === "deny") {
17693
+ return {
17694
+ verdict: "deny_pending_approval",
17208
17695
  reason: "file_mutation_missing_path",
17209
17696
  summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17210
17697
  fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
@@ -17231,138 +17718,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17231
17718
  }
17232
17719
  };
17233
17720
  }
17234
- const signals = [];
17235
- const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(cwd, filePath);
17236
- const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17237
- if (hitsProtectedRoot) {
17238
- signals.push("control_plane_path");
17239
- return classifyFileMutationWithPolicy({
17240
- toolName,
17241
- toolKind,
17242
- filePath,
17243
- resolvedPath,
17244
- repoRoot,
17245
- cwd,
17246
- config,
17247
- options,
17248
- signals,
17249
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17250
- locationLabel: "control_plane"
17251
- });
17252
- }
17253
- const workspaceMatch = resolveWorkspaceRootMatch(
17254
- repoRoot,
17255
- options.trustedWorkspaceRoots,
17256
- resolvedPath
17257
- );
17258
- if (workspaceMatch === null) {
17259
- signals.push("outside_repo_path");
17260
- return classifyFileMutationWithPolicy({
17261
- toolName,
17262
- toolKind,
17263
- filePath,
17264
- resolvedPath,
17265
- repoRoot,
17266
- cwd,
17267
- config,
17268
- options,
17269
- signals,
17270
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17271
- locationLabel: "outside_repo"
17272
- });
17273
- }
17274
- if (workspaceMatch.kind === "trusted") {
17275
- signals.push("trusted_workspace_root");
17276
- }
17277
- const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17278
- const workspacePrescan = mutationPrescanRequiresAsk({
17279
- targets: [filePath],
17280
- cwd,
17281
- repoRoot,
17282
- trustedCwd,
17283
- trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17284
- sensitivePaths
17285
- });
17286
- if (workspacePrescan) {
17287
- return classifyFileMutationWithPolicy({
17288
- toolName,
17289
- toolKind,
17290
- filePath,
17291
- resolvedPath,
17292
- repoRoot,
17293
- cwd,
17294
- config,
17295
- options,
17296
- signals,
17297
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17298
- locationLabel: "sensitive_path"
17299
- });
17300
- }
17301
- const relativePath = workspaceMatch.relativePath;
17302
- if (matchesSensitivePath(relativePath, sensitivePaths)) {
17303
- signals.push("sensitive_path");
17304
- return classifyFileMutationWithPolicy({
17305
- toolName,
17306
- toolKind,
17307
- filePath,
17308
- resolvedPath,
17309
- repoRoot,
17310
- cwd,
17311
- config,
17312
- options,
17313
- signals,
17314
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17315
- locationLabel: "sensitive_path"
17316
- });
17317
- }
17318
- if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17319
- if (isGitPath(resolvedPath, repoRoot)) {
17320
- signals.push("protected_artifact");
17321
- return classifyFileMutationWithPolicy({
17322
- toolName,
17323
- toolKind,
17324
- filePath,
17325
- resolvedPath,
17326
- repoRoot,
17327
- cwd,
17328
- config,
17329
- options,
17330
- signals,
17331
- isDelete: true,
17332
- locationLabel: "sensitive_path"
17333
- });
17334
- }
17335
- signals.push("file_delete");
17336
- return classifyFileMutationWithPolicy({
17337
- toolName,
17338
- toolKind,
17339
- filePath,
17340
- resolvedPath,
17341
- repoRoot,
17342
- cwd,
17343
- config,
17344
- options,
17345
- signals,
17346
- isDelete: true,
17347
- locationLabel: "repo_local"
17348
- });
17349
- }
17350
- signals.push("file_mutation");
17351
- return classifyFileMutationWithPolicy({
17721
+ return classifyFilePathMutation({
17352
17722
  toolName,
17353
17723
  toolKind,
17354
- filePath,
17355
- resolvedPath,
17724
+ filePath: filePath2,
17356
17725
  repoRoot,
17357
17726
  cwd,
17358
17727
  config,
17359
17728
  options,
17360
- signals,
17361
- isDelete: false,
17362
- locationLabel: "repo_local"
17729
+ sensitivePaths,
17730
+ protectedRoots,
17731
+ isDelete: effect === "file_delete"
17363
17732
  });
17364
17733
  }
17365
- if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
17734
+ if (effect === "apply_patch") {
17366
17735
  const patch = extractPatch(payload);
17367
17736
  const targets = patch ? applyPatchTargets2(patch) : [];
17368
17737
  if (targets.length === 0) {
@@ -17400,7 +17769,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17400
17769
  const result = await classifyToolUse(
17401
17770
  {
17402
17771
  tool_name: target.delete ? "Delete" : "Write",
17403
- tool_input: { path: target.path }
17772
+ tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
17404
17773
  },
17405
17774
  repoRoot,
17406
17775
  cwd,
@@ -17426,19 +17795,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17426
17795
  }
17427
17796
  };
17428
17797
  }
17429
- return {
17430
- verdict: "allow",
17431
- reason: "unclassified_tool",
17432
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17433
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17434
- assessment: {
17435
- reversibility: "reversible",
17436
- external: false,
17437
- blastRadius: "tool scope",
17438
- confidence: 0.5,
17439
- signals: ["unclassified_tool"]
17798
+ if (effect === "file_read") {
17799
+ const filePath2 = extractFilePath(payload);
17800
+ if (!filePath2) {
17801
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17440
17802
  }
17441
- };
17803
+ return classifyFilePathRead({
17804
+ toolName,
17805
+ toolKind,
17806
+ filePath: filePath2,
17807
+ repoRoot,
17808
+ cwd,
17809
+ config,
17810
+ options,
17811
+ sensitivePaths,
17812
+ protectedRoots
17813
+ });
17814
+ }
17815
+ if (effect === "search_read") {
17816
+ return {
17817
+ verdict: "allow",
17818
+ reason: "effect.search_read",
17819
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17820
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17821
+ assessment: {
17822
+ reversibility: "reversible",
17823
+ external: false,
17824
+ blastRadius: "tool scope",
17825
+ confidence: 0.88,
17826
+ signals: ["effect.search_read"]
17827
+ }
17828
+ };
17829
+ }
17830
+ const filePath = extractFilePath(payload);
17831
+ if (filePath) {
17832
+ return classifyFilePathMutation({
17833
+ toolName,
17834
+ toolKind,
17835
+ filePath,
17836
+ repoRoot,
17837
+ cwd,
17838
+ config,
17839
+ options,
17840
+ sensitivePaths,
17841
+ protectedRoots,
17842
+ isDelete: toolKind === "delete"
17843
+ });
17844
+ }
17845
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17442
17846
  }
17443
17847
 
17444
17848
  // src/core/gate-engine.ts
@@ -17773,15 +18177,8 @@ function gateEnabledForAction(config, action) {
17773
18177
  if (action.kind === "subagent") {
17774
18178
  return config.gates.subagent;
17775
18179
  }
17776
- const toolName = action.toolName ?? String(action.payload?.tool_name ?? "");
17777
- if (toolName === "Shell") {
17778
- return config.gates.toolShell;
17779
- }
17780
- if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
17781
- return config.gates.fileMutation;
17782
- }
17783
- if (toolName === "Task") {
17784
- return config.gates.subagent;
18180
+ if (action.kind === "tool") {
18181
+ return config.gates.fileMutation || config.gates.toolShell;
17785
18182
  }
17786
18183
  return true;
17787
18184
  }
@@ -17818,7 +18215,7 @@ function hashDecisionConfig(config) {
17818
18215
  init_fingerprint2();
17819
18216
 
17820
18217
  // src/version.ts
17821
- var PACKAGE_VERSION = "0.9.4";
18218
+ var PACKAGE_VERSION = "0.10.1";
17822
18219
 
17823
18220
  // src/runtime-provenance.ts
17824
18221
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -17891,56 +18288,56 @@ function capabilityRequestsBlockRecovery(requests) {
17891
18288
  init_fingerprint2();
17892
18289
  init_path_utils();
17893
18290
  import { randomUUID as randomUUID5 } from "node:crypto";
17894
- import { existsSync as existsSync15 } from "node:fs";
17895
- import { mkdir as mkdir11, readdir as readdir3, readFile as readFile12, rename as rename3, rm as rm6 } from "node:fs/promises";
17896
- import path54 from "node:path";
18291
+ import { existsSync as existsSync16 } from "node:fs";
18292
+ import { mkdir as mkdir12, readdir as readdir3, readFile as readFile13, rename as rename4, rm as rm6 } from "node:fs/promises";
18293
+ import path55 from "node:path";
17897
18294
 
17898
18295
  // src/core/recovery/artifact-store.ts
17899
18296
  init_fingerprint2();
17900
18297
  init_path_utils();
17901
18298
  import { randomUUID as randomUUID4 } from "node:crypto";
17902
- import { existsSync as existsSync13 } from "node:fs";
17903
- import { lstat as lstat7, mkdir as mkdir10, open as open5, readdir as readdir2, readFile as readFile9, rename as rename2, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
17904
- import path51 from "node:path";
18299
+ import { existsSync as existsSync14 } from "node:fs";
18300
+ import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile10, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
18301
+ import path52 from "node:path";
17905
18302
 
17906
18303
  // src/core/recovery/snapshot-node.ts
17907
18304
  init_fingerprint2();
17908
18305
  init_path_utils();
17909
18306
  import { createHash as createHash12 } from "node:crypto";
17910
- import { existsSync as existsSync12 } from "node:fs";
18307
+ import { existsSync as existsSync13 } from "node:fs";
17911
18308
  import {
17912
- chmod as chmod3,
18309
+ chmod as chmod4,
17913
18310
  copyFile as copyFile2,
17914
18311
  lstat as lstat6,
17915
- mkdir as mkdir9,
17916
- open as open4,
17917
- readFile as readFile8,
18312
+ mkdir as mkdir10,
18313
+ open as open5,
18314
+ readFile as readFile9,
17918
18315
  readlink as readlink4,
17919
18316
  rm as rm4,
17920
18317
  rmdir as rmdir2,
17921
18318
  symlink as symlink3,
17922
18319
  writeFile as writeFile6
17923
18320
  } from "node:fs/promises";
17924
- import path50 from "node:path";
18321
+ import path51 from "node:path";
17925
18322
  var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
17926
18323
  function validRecoveryRelativePath(relativePath) {
17927
- if (!relativePath || relativePath.includes("\0") || path50.isAbsolute(relativePath)) return false;
17928
- const normalized = path50.normalize(relativePath);
17929
- return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path50.sep}`);
18324
+ if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
18325
+ const normalized = path51.normalize(relativePath);
18326
+ return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
17930
18327
  }
17931
18328
  async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17932
18329
  if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
17933
18330
  const root = canonicalPath(resourceRoot);
17934
- const target = path50.resolve(root, relativePath);
17935
- const relative = path50.relative(root, target);
17936
- if (relative === ".." || relative.startsWith(`..${path50.sep}`) || path50.isAbsolute(relative)) {
18331
+ const target = path51.resolve(root, relativePath);
18332
+ const relative = path51.relative(root, target);
18333
+ if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
17937
18334
  throw new Error("recovery_path_escape");
17938
18335
  }
17939
18336
  let current = root;
17940
- const parentParts = path50.relative(root, path50.dirname(target)).split(path50.sep).filter(Boolean);
18337
+ const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
17941
18338
  for (const part of parentParts) {
17942
- current = path50.join(current, part);
17943
- if (!existsSync12(current)) break;
18339
+ current = path51.join(current, part);
18340
+ if (!existsSync13(current)) break;
17944
18341
  const info = await lstat6(current);
17945
18342
  if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
17946
18343
  if (!info.isDirectory()) break;
@@ -17948,7 +18345,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
17948
18345
  return target;
17949
18346
  }
17950
18347
  async function fsyncPath(filePath) {
17951
- const handle = await open4(filePath, "r");
18348
+ const handle = await open5(filePath, "r");
17952
18349
  try {
17953
18350
  await handle.sync();
17954
18351
  } finally {
@@ -17990,13 +18387,13 @@ async function captureRecoverySnapshot(filePath, options) {
17990
18387
  return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
17991
18388
  }
17992
18389
  if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
17993
- const content = await readFile8(filePath);
18390
+ const content = await readFile9(filePath);
17994
18391
  const hash = createHash12("sha256").update(content).digest("hex");
17995
18392
  let blob;
17996
18393
  if (options?.blobDir) {
17997
- await mkdir9(options.blobDir, { recursive: true, mode: 448 });
17998
- const blobPath = path50.join(options.blobDir, hash);
17999
- if (!existsSync12(blobPath)) {
18394
+ await mkdir10(options.blobDir, { recursive: true, mode: 448 });
18395
+ const blobPath = path51.join(options.blobDir, hash);
18396
+ if (!existsSync13(blobPath)) {
18000
18397
  await writeFile6(blobPath, content, { mode: 384 });
18001
18398
  await fsyncPath(blobPath);
18002
18399
  }
@@ -18050,7 +18447,7 @@ async function validateRecoverySnapshot(params) {
18050
18447
  if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
18051
18448
  let content;
18052
18449
  try {
18053
- content = await readFile8(path50.join(params.artifactDir, record.blob));
18450
+ content = await readFile9(path51.join(params.artifactDir, record.blob));
18054
18451
  } catch {
18055
18452
  throw new Error(params.corruptReason);
18056
18453
  }
@@ -18078,16 +18475,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
18078
18475
  ]);
18079
18476
  var STAGING_STALE_MS = 5 * 6e4;
18080
18477
  function checkpointsRoot(stateDir) {
18081
- return path51.join(stateDir, "recovery", "checkpoints");
18478
+ return path52.join(stateDir, "recovery", "checkpoints");
18082
18479
  }
18083
18480
  function checkpointDir(stateDir, checkpointId) {
18084
18481
  if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
18085
18482
  throw new Error("invalid_recovery_checkpoint_id");
18086
18483
  }
18087
- return path51.join(checkpointsRoot(stateDir), checkpointId);
18484
+ return path52.join(checkpointsRoot(stateDir), checkpointId);
18088
18485
  }
18089
18486
  async function fsyncPath2(filePath) {
18090
- const handle = await open5(filePath, "r");
18487
+ const handle = await open6(filePath, "r");
18091
18488
  try {
18092
18489
  await handle.sync();
18093
18490
  } finally {
@@ -18095,13 +18492,13 @@ async function fsyncPath2(filePath) {
18095
18492
  }
18096
18493
  }
18097
18494
  async function atomicWriteJson(filePath, value) {
18098
- await mkdir10(path51.dirname(filePath), { recursive: true, mode: 448 });
18495
+ await mkdir11(path52.dirname(filePath), { recursive: true, mode: 448 });
18099
18496
  const temporary = `${filePath}.tmp-${randomUUID4()}`;
18100
18497
  await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
18101
18498
  `, { mode: 384 });
18102
18499
  await fsyncPath2(temporary);
18103
- await rename2(temporary, filePath);
18104
- await fsyncPath2(path51.dirname(filePath));
18500
+ await rename3(temporary, filePath);
18501
+ await fsyncPath2(path52.dirname(filePath));
18105
18502
  }
18106
18503
  async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18107
18504
  const value = {
@@ -18111,13 +18508,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
18111
18508
  manifestHash,
18112
18509
  ...detail ? { detail } : {}
18113
18510
  };
18114
- await atomicWriteJson(path51.join(artifactDir, "state.json"), value);
18511
+ await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
18115
18512
  }
18116
18513
  async function directorySize(root) {
18117
- if (!existsSync13(root)) return 0;
18514
+ if (!existsSync14(root)) return 0;
18118
18515
  let total = 0;
18119
18516
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18120
- const entryPath = path51.join(root, entry.name);
18517
+ const entryPath = path52.join(root, entry.name);
18121
18518
  if (entry.isDirectory()) total += await directorySize(entryPath);
18122
18519
  else total += (await lstat7(entryPath)).size;
18123
18520
  }
@@ -18162,9 +18559,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18162
18559
  let rawManifest;
18163
18560
  let state;
18164
18561
  try {
18165
- rawManifest = JSON.parse(await readFile9(path51.join(artifactDir, "manifest.json"), "utf8"));
18562
+ rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
18166
18563
  state = JSON.parse(
18167
- await readFile9(path51.join(artifactDir, "state.json"), "utf8")
18564
+ await readFile10(path52.join(artifactDir, "state.json"), "utf8")
18168
18565
  );
18169
18566
  } catch {
18170
18567
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
@@ -18180,10 +18577,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18180
18577
  }
18181
18578
  const entryPaths = /* @__PURE__ */ new Set();
18182
18579
  for (const entry of manifest.entries) {
18183
- if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path51.normalize(entry.path))) {
18580
+ 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))) {
18184
18581
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18185
18582
  }
18186
- entryPaths.add(path51.normalize(entry.path));
18583
+ entryPaths.add(path52.normalize(entry.path));
18187
18584
  for (const [side, snapshot] of [
18188
18585
  ["before", entry.before],
18189
18586
  ["after", entry.after]
@@ -18197,9 +18594,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18197
18594
  });
18198
18595
  }
18199
18596
  }
18200
- const receiptPath = path51.join(artifactDir, "receipt.json");
18597
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18201
18598
  let receipt;
18202
- if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync13(receiptPath)) {
18599
+ if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
18203
18600
  receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18204
18601
  }
18205
18602
  return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
@@ -18207,7 +18604,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
18207
18604
  async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
18208
18605
  let rawReceipt;
18209
18606
  try {
18210
- rawReceipt = JSON.parse(await readFile9(path51.join(artifactDir, "receipt.json"), "utf8"));
18607
+ rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
18211
18608
  } catch {
18212
18609
  throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
18213
18610
  }
@@ -18230,8 +18627,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
18230
18627
  return receipt;
18231
18628
  }
18232
18629
  async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18233
- const receiptPath = path51.join(artifactDir, "receipt.json");
18234
- if (existsSync13(receiptPath)) {
18630
+ const receiptPath = path52.join(artifactDir, "receipt.json");
18631
+ if (existsSync14(receiptPath)) {
18235
18632
  return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
18236
18633
  }
18237
18634
  const receipt = {
@@ -18248,14 +18645,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
18248
18645
  }
18249
18646
  async function checkpointIds(stateDir) {
18250
18647
  const root = checkpointsRoot(stateDir);
18251
- if (!existsSync13(root)) return [];
18648
+ if (!existsSync14(root)) return [];
18252
18649
  return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
18253
18650
  }
18254
18651
  async function artifactRepoRoot(stateDir, checkpointId) {
18255
18652
  const artifactDir = checkpointDir(stateDir, checkpointId);
18256
18653
  try {
18257
18654
  const manifest = JSON.parse(
18258
- await readFile9(path51.join(artifactDir, "manifest.json"), "utf8")
18655
+ await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
18259
18656
  );
18260
18657
  if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
18261
18658
  return canonicalPath(manifest.repoRoot);
@@ -18263,7 +18660,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
18263
18660
  } catch {
18264
18661
  }
18265
18662
  try {
18266
- const owner = JSON.parse(await readFile9(path51.join(artifactDir, "owner.json"), "utf8"));
18663
+ const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
18267
18664
  return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
18268
18665
  } catch {
18269
18666
  return null;
@@ -18279,14 +18676,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
18279
18676
  }
18280
18677
  async function cleanupOrphanedStaging(stateDir) {
18281
18678
  const root = checkpointsRoot(stateDir);
18282
- if (!existsSync13(root)) return;
18679
+ if (!existsSync14(root)) return;
18283
18680
  const now = Date.now();
18284
18681
  for (const entry of await readdir2(root, { withFileTypes: true })) {
18285
18682
  if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
18286
- const stagingPath = path51.join(root, entry.name);
18683
+ const stagingPath = path52.join(root, entry.name);
18287
18684
  let stale = false;
18288
18685
  try {
18289
- const owner = JSON.parse(await readFile9(path51.join(stagingPath, "owner.json"), "utf8"));
18686
+ const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
18290
18687
  const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
18291
18688
  const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
18292
18689
  let alive = false;
@@ -18326,9 +18723,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
18326
18723
 
18327
18724
  // src/core/recovery/reconcile.ts
18328
18725
  init_fingerprint2();
18329
- import { existsSync as existsSync14 } from "node:fs";
18330
- import { readFile as readFile10 } from "node:fs/promises";
18331
- import path52 from "node:path";
18726
+ import { existsSync as existsSync15 } from "node:fs";
18727
+ import { readFile as readFile11 } from "node:fs/promises";
18728
+ import path53 from "node:path";
18332
18729
  async function matchRecoverySide(resourceRoot, entries, side) {
18333
18730
  for (const entry of entries) {
18334
18731
  const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
@@ -18342,9 +18739,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18342
18739
  loaded = await readRecoveryArtifact(stateDir, checkpointId);
18343
18740
  } catch {
18344
18741
  const artifactDir = checkpointDir(stateDir, checkpointId);
18345
- if (existsSync14(artifactDir)) {
18346
- const manifestPath = path52.join(artifactDir, "manifest.json");
18347
- const hash = existsSync14(manifestPath) ? hashValue(await readFile10(manifestPath, "utf8")) : "unavailable";
18742
+ if (existsSync15(artifactDir)) {
18743
+ const manifestPath = path53.join(artifactDir, "manifest.json");
18744
+ const hash = existsSync15(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
18348
18745
  await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
18349
18746
  }
18350
18747
  return "corrupt";
@@ -18379,8 +18776,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
18379
18776
 
18380
18777
  // src/core/recovery/resource-identity.ts
18381
18778
  init_fingerprint2();
18382
- import { lstat as lstat8, readFile as readFile11, realpath as realpath3 } from "node:fs/promises";
18383
- import path53 from "node:path";
18779
+ import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
18780
+ import path54 from "node:path";
18384
18781
  async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18385
18782
  const resolvedRoot = await realpath3(resourceRoot);
18386
18783
  if (resourceKind === "directory") {
@@ -18388,13 +18785,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
18388
18785
  if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
18389
18786
  return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
18390
18787
  }
18391
- const dotGit = path53.join(resolvedRoot, ".git");
18788
+ const dotGit = path54.join(resolvedRoot, ".git");
18392
18789
  const gitInfo = await lstat8(dotGit);
18393
18790
  let gitMetadataPath = dotGit;
18394
18791
  if (gitInfo.isFile()) {
18395
- const marker = (await readFile11(dotGit, "utf8")).trim();
18792
+ const marker = (await readFile12(dotGit, "utf8")).trim();
18396
18793
  if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
18397
- gitMetadataPath = path53.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18794
+ gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
18398
18795
  } else if (!gitInfo.isDirectory()) {
18399
18796
  throw new Error("recovery_repo_identity_unavailable");
18400
18797
  }
@@ -18459,7 +18856,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
18459
18856
  async function prepareRecoveryCheckpoint(params) {
18460
18857
  const backend = params.backend ?? "git_worktree";
18461
18858
  const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
18462
- await mkdir11(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18859
+ await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
18463
18860
  await cleanupOrphanedStaging(params.stateDir);
18464
18861
  await garbageCollect(params.stateDir, params.config, params.repoRoot);
18465
18862
  const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
@@ -18467,16 +18864,16 @@ async function prepareRecoveryCheckpoint(params) {
18467
18864
  throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18468
18865
  }
18469
18866
  const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
18470
- const temporary = path54.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18867
+ const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
18471
18868
  const finalDir = checkpointDir(params.stateDir, checkpointId);
18472
- await mkdir11(temporary, { recursive: true, mode: 448 });
18473
- await atomicWriteJson(path54.join(temporary, "owner.json"), {
18869
+ await mkdir12(temporary, { recursive: true, mode: 448 });
18870
+ await atomicWriteJson(path55.join(temporary, "owner.json"), {
18474
18871
  version: 1,
18475
18872
  pid: process.pid,
18476
18873
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18477
18874
  repoRoot: canonicalPath(params.repoRoot)
18478
18875
  });
18479
- await mkdir11(path54.join(temporary, "blobs"), { recursive: true, mode: 448 });
18876
+ await mkdir12(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
18480
18877
  try {
18481
18878
  const entries = [];
18482
18879
  const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
@@ -18485,8 +18882,8 @@ async function prepareRecoveryCheckpoint(params) {
18485
18882
  )) {
18486
18883
  const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
18487
18884
  if (protectedRoots.some((root) => {
18488
- const relative = path54.relative(root, target);
18489
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path54.sep}`) && !path54.isAbsolute(relative);
18885
+ const relative = path55.relative(root, target);
18886
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
18490
18887
  })) {
18491
18888
  throw new Error("recovery_protected_path");
18492
18889
  }
@@ -18498,7 +18895,7 @@ async function prepareRecoveryCheckpoint(params) {
18498
18895
  entries.push({
18499
18896
  path: change.relativePath,
18500
18897
  before: await captureRecoverySnapshot(baseline, {
18501
- blobDir: path54.join(temporary, "blobs")
18898
+ blobDir: path55.join(temporary, "blobs")
18502
18899
  }),
18503
18900
  after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
18504
18901
  });
@@ -18535,12 +18932,12 @@ async function prepareRecoveryCheckpoint(params) {
18535
18932
  entries
18536
18933
  };
18537
18934
  const manifestHash = hashValue(canonicalStringify(manifest));
18538
- await atomicWriteJson(path54.join(temporary, "manifest.json"), manifest);
18935
+ await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
18539
18936
  await writeRecoveryState(temporary, "prepared", manifestHash);
18540
18937
  await fsyncPath2(temporary);
18541
18938
  const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
18542
18939
  if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
18543
- await rename3(temporary, finalDir);
18940
+ await rename4(temporary, finalDir);
18544
18941
  await fsyncPath2(checkpointsRoot(params.stateDir));
18545
18942
  return {
18546
18943
  checkpointId,
@@ -18578,7 +18975,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18578
18975
  } catch {
18579
18976
  try {
18580
18977
  const raw = JSON.parse(
18581
- await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18978
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18582
18979
  );
18583
18980
  rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
18584
18981
  } catch {
@@ -18612,7 +19009,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18612
19009
  } catch {
18613
19010
  try {
18614
19011
  const manifest = JSON.parse(
18615
- await readFile12(path54.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
19012
+ await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
18616
19013
  );
18617
19014
  if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
18618
19015
  if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
@@ -18637,12 +19034,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
18637
19034
  async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18638
19035
  const root = checkpointsRoot(stateDir);
18639
19036
  if (!repoRoot) return directorySize(root);
18640
- if (!existsSync15(root)) return 0;
19037
+ if (!existsSync16(root)) return 0;
18641
19038
  const expected = canonicalPath(repoRoot);
18642
19039
  let total = 0;
18643
19040
  for (const entry of await readdir3(root, { withFileTypes: true })) {
18644
19041
  if (!entry.isDirectory()) continue;
18645
- const entryPath = path54.join(root, entry.name);
19042
+ const entryPath = path55.join(root, entry.name);
18646
19043
  if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
18647
19044
  if (await artifactRepoRoot(stateDir, entry.name) === expected) {
18648
19045
  total += await directorySize(entryPath);
@@ -18651,7 +19048,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
18651
19048
  }
18652
19049
  if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
18653
19050
  try {
18654
- const owner = JSON.parse(await readFile12(path54.join(entryPath, "owner.json"), "utf8"));
19051
+ const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
18655
19052
  if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
18656
19053
  total += await directorySize(entryPath);
18657
19054
  }
@@ -18693,16 +19090,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
18693
19090
  init_scrub();
18694
19091
 
18695
19092
  // src/core/transactional/file-checkpoint-backend.ts
18696
- import { cp, lstat as lstat11, mkdir as mkdir13, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
19093
+ import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
18697
19094
  import os5 from "node:os";
18698
- import path58 from "node:path";
19095
+ import path59 from "node:path";
18699
19096
 
18700
19097
  // src/core/transactional/file-checkpoint-git.ts
18701
19098
  init_path_utils();
18702
19099
  import { spawn as spawn8 } from "node:child_process";
18703
19100
  import { createHash as createHash13 } from "node:crypto";
18704
- import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
18705
- import path55 from "node:path";
19101
+ import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
19102
+ import path56 from "node:path";
18706
19103
  var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
18707
19104
  var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
18708
19105
  var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
@@ -18736,7 +19133,7 @@ function rethrowStableFileCheckpointError(error) {
18736
19133
  }
18737
19134
  async function rootGitMetadataPresent(repoRoot) {
18738
19135
  try {
18739
- await lstat9(path55.join(repoRoot, ".git"));
19136
+ await lstat9(path56.join(repoRoot, ".git"));
18740
19137
  return true;
18741
19138
  } catch {
18742
19139
  return false;
@@ -18785,10 +19182,10 @@ function execGit2(repoRoot, args) {
18785
19182
  }
18786
19183
  async function resolveGitPath(repoRoot, gitPath) {
18787
19184
  const trimmed = gitPath.trim();
18788
- if (path55.isAbsolute(trimmed)) {
19185
+ if (path56.isAbsolute(trimmed)) {
18789
19186
  return trimmed;
18790
19187
  }
18791
- return path55.join(repoRoot, trimmed);
19188
+ return path56.join(repoRoot, trimmed);
18792
19189
  }
18793
19190
  async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
18794
19191
  await execGit2(sourceRoot, [
@@ -18830,7 +19227,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18830
19227
  destinationRoot,
18831
19228
  await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
18832
19229
  );
18833
- const destinationShared = path55.join(destinationGitDir, path55.basename(sourceShared));
19230
+ const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
18834
19231
  try {
18835
19232
  await copyFile3(sourceShared, destinationShared);
18836
19233
  } catch (error) {
@@ -18839,14 +19236,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
18839
19236
  }
18840
19237
  async function readGitFile(gitDir, relativePath) {
18841
19238
  try {
18842
- return await readFile13(path55.join(gitDir, relativePath));
19239
+ return await readFile14(path56.join(gitDir, relativePath));
18843
19240
  } catch {
18844
19241
  return null;
18845
19242
  }
18846
19243
  }
18847
19244
  async function readAbsoluteGitFile(absolutePath) {
18848
19245
  try {
18849
- return await readFile13(absolutePath);
19246
+ return await readFile14(absolutePath);
18850
19247
  } catch {
18851
19248
  return null;
18852
19249
  }
@@ -18863,8 +19260,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18863
19260
  repoRoot,
18864
19261
  await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
18865
19262
  );
18866
- const relative = path55.resolve(resolved).startsWith(path55.resolve(gitDir)) ? path55.relative(gitDir, resolved) : resolved;
18867
- const content = typeof relative === "string" && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19263
+ const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
19264
+ const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18868
19265
  if (content !== null) {
18869
19266
  hashGitFileContent(hash, gitPath, content);
18870
19267
  }
@@ -18874,13 +19271,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
18874
19271
  async function hashGitTree(gitDir, relativeDir, hash) {
18875
19272
  let names;
18876
19273
  try {
18877
- names = await readdir4(path55.join(gitDir, relativeDir));
19274
+ names = await readdir4(path56.join(gitDir, relativeDir));
18878
19275
  } catch {
18879
19276
  return;
18880
19277
  }
18881
19278
  for (const name of names.sort()) {
18882
- const relativePath = relativeDir ? path55.join(relativeDir, name) : name;
18883
- const absolutePath = path55.join(gitDir, relativePath);
19279
+ const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
19280
+ const absolutePath = path56.join(gitDir, relativePath);
18884
19281
  let childNames = null;
18885
19282
  try {
18886
19283
  childNames = await readdir4(absolutePath);
@@ -18902,7 +19299,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
18902
19299
  }
18903
19300
  async function computeGitMetadataFingerprint(repoRoot) {
18904
19301
  const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
18905
- const gitDir = path55.isAbsolute(gitDirRel) ? gitDirRel : path55.join(repoRoot, gitDirRel);
19302
+ const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
18906
19303
  const hash = createHash13("sha256");
18907
19304
  for (const file of [
18908
19305
  "HEAD",
@@ -18925,8 +19322,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
18925
19322
  const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
18926
19323
  if (sharedIndex) {
18927
19324
  const resolved = await resolveGitPath(repoRoot, sharedIndex);
18928
- const relative = path55.relative(gitDir, resolved);
18929
- const content = relative && !relative.startsWith("..") && !path55.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
19325
+ const relative = path56.relative(gitDir, resolved);
19326
+ const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
18930
19327
  if (content !== null) {
18931
19328
  hashGitFileContent(hash, "shared-index", content);
18932
19329
  }
@@ -18937,7 +19334,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
18937
19334
  await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
18938
19335
  }
18939
19336
  try {
18940
- const rootGitPath = path55.join(repoRoot, ".git");
19337
+ const rootGitPath = path56.join(repoRoot, ".git");
18941
19338
  const rootGitInfo = await lstat9(rootGitPath);
18942
19339
  if (rootGitInfo.isFile()) {
18943
19340
  const content = await readAbsoluteGitFile(rootGitPath);
@@ -18953,14 +19350,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
18953
19350
  function resolveExecutionCwdRelative(resourceRoot, cwd) {
18954
19351
  const resolvedCwd = canonicalPath(cwd);
18955
19352
  const resourceCanonical = canonicalPath(resourceRoot);
18956
- const relative = path55.relative(resourceCanonical, resolvedCwd);
19353
+ const relative = path56.relative(resourceCanonical, resolvedCwd);
18957
19354
  if (relative === "" || relative === ".") {
18958
19355
  return "";
18959
19356
  }
18960
- if (relative.startsWith("..") || path55.isAbsolute(relative)) {
19357
+ if (relative.startsWith("..") || path56.isAbsolute(relative)) {
18961
19358
  throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
18962
19359
  }
18963
- return relative.split(path55.sep).join("/");
19360
+ return relative.split(path56.sep).join("/");
18964
19361
  }
18965
19362
 
18966
19363
  // src/core/transactional/file-checkpoint-isolation.ts
@@ -18981,8 +19378,8 @@ function fileCheckpointIsolationReason(context) {
18981
19378
  }
18982
19379
 
18983
19380
  // src/core/transactional/file-checkpoint-staging.ts
18984
- import { readdir as readdir5, readFile as readFile14, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
18985
- import path56 from "node:path";
19381
+ import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
19382
+ import path57 from "node:path";
18986
19383
  function isOwnerProcessAlive(pid) {
18987
19384
  try {
18988
19385
  process.kill(pid, 0);
@@ -18992,12 +19389,12 @@ function isOwnerProcessAlive(pid) {
18992
19389
  }
18993
19390
  }
18994
19391
  async function writeOwnerMarker(stagingRoot, marker) {
18995
- await writeFile8(path56.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
19392
+ await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
18996
19393
  `, "utf8");
18997
19394
  }
18998
19395
  async function readOwnerMarker(stagingRoot) {
18999
19396
  try {
19000
- const raw = await readFile14(path56.join(stagingRoot, "owner.json"), "utf8");
19397
+ const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
19001
19398
  return JSON.parse(raw.trim());
19002
19399
  } catch {
19003
19400
  return null;
@@ -19015,7 +19412,7 @@ async function collectDeadOwnerStaging(parentDir) {
19015
19412
  if (!name.startsWith("belay-file-checkpoint-")) {
19016
19413
  continue;
19017
19414
  }
19018
- const stagingRoot = path56.join(parentDir, name);
19415
+ const stagingRoot = path57.join(parentDir, name);
19019
19416
  const marker = await readOwnerMarker(stagingRoot);
19020
19417
  if (!marker) {
19021
19418
  dead.push(stagingRoot);
@@ -19038,7 +19435,7 @@ import { constants as fsConstants2 } from "node:fs";
19038
19435
  import {
19039
19436
  copyFile as copyFile4,
19040
19437
  lstat as lstat10,
19041
- mkdir as mkdir12,
19438
+ mkdir as mkdir13,
19042
19439
  mkdtemp as mkdtemp4,
19043
19440
  readlink as readlink5,
19044
19441
  rm as rm8,
@@ -19047,17 +19444,17 @@ import {
19047
19444
  writeFile as writeFile9
19048
19445
  } from "node:fs/promises";
19049
19446
  import os4 from "node:os";
19050
- import path57 from "node:path";
19447
+ import path58 from "node:path";
19051
19448
  var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
19052
19449
  async function chmodSafe2(target, mode) {
19053
19450
  try {
19054
- const { chmod: chmod4 } = await import("node:fs/promises");
19055
- await chmod4(target, mode & 511);
19451
+ const { chmod: chmod5 } = await import("node:fs/promises");
19452
+ await chmod5(target, mode & 511);
19056
19453
  } catch {
19057
19454
  }
19058
19455
  }
19059
19456
  async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
19060
- await mkdir12(path57.dirname(destinationPath), { recursive: true });
19457
+ await mkdir13(path58.dirname(destinationPath), { recursive: true });
19061
19458
  if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
19062
19459
  try {
19063
19460
  await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
@@ -19079,11 +19476,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
19079
19476
  const info = await lstat10(sourcePath);
19080
19477
  await rm8(destinationPath, { force: true, recursive: false });
19081
19478
  if (info.isDirectory() && !info.isSymbolicLink()) {
19082
- await mkdir12(destinationPath, { recursive: true, mode: info.mode & 511 });
19479
+ await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
19083
19480
  return strategy;
19084
19481
  }
19085
19482
  if (info.isSymbolicLink()) {
19086
- await mkdir12(path57.dirname(destinationPath), { recursive: true });
19483
+ await mkdir13(path58.dirname(destinationPath), { recursive: true });
19087
19484
  await symlink4(await readlink5(sourcePath), destinationPath);
19088
19485
  return strategy;
19089
19486
  }
@@ -19139,9 +19536,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
19139
19536
  async function probeFileCloneStrategy() {
19140
19537
  let tempDir = null;
19141
19538
  try {
19142
- tempDir = await mkdtemp4(path57.join(os4.tmpdir(), "belay-clone-probe-"));
19143
- const source = path57.join(tempDir, "source.txt");
19144
- const destination = path57.join(tempDir, "dest.txt");
19539
+ tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
19540
+ const source = path58.join(tempDir, "source.txt");
19541
+ const destination = path58.join(tempDir, "dest.txt");
19145
19542
  await writeFile9(source, "probe\n");
19146
19543
  if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
19147
19544
  try {
@@ -19294,11 +19691,11 @@ async function protectedRootState(root) {
19294
19691
  return `directory:${node.hash}:${index.treeHash}`;
19295
19692
  }
19296
19693
  function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
19297
- const relative = path58.relative(path58.resolve(resourceRoot), path58.resolve(protectedRoot));
19298
- if (relative === "" || relative.startsWith("..") || path58.isAbsolute(relative)) {
19694
+ const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
19695
+ if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
19299
19696
  return null;
19300
19697
  }
19301
- return path58.join(executionRoot, relative);
19698
+ return path59.join(executionRoot, relative);
19302
19699
  }
19303
19700
  async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
19304
19701
  const states = /* @__PURE__ */ new Map();
@@ -19323,15 +19720,15 @@ async function directoryByteSize(root, deadlineMs) {
19323
19720
  }
19324
19721
  let total = 0;
19325
19722
  for (const name of await readdir6(root)) {
19326
- total += await directoryByteSize(path58.join(root, name), deadlineMs);
19723
+ total += await directoryByteSize(path59.join(root, name), deadlineMs);
19327
19724
  }
19328
19725
  return total;
19329
19726
  }
19330
19727
  async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
19331
19728
  const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
19332
- const sourceGitDir = path58.isAbsolute(gitDirRel) ? gitDirRel : path58.join(sourceRoot, gitDirRel);
19333
- const relativeGitDir = path58.relative(path58.resolve(sourceRoot), path58.resolve(sourceGitDir));
19334
- const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path58.join(destinationRoot, relativeGitDir) : path58.join(destinationRoot, ".git");
19729
+ const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
19730
+ const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
19731
+ const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
19335
19732
  await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
19336
19733
  }
19337
19734
  async function prepareDirtyGitSnapshot(context) {
@@ -19340,7 +19737,7 @@ async function prepareDirtyGitSnapshot(context) {
19340
19737
  const quotas = context.fileCheckpoint;
19341
19738
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19342
19739
  await removeDeadOwnerStaging(os5.tmpdir());
19343
- const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19740
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19344
19741
  await writeOwnerMarker(stagingRoot, {
19345
19742
  version: 1,
19346
19743
  pid: process.pid,
@@ -19348,8 +19745,8 @@ async function prepareDirtyGitSnapshot(context) {
19348
19745
  resourceRoot: context.repoRoot,
19349
19746
  backend: "file_checkpoint"
19350
19747
  });
19351
- const baselineRoot = path58.join(stagingRoot, "baseline");
19352
- const executionRoot = path58.join(stagingRoot, "execution");
19748
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19749
+ const executionRoot = path59.join(stagingRoot, "execution");
19353
19750
  try {
19354
19751
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19355
19752
  const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
@@ -19383,7 +19780,7 @@ async function prepareDirtyGitSnapshot(context) {
19383
19780
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19384
19781
  }
19385
19782
  await writeFile10(
19386
- path58.join(stagingRoot, "baseline-index.json"),
19783
+ path59.join(stagingRoot, "baseline-index.json"),
19387
19784
  `${JSON.stringify(baselineIndex)}
19388
19785
  `,
19389
19786
  "utf8"
@@ -19433,7 +19830,7 @@ async function prepareNonGitSnapshot(context) {
19433
19830
  const quotas = context.fileCheckpoint;
19434
19831
  const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
19435
19832
  await removeDeadOwnerStaging(os5.tmpdir());
19436
- const stagingRoot = await mkdtemp5(path58.join(os5.tmpdir(), "belay-file-checkpoint-"));
19833
+ const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
19437
19834
  await writeOwnerMarker(stagingRoot, {
19438
19835
  version: 1,
19439
19836
  pid: process.pid,
@@ -19441,8 +19838,8 @@ async function prepareNonGitSnapshot(context) {
19441
19838
  resourceRoot: context.repoRoot,
19442
19839
  backend: "file_checkpoint"
19443
19840
  });
19444
- const baselineRoot = path58.join(stagingRoot, "baseline");
19445
- const executionRoot = path58.join(stagingRoot, "execution");
19841
+ const baselineRoot = path59.join(stagingRoot, "baseline");
19842
+ const executionRoot = path59.join(stagingRoot, "execution");
19446
19843
  try {
19447
19844
  resolveExecutionCwdRelative(context.repoRoot, context.cwd);
19448
19845
  const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
@@ -19451,7 +19848,7 @@ async function prepareNonGitSnapshot(context) {
19451
19848
  quotas,
19452
19849
  deadlineMs
19453
19850
  });
19454
- await mkdir13(baselineRoot, { recursive: true });
19851
+ await mkdir14(baselineRoot, { recursive: true });
19455
19852
  const baselineIndex = await buildFileTreeIndex({
19456
19853
  resourceRoot: baselineRoot,
19457
19854
  excludedRoots,
@@ -19471,7 +19868,7 @@ async function prepareNonGitSnapshot(context) {
19471
19868
  throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
19472
19869
  }
19473
19870
  await writeFile10(
19474
- path58.join(stagingRoot, "baseline-index.json"),
19871
+ path59.join(stagingRoot, "baseline-index.json"),
19475
19872
  `${JSON.stringify(baselineIndex)}
19476
19873
  `,
19477
19874
  "utf8"
@@ -19481,7 +19878,7 @@ async function prepareNonGitSnapshot(context) {
19481
19878
  quotas,
19482
19879
  deadlineMs
19483
19880
  });
19484
- await mkdir13(executionRoot, { recursive: true });
19881
+ await mkdir14(executionRoot, { recursive: true });
19485
19882
  const finalSourceIndex = await buildFileTreeIndex({
19486
19883
  resourceRoot: context.repoRoot,
19487
19884
  excludedRoots,
@@ -19824,10 +20221,10 @@ async function selectTransactionalBackend(context) {
19824
20221
  }
19825
20222
 
19826
20223
  // src/core/transactional/diff-evaluator.ts
19827
- import path59 from "node:path";
20224
+ import path60 from "node:path";
19828
20225
  init_path_utils();
19829
20226
  function categorizeChange(change, ctx) {
19830
- const absolutePath = canonicalPath(path59.join(ctx.repoRoot, change.relativePath));
20227
+ const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
19831
20228
  if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
19832
20229
  return "repo_outside";
19833
20230
  }
@@ -20377,17 +20774,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
20377
20774
  }
20378
20775
 
20379
20776
  // src/core/notify.ts
20777
+ init_path_utils();
20380
20778
  import { execFile } from "node:child_process";
20779
+ import path61 from "node:path";
20381
20780
  import { promisify } from "node:util";
20382
20781
  var execFileAsync = promisify(execFile);
20383
- async function notifyDeny(config, event) {
20384
- const payload = JSON.stringify(event);
20385
- if (config.webhookUrl) {
20782
+ var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
20783
+ function webhookConfigIssue(url) {
20784
+ let parsed;
20785
+ try {
20786
+ parsed = new URL(url);
20787
+ } catch {
20788
+ return `notifications.webhookUrl is invalid: ${url}`;
20789
+ }
20790
+ if (parsed.protocol === "https:") {
20791
+ return null;
20792
+ }
20793
+ const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
20794
+ if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
20795
+ return null;
20796
+ }
20797
+ return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
20798
+ }
20799
+ function commandHookConfigIssue(commandHook, repoRoot) {
20800
+ if (!path61.isAbsolute(commandHook)) {
20801
+ return `notifications.commandHook must be an absolute path: ${commandHook}`;
20802
+ }
20803
+ if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
20804
+ return `notifications.commandHook must not be inside the repository: ${commandHook}`;
20805
+ }
20806
+ return null;
20807
+ }
20808
+ async function notifyDeny(config, event, deps = {
20809
+ fetch: globalThis.fetch.bind(globalThis),
20810
+ execFile: (file, args, options) => execFileAsync(file, [...args], options)
20811
+ }) {
20812
+ const payload = JSON.stringify({
20813
+ approvalId: event.approvalId,
20814
+ reason: event.reason,
20815
+ summary: event.summary,
20816
+ repoRoot: event.repoRoot,
20817
+ fingerprint: event.fingerprint
20818
+ });
20819
+ const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
20820
+ if (config.webhookUrl && !webhookIssue) {
20386
20821
  try {
20387
20822
  const controller = new AbortController();
20388
20823
  const timeout = setTimeout(() => controller.abort(), 5e3);
20389
20824
  try {
20390
- await fetch(config.webhookUrl, {
20825
+ await deps.fetch(config.webhookUrl, {
20391
20826
  method: "POST",
20392
20827
  headers: { "content-type": "application/json" },
20393
20828
  body: payload,
@@ -20399,17 +20834,16 @@ async function notifyDeny(config, event) {
20399
20834
  } catch {
20400
20835
  }
20401
20836
  }
20402
- if (config.commandHook) {
20837
+ const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
20838
+ if (config.commandHook && !commandHookIssue) {
20403
20839
  try {
20404
- await execFileAsync(config.commandHook, [], {
20840
+ await deps.execFile(config.commandHook, [], {
20405
20841
  env: {
20406
- ...process.env,
20407
20842
  BELAY_APPROVAL_ID: event.approvalId,
20408
20843
  BELAY_REASON: event.reason,
20409
20844
  BELAY_SUMMARY: event.summary,
20410
20845
  BELAY_REPO_ROOT: event.repoRoot,
20411
- BELAY_FINGERPRINT: event.fingerprint,
20412
- BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
20846
+ BELAY_FINGERPRINT: event.fingerprint
20413
20847
  }
20414
20848
  });
20415
20849
  } catch {
@@ -20419,9 +20853,10 @@ async function notifyDeny(config, event) {
20419
20853
 
20420
20854
  // src/adapters/shared/gate-runtime.ts
20421
20855
  init_path_utils();
20856
+ init_repo_config_trust();
20422
20857
 
20423
20858
  // src/adapters/layouts/protected-paths.ts
20424
- import path60 from "node:path";
20859
+ import path62 from "node:path";
20425
20860
  function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20426
20861
  const roots = [
20427
20862
  layout.configPath(repoRoot),
@@ -20433,7 +20868,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
20433
20868
  if (controlPlaneDir) {
20434
20869
  roots.push(controlPlaneDir);
20435
20870
  }
20436
- return roots.map((entry) => path60.resolve(entry));
20871
+ return roots.map((entry) => path62.resolve(entry));
20437
20872
  }
20438
20873
 
20439
20874
  // src/adapters/shared/gate-runtime.ts
@@ -20474,7 +20909,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
20474
20909
  }
20475
20910
  async function loadJsonFile(filePath, fallback) {
20476
20911
  try {
20477
- const raw = await readFile15(filePath, "utf8");
20912
+ const raw = await readFile16(filePath, "utf8");
20478
20913
  return JSON.parse(raw);
20479
20914
  } catch {
20480
20915
  return fallback;
@@ -20483,11 +20918,18 @@ async function loadJsonFile(filePath, fallback) {
20483
20918
  function createDefaultGateRuntimeDeps() {
20484
20919
  return {
20485
20920
  async readConfig(configPath) {
20486
- return loadJsonFile(configPath, {});
20921
+ try {
20922
+ return JSON.parse(await readFile16(configPath, "utf8"));
20923
+ } catch (error) {
20924
+ if (error.code === "ENOENT") {
20925
+ return {};
20926
+ }
20927
+ throw error;
20928
+ }
20487
20929
  },
20488
20930
  async appendAudit(ctx, event) {
20489
- const auditPath = path61.join(ctx.repoRoot, ctx.config.audit.logPath);
20490
- await mkdir14(path61.dirname(auditPath), { recursive: true });
20931
+ const auditPath = path63.join(ctx.repoRoot, ctx.config.audit.logPath);
20932
+ await mkdir15(path63.dirname(auditPath), { recursive: true });
20491
20933
  const provenance = auditProvenance(ctx.config);
20492
20934
  const record = {
20493
20935
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20520,7 +20962,7 @@ function createDefaultGateRuntimeDeps() {
20520
20962
  };
20521
20963
  },
20522
20964
  async writeApprovals(filePath, state) {
20523
- await mkdir14(path61.dirname(filePath), { recursive: true });
20965
+ await mkdir15(path63.dirname(filePath), { recursive: true });
20524
20966
  await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
20525
20967
  `, "utf8");
20526
20968
  },
@@ -20541,10 +20983,11 @@ function createDefaultGateRuntimeDeps() {
20541
20983
  }
20542
20984
  async function resolveGateConfig(ctx, deps) {
20543
20985
  const loaded = await deps.readConfig(ctx.configPath);
20986
+ await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
20544
20987
  let teamConfig = null;
20545
20988
  const teamPath = teamConfigPath();
20546
- if (existsSync16(teamPath)) {
20547
- teamConfig = JSON.parse(await readFile15(teamPath, "utf8"));
20989
+ if (existsSync17(teamPath)) {
20990
+ teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
20548
20991
  }
20549
20992
  return resolveLayeredConfig({
20550
20993
  repoConfig: loaded,
@@ -20682,7 +21125,7 @@ function deriveWorkspaceRootScopeHint(params) {
20682
21125
  if (!targetPath) {
20683
21126
  return void 0;
20684
21127
  }
20685
- const candidateRoot = canonicalPath(path61.dirname(targetPath));
21128
+ const candidateRoot = canonicalPath(path63.dirname(targetPath));
20686
21129
  const validation = validateTrustedWorkspaceRootCandidate({
20687
21130
  candidatePath: candidateRoot,
20688
21131
  repoRoot: action.repoRoot,
@@ -21202,40 +21645,6 @@ async function evaluateGatedAction(ctx, deps, params) {
21202
21645
  scopeHintPayload: params.payload
21203
21646
  });
21204
21647
  }
21205
- async function gateUnmappedToolVerdict(ctx, deps, toolName, payload) {
21206
- const scrubOpts = scrubOptionsFromConfig(ctx.config);
21207
- const replayPayload = fingerprintReplayPayload("tool", payload, scrubOpts) ?? {};
21208
- const result = {
21209
- verdict: "deny_pending_approval",
21210
- reason: "unmapped_tool",
21211
- summary: toolName,
21212
- fingerprint: toolFingerprint(toolName, replayPayload, ctx.repoRoot),
21213
- assessment: {
21214
- reversibility: "irreversible",
21215
- external: false,
21216
- blastRadius: "unknown Codex tool action",
21217
- confidence: 0.5,
21218
- signals: ["unmapped_tool"]
21219
- }
21220
- };
21221
- return gateDecisionToVerdict(ctx, deps, "tool", result, {
21222
- approvalInput: {
21223
- input: toolName,
21224
- inputKind: "tool",
21225
- cwd: ctx.repoRoot,
21226
- toolName,
21227
- payload: replayPayload
21228
- },
21229
- replayAction: {
21230
- kind: "tool",
21231
- cwd: ctx.repoRoot,
21232
- toolName,
21233
- payload: replayPayload,
21234
- fingerprint: result.fingerprint,
21235
- repoRoot: ctx.repoRoot
21236
- }
21237
- });
21238
- }
21239
21648
  async function consumeCapabilityGrantIfUsed(ctx, deps, result) {
21240
21649
  if (result.reason !== "capability_grant" && result.authorizationDecision?.matchedRule !== "grant.exact") {
21241
21650
  return true;
@@ -21292,7 +21701,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21292
21701
  event: auditEvent,
21293
21702
  sourceEvent,
21294
21703
  kind,
21295
- repoLabel: path61.basename(ctx.repoRoot),
21704
+ repoLabel: path63.basename(ctx.repoRoot),
21296
21705
  ...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
21297
21706
  fingerprint: result.fingerprint,
21298
21707
  summary: result.normalizedCommand ?? result.summary ?? "",
@@ -21324,21 +21733,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21324
21733
  if (created) {
21325
21734
  await recordGateApprovalAsk(stateDir, result.reason, false);
21326
21735
  }
21327
- let approvalToken;
21328
- try {
21329
- approvalToken = await issueApprovalToken(
21330
- {
21331
- approvalId: approval.approvalId,
21332
- fingerprint: approval.fingerprint,
21333
- repoRoot: approval.repoRoot,
21334
- issuedAt: approval.createdAt,
21335
- expiresAt: approval.expiresAt
21336
- },
21337
- configuredControlPlaneDir(ctx.config)
21338
- );
21339
- } catch {
21340
- approvalToken = void 0;
21341
- }
21342
21736
  const denialReason = failure?.reason ?? result.reason;
21343
21737
  if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
21344
21738
  await notifyDeny(ctx.config.notifications, {
@@ -21346,8 +21740,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
21346
21740
  reason: denialReason,
21347
21741
  summary: result.normalizedCommand ?? result.summary ?? "",
21348
21742
  repoRoot: ctx.repoRoot,
21349
- fingerprint: result.fingerprint,
21350
- approvalToken
21743
+ fingerprint: result.fingerprint
21351
21744
  });
21352
21745
  }
21353
21746
  const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
@@ -21762,38 +22155,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
21762
22155
  }
21763
22156
 
21764
22157
  // src/adapters/shared/repo-root.ts
21765
- import { existsSync as existsSync17 } from "node:fs";
21766
- import path62 from "node:path";
22158
+ import { existsSync as existsSync18 } from "node:fs";
22159
+ import path64 from "node:path";
21767
22160
  function belayConfigPath(current, adapterName) {
21768
22161
  if (adapterName === "cursor") {
21769
- return path62.join(current, ".cursor", "belay.config.json");
22162
+ return path64.join(current, ".cursor", "belay.config.json");
21770
22163
  }
21771
22164
  if (adapterName === "claude") {
21772
- return path62.join(current, ".claude", "belay.config.json");
22165
+ return path64.join(current, ".claude", "belay.config.json");
21773
22166
  }
21774
- return path62.join(current, ".codex", "belay.config.json");
22167
+ return path64.join(current, ".codex", "belay.config.json");
21775
22168
  }
21776
22169
  function markerMatches(current, marker, layout) {
21777
- const markerPath = path62.join(current, marker);
21778
- if (!existsSync17(markerPath)) {
22170
+ const markerPath = path64.join(current, marker);
22171
+ if (!existsSync18(markerPath)) {
21779
22172
  return false;
21780
22173
  }
21781
22174
  if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
21782
- return existsSync17(belayConfigPath(current, layout.name));
22175
+ return existsSync18(belayConfigPath(current, layout.name));
21783
22176
  }
21784
22177
  return true;
21785
22178
  }
21786
22179
  function findRepoRoot(startPath, layout) {
21787
- let current = path62.resolve(startPath);
22180
+ let current = path64.resolve(startPath);
21788
22181
  while (true) {
21789
22182
  for (const marker of layout.repoRootMarkers) {
21790
22183
  if (markerMatches(current, marker, layout)) {
21791
22184
  return current;
21792
22185
  }
21793
22186
  }
21794
- const parent = path62.dirname(current);
22187
+ const parent = path64.dirname(current);
21795
22188
  if (parent === current) {
21796
- return path62.resolve(startPath);
22189
+ return path64.resolve(startPath);
21797
22190
  }
21798
22191
  current = parent;
21799
22192
  }
@@ -21827,7 +22220,7 @@ function resolveCodexActionCwd(payload, fallbackCwd = process2.cwd(), options =
21827
22220
  const nestedCwd = options.includeToolInputCwd ? nonEmptyPathString(toolInput?.working_directory) ?? nonEmptyPathString(toolInput?.cwd) : void 0;
21828
22221
  const payloadCwd = nonEmptyPathString(payload.cwd);
21829
22222
  const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
21830
- return path63.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
22223
+ return path65.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
21831
22224
  }
21832
22225
  async function loadRuntimeContext(cwd) {
21833
22226
  const repoRoot = findRepoRoot(cwd, codexLayout);
@@ -21943,13 +22336,14 @@ async function runToolGateHook(eventName) {
21943
22336
  const ctx = await loadRuntimeContext(cwd);
21944
22337
  const deps = createDefaultGateRuntimeDeps();
21945
22338
  if (!kind) {
21946
- const policy = ctx.config.policy?.codexUnmappedTool ?? "deny";
21947
- if (policy === "allow") {
21948
- await appendObservedAudit(ctx, deps, eventName, payload);
21949
- jsonResponse({});
21950
- return;
21951
- }
21952
- const verdict3 = await gateUnmappedToolVerdict(ctx, deps, toolName, payload);
22339
+ const normalizedPayload2 = normalizeCodexToolPayload("tool", payload);
22340
+ const verdict3 = await evaluateGatedAction(ctx, deps, {
22341
+ kind: "tool",
22342
+ cwd,
22343
+ payload: normalizedPayload2,
22344
+ toolName,
22345
+ sourceEvent: eventName
22346
+ });
21953
22347
  jsonResponse(gateVerdictToCodexPreToolUseResponse(verdict3));
21954
22348
  return;
21955
22349
  }