@devrouter/cli 0.0.68 → 0.0.70

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.
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/core/devpod-exec.ts
26
- var import_node_child_process28 = require("child_process");
26
+ var import_node_child_process29 = require("child_process");
27
27
  var import_node_crypto21 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
@@ -1769,7 +1769,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
1769
1769
  }
1770
1770
 
1771
1771
  // src/core/devsy-exec.ts
1772
- var import_node_child_process27 = require("child_process");
1772
+ var import_node_child_process28 = require("child_process");
1773
1773
 
1774
1774
  // src/core/devsy-exec-proof.ts
1775
1775
  var import_node_fs6 = __toESM(require("fs"));
@@ -1858,7 +1858,7 @@ function revalidateDevsyExecProof(repoPath, proof) {
1858
1858
 
1859
1859
  // src/core/devsy-mutation.ts
1860
1860
  var import_node_child_process15 = require("child_process");
1861
- var import_node_fs19 = __toESM(require("fs"));
1861
+ var import_node_fs20 = __toESM(require("fs"));
1862
1862
  var import_node_path24 = __toESM(require("path"));
1863
1863
 
1864
1864
  // src/core/devsy-agent.ts
@@ -2018,6 +2018,7 @@ function requireReadyDevsyAgent(options = {}) {
2018
2018
  }
2019
2019
 
2020
2020
  // src/core/managed-devsy-stop.ts
2021
+ var import_node_fs16 = __toESM(require("fs"));
2021
2022
  var import_node_os4 = __toESM(require("os"));
2022
2023
  var import_node_path20 = __toESM(require("path"));
2023
2024
  var import_node_util4 = require("util");
@@ -5158,7 +5159,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs14.default.rea
5158
5159
  const config = parseConfig(parsed ?? {}, configPath);
5159
5160
  const requiredVersion = config.devrouter?.version;
5160
5161
  if (requiredVersion && !hasWarnedVersionMismatch) {
5161
- const cliVersion = true ? "0.0.68" : "0.0.0-dev";
5162
+ const cliVersion = true ? "0.0.70" : "0.0.0-dev";
5162
5163
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
5163
5164
  hasWarnedVersionMismatch = true;
5164
5165
  process.stderr.write(
@@ -5682,12 +5683,108 @@ function containerIdentity(container) {
5682
5683
  mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
5683
5684
  });
5684
5685
  }
5686
+ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
5687
+ if (!import_node_fs16.default.existsSync(import_node_path20.default.join(repoPath, ".devrouter.yml"))) return false;
5688
+ const linked = isLinkedWorktree(repoPath);
5689
+ const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
5690
+ const registration2 = () => {
5691
+ resetWorkspaceRuntimeCaches();
5692
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy")
5693
+ throw new Error("Initial managed stop provider selection changed.");
5694
+ const owner2 = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
5695
+ if (owner2.status !== "owned")
5696
+ throw new Error("Initial managed stop requires one exact Devsy registration.");
5697
+ if (linked) {
5698
+ const record5 = workspace ? readWorkspaceOwnership(repoPath, workspace) : void 0;
5699
+ if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace)
5700
+ throw new Error("Initial managed stop workspace ownership changed.");
5701
+ }
5702
+ return owner2.workspace;
5703
+ };
5704
+ const runtime = loadRuntimeConfig(repoPath, workspace ?? "");
5705
+ if (!runtime.config.managedRuntime) return false;
5706
+ const owner = registration2();
5707
+ if (!owner.context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId))
5708
+ throw new Error("Initial managed stop requires exact provider context.");
5709
+ const plan = inspectManagedDevcontainerConfig({
5710
+ repoPath,
5711
+ config: runtime.config,
5712
+ profile: runtime.resolvedProfile,
5713
+ linked
5714
+ });
5715
+ const providerRoot = import_node_path20.default.resolve(process.env.DEVSY_HOME || import_node_path20.default.join(import_node_os4.default.homedir(), ".devsy"));
5716
+ const featureDirectory = import_node_path20.default.join(
5717
+ providerRoot,
5718
+ "contexts",
5719
+ owner.context,
5720
+ "workspaces",
5721
+ devsyId,
5722
+ "agent",
5723
+ ".docker-compose"
5724
+ );
5725
+ const observe = () => {
5726
+ if (!(0, import_node_util4.isDeepStrictEqual)(registration2(), owner) || readManagedRuntimeState(repoPath, workspace))
5727
+ throw new Error("Initial managed stop ownership or runtime state changed.");
5728
+ const matches = inspectWorkspaceContainers().filter(
5729
+ (c) => sameWorkspacePath(
5730
+ c.labels["com.docker.compose.project.working_dir"] ?? "",
5731
+ plan.composeDirectory
5732
+ )
5733
+ );
5734
+ if (!matches.length) return [];
5735
+ const projects = new Set(matches.map((c) => c.labels["com.docker.compose.project"]));
5736
+ if (projects.size !== 1 || !matches[0]?.labels["com.docker.compose.project"])
5737
+ throw new Error("Initial managed stop requires one exact Compose project.");
5738
+ const population = inspectManagedStopContainers(
5739
+ matches[0].labels["com.docker.compose.project"]
5740
+ );
5741
+ if (!sameSet(
5742
+ matches.map((c) => c.id),
5743
+ population.map((c) => c.id)
5744
+ ))
5745
+ throw new Error("Initial managed stop population changed.");
5746
+ proveManagedComposePopulation({
5747
+ plan,
5748
+ repoPath,
5749
+ composeProject: matches[0].labels["com.docker.compose.project"],
5750
+ providerRoot,
5751
+ featureDirectory,
5752
+ containers: population
5753
+ });
5754
+ return population;
5755
+ };
5756
+ const initial = observe();
5757
+ const stable = () => {
5758
+ const current = observe();
5759
+ if (!sameSet(
5760
+ initial.map((c) => c.id),
5761
+ current.map((c) => c.id)
5762
+ ) || current.some((c) => {
5763
+ const prior = initial.find((p) => p.id === c.id);
5764
+ return !prior || containerIdentity(prior) !== containerIdentity(c) || !prior.state.Running && c.state.Running;
5765
+ }))
5766
+ throw new Error("Initial managed stop population or identity changed.");
5767
+ return current;
5768
+ };
5769
+ stable();
5770
+ if (!initial.length) return false;
5771
+ for (const container of initial) {
5772
+ const current = stable().find((c) => c.id === container.id);
5773
+ if (current?.state.Running)
5774
+ stopExactManagedService(current.id, current.labels["com.docker.compose.service"] ?? "", {
5775
+ timeoutMs: 3e4
5776
+ });
5777
+ }
5778
+ if (stable().some((c) => c.state.Running))
5779
+ throw new Error("Initial managed stop left a workload running.");
5780
+ return true;
5781
+ }
5685
5782
  function stopRetainedManagedDevsyWorkspace(options) {
5686
5783
  const { repoPath, devsyId } = options;
5687
5784
  const linked = isLinkedWorktree(repoPath);
5688
5785
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
5689
5786
  const retainedState = readManagedRuntimeState(repoPath, workspace);
5690
- if (!retainedState) return false;
5787
+ if (!retainedState) return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
5691
5788
  const state = retainedState;
5692
5789
  if (state.devpodId !== devsyId || linked && !workspace) {
5693
5790
  throw new Error("Managed stop requires the exact retained workspace identity.");
@@ -5917,12 +6014,12 @@ function networkProviderEnvironment(binding, source) {
5917
6014
 
5918
6015
  // src/core/network-claim-lookup.ts
5919
6016
  var import_node_crypto10 = require("crypto");
5920
- var import_node_fs18 = __toESM(require("fs"));
6017
+ var import_node_fs19 = __toESM(require("fs"));
5921
6018
  var import_node_path23 = __toESM(require("path"));
5922
6019
 
5923
6020
  // src/core/network-claims.ts
5924
6021
  var import_node_crypto9 = require("crypto");
5925
- var import_node_fs17 = __toESM(require("fs"));
6022
+ var import_node_fs18 = __toESM(require("fs"));
5926
6023
  var import_node_path22 = __toESM(require("path"));
5927
6024
 
5928
6025
  // src/core/network-capacity.ts
@@ -6264,7 +6361,7 @@ function overlaps(left, right) {
6264
6361
  }
6265
6362
 
6266
6363
  // src/core/network-policy.ts
6267
- var import_node_fs16 = __toESM(require("fs"));
6364
+ var import_node_fs17 = __toESM(require("fs"));
6268
6365
  var import_node_path21 = __toESM(require("path"));
6269
6366
  var NETWORK_POLICY_VERSION = 1;
6270
6367
  var NETWORK_POLICY_FILE = import_node_path21.default.join(DEVROUTER_HOME, "network-policy.json");
@@ -6460,7 +6557,7 @@ function hasControlCharacter(value) {
6460
6557
  function readPolicyFile(filePath) {
6461
6558
  let initialStats;
6462
6559
  try {
6463
- initialStats = import_node_fs16.default.lstatSync(filePath);
6560
+ initialStats = import_node_fs17.default.lstatSync(filePath);
6464
6561
  } catch (error) {
6465
6562
  if (isErrno(error, "ENOENT")) return { status: "absent" };
6466
6563
  return { status: "invalid", error: "network policy could not be read" };
@@ -6473,16 +6570,16 @@ function readPolicyFile(filePath) {
6473
6570
  }
6474
6571
  let fileDescriptor;
6475
6572
  try {
6476
- fileDescriptor = import_node_fs16.default.openSync(
6573
+ fileDescriptor = import_node_fs17.default.openSync(
6477
6574
  filePath,
6478
- import_node_fs16.default.constants.O_RDONLY | import_node_fs16.default.constants.O_NONBLOCK | (import_node_fs16.default.constants.O_NOFOLLOW ?? 0)
6575
+ import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NONBLOCK | (import_node_fs17.default.constants.O_NOFOLLOW ?? 0)
6479
6576
  );
6480
6577
  } catch (error) {
6481
6578
  if (isErrno(error, "ENOENT")) return { status: "absent" };
6482
6579
  return { status: "invalid", error: "network policy could not be read" };
6483
6580
  }
6484
6581
  try {
6485
- const openedStats = import_node_fs16.default.fstatSync(fileDescriptor);
6582
+ const openedStats = import_node_fs17.default.fstatSync(fileDescriptor);
6486
6583
  if (!openedStats.isFile()) {
6487
6584
  return { status: "invalid", error: "network policy must be a regular file" };
6488
6585
  }
@@ -6495,12 +6592,12 @@ function readPolicyFile(filePath) {
6495
6592
  const buffer = Buffer.alloc(openedStats.size);
6496
6593
  let offset = 0;
6497
6594
  while (offset < buffer.length) {
6498
- const bytesRead = import_node_fs16.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
6595
+ const bytesRead = import_node_fs17.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
6499
6596
  if (bytesRead === 0)
6500
6597
  return { status: "invalid", error: "network policy changed during read" };
6501
6598
  offset += bytesRead;
6502
6599
  }
6503
- const finalStats = import_node_fs16.default.fstatSync(fileDescriptor);
6600
+ const finalStats = import_node_fs17.default.fstatSync(fileDescriptor);
6504
6601
  if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
6505
6602
  return { status: "invalid", error: "network policy changed during read" };
6506
6603
  }
@@ -6509,7 +6606,7 @@ function readPolicyFile(filePath) {
6509
6606
  return { status: "invalid", error: "network policy could not be read" };
6510
6607
  } finally {
6511
6608
  try {
6512
- import_node_fs16.default.closeSync(fileDescriptor);
6609
+ import_node_fs17.default.closeSync(fileDescriptor);
6513
6610
  } catch {
6514
6611
  }
6515
6612
  }
@@ -6935,27 +7032,27 @@ function assertExactKeys(value, keys2) {
6935
7032
  function readDocument(store) {
6936
7033
  let descriptor;
6937
7034
  try {
6938
- descriptor = import_node_fs17.default.openSync(
7035
+ descriptor = import_node_fs18.default.openSync(
6939
7036
  store.filePath,
6940
- import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NONBLOCK | (import_node_fs17.default.constants.O_NOFOLLOW ?? 0)
7037
+ import_node_fs18.default.constants.O_RDONLY | import_node_fs18.default.constants.O_NONBLOCK | (import_node_fs18.default.constants.O_NOFOLLOW ?? 0)
6941
7038
  );
6942
7039
  } catch (error) {
6943
7040
  if (isErrno2(error, "ENOENT")) return void 0;
6944
7041
  throw claimError("invalid-state", "network claim state could not be read");
6945
7042
  }
6946
7043
  try {
6947
- const stat = import_node_fs17.default.fstatSync(descriptor);
7044
+ const stat = import_node_fs18.default.fstatSync(descriptor);
6948
7045
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
6949
7046
  throw claimError("invalid-state", "network claim state is not a bounded private file");
6950
7047
  }
6951
7048
  const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
6952
7049
  let count = 0;
6953
7050
  while (count < buffer.length) {
6954
- const bytesRead = import_node_fs17.default.readSync(descriptor, buffer, count, buffer.length - count, null);
7051
+ const bytesRead = import_node_fs18.default.readSync(descriptor, buffer, count, buffer.length - count, null);
6955
7052
  if (bytesRead === 0) break;
6956
7053
  count += bytesRead;
6957
7054
  }
6958
- const finalStat = import_node_fs17.default.fstatSync(descriptor);
7055
+ const finalStat = import_node_fs18.default.fstatSync(descriptor);
6959
7056
  if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
6960
7057
  throw claimError("invalid-state", "network claim state changed during read");
6961
7058
  }
@@ -6967,7 +7064,7 @@ function readDocument(store) {
6967
7064
  }
6968
7065
  return validateDocument(parsed, store.daemonId);
6969
7066
  } finally {
6970
- import_node_fs17.default.closeSync(descriptor);
7067
+ import_node_fs18.default.closeSync(descriptor);
6971
7068
  }
6972
7069
  }
6973
7070
  function validateDocument(value, daemonId) {
@@ -7071,7 +7168,7 @@ function withStoreLock(daemonId, options, operation) {
7071
7168
  if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > MAX_LOCK_WAIT_MS) {
7072
7169
  throw claimError("invalid-input", "network claim lock wait is invalid");
7073
7170
  }
7074
- import_node_fs17.default.mkdirSync(store.root, { recursive: true, mode: 448 });
7171
+ import_node_fs18.default.mkdirSync(store.root, { recursive: true, mode: 448 });
7075
7172
  assertDirectory(store.root);
7076
7173
  return withFileLockSync(
7077
7174
  store.lockPath,
@@ -7102,7 +7199,7 @@ function validateRoot(root) {
7102
7199
  function assertDirectory(directory) {
7103
7200
  let stat;
7104
7201
  try {
7105
- stat = import_node_fs17.default.lstatSync(directory);
7202
+ stat = import_node_fs18.default.lstatSync(directory);
7106
7203
  } catch {
7107
7204
  throw claimError("invalid-state", "network claim root could not be read");
7108
7205
  }
@@ -7144,7 +7241,7 @@ function networkOwnerKey(repoPath) {
7144
7241
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
7145
7242
  let names;
7146
7243
  try {
7147
- names = import_node_fs18.default.readdirSync(root);
7244
+ names = import_node_fs19.default.readdirSync(root);
7148
7245
  } catch (error) {
7149
7246
  if (error.code === "ENOENT") return void 0;
7150
7247
  throw new Error("Network claims cannot be inspected; repair is required.");
@@ -7154,20 +7251,20 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
7154
7251
  let found;
7155
7252
  for (const file of files) {
7156
7253
  const full = import_node_path23.default.join(root, file);
7157
- const stat = import_node_fs18.default.lstatSync(full);
7254
+ const stat = import_node_fs19.default.lstatSync(full);
7158
7255
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
7159
7256
  throw new Error("Invalid retained network claim file.");
7160
7257
  let value;
7161
- const descriptor = import_node_fs18.default.openSync(
7258
+ const descriptor = import_node_fs19.default.openSync(
7162
7259
  full,
7163
- import_node_fs18.default.constants.O_RDONLY | import_node_fs18.default.constants.O_NONBLOCK | import_node_fs18.default.constants.O_NOFOLLOW
7260
+ import_node_fs19.default.constants.O_RDONLY | import_node_fs19.default.constants.O_NONBLOCK | import_node_fs19.default.constants.O_NOFOLLOW
7164
7261
  );
7165
7262
  try {
7166
- if (!import_node_fs18.default.fstatSync(descriptor).isFile()) throw new Error();
7263
+ if (!import_node_fs19.default.fstatSync(descriptor).isFile()) throw new Error();
7167
7264
  const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
7168
7265
  let size = 0;
7169
7266
  while (size < bytes2.length) {
7170
- const count = import_node_fs18.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
7267
+ const count = import_node_fs19.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
7171
7268
  if (!count) break;
7172
7269
  size += count;
7173
7270
  }
@@ -7177,7 +7274,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
7177
7274
  } catch {
7178
7275
  throw new Error("Retained network claim could not be read safely.");
7179
7276
  } finally {
7180
- import_node_fs18.default.closeSync(descriptor);
7277
+ import_node_fs19.default.closeSync(descriptor);
7181
7278
  }
7182
7279
  if (typeof value.daemonId !== "string" || `${(0, import_node_crypto10.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
7183
7280
  throw new Error("Retained network daemon identity is invalid.");
@@ -7347,7 +7444,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
7347
7444
  }
7348
7445
  }
7349
7446
  function withMutationLock(activity, target, operation) {
7350
- import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7447
+ import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7351
7448
  return withFileLockSync(
7352
7449
  DEVSY_MUTATION_LOCK_FILE,
7353
7450
  {
@@ -7361,7 +7458,7 @@ function withMutationLock(activity, target, operation) {
7361
7458
  );
7362
7459
  }
7363
7460
  function withMutationLockAsync(activity, target, operation) {
7364
- import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7461
+ import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7365
7462
  return withFileLock(
7366
7463
  DEVSY_MUTATION_LOCK_FILE,
7367
7464
  {
@@ -7598,7 +7695,7 @@ var ExecutionOutcomeError = class extends Error {
7598
7695
 
7599
7696
  // src/core/reliability-lifecycle.ts
7600
7697
  var import_node_crypto20 = require("crypto");
7601
- var import_node_path38 = __toESM(require("path"));
7698
+ var import_node_path39 = __toESM(require("path"));
7602
7699
  var import_node_util6 = require("util");
7603
7700
 
7604
7701
  // src/core/capacity-accounting.ts
@@ -7656,11 +7753,11 @@ function evaluateCapacity(budgets, samples, charges, requested, nowMs, maxSample
7656
7753
  }
7657
7754
 
7658
7755
  // src/core/capacity-policy.ts
7659
- var import_node_fs20 = __toESM(require("fs"));
7756
+ var import_node_fs21 = __toESM(require("fs"));
7660
7757
  var import_node_path25 = __toESM(require("path"));
7661
7758
 
7662
7759
  // src/core/capacity-store.ts
7663
- var import_node_fs21 = __toESM(require("fs"));
7760
+ var import_node_fs22 = __toESM(require("fs"));
7664
7761
  var import_node_path26 = __toESM(require("path"));
7665
7762
  var MAX_BYTES = 1048576;
7666
7763
  var CapacitySnapshotChangedError = class extends Error {
@@ -7670,8 +7767,8 @@ var CapacitySnapshotChangedError = class extends Error {
7670
7767
  }
7671
7768
  };
7672
7769
  function assertPrivateDirectory(directoryPath) {
7673
- import_node_fs21.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
7674
- const directory = import_node_fs21.default.lstatSync(directoryPath);
7770
+ import_node_fs22.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
7771
+ const directory = import_node_fs22.default.lstatSync(directoryPath);
7675
7772
  if (!directory.isDirectory() || directory.isSymbolicLink() || directory.uid !== process.getuid?.() || (directory.mode & 63) !== 0)
7676
7773
  throw new Error("Capacity reservation directory is not private.");
7677
7774
  }
@@ -7762,24 +7859,24 @@ var CapacityStore = class {
7762
7859
  read() {
7763
7860
  let descriptor;
7764
7861
  try {
7765
- descriptor = import_node_fs21.default.openSync(this.file, import_node_fs21.default.constants.O_RDONLY | import_node_fs21.default.constants.O_NOFOLLOW);
7862
+ descriptor = import_node_fs22.default.openSync(this.file, import_node_fs22.default.constants.O_RDONLY | import_node_fs22.default.constants.O_NOFOLLOW);
7766
7863
  } catch (error) {
7767
7864
  if (error.code === "ENOENT")
7768
7865
  return { version: 1, revision: 0, reservations: [] };
7769
7866
  throw error;
7770
7867
  }
7771
7868
  try {
7772
- const stat = import_node_fs21.default.fstatSync(descriptor);
7869
+ const stat = import_node_fs22.default.fstatSync(descriptor);
7773
7870
  if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_BYTES)
7774
7871
  throw new Error("Unsafe capacity reservation snapshot.");
7775
7872
  const buffer = Buffer.alloc(MAX_BYTES + 1);
7776
- const count = import_node_fs21.default.readSync(descriptor, buffer, 0, buffer.length, 0);
7873
+ const count = import_node_fs22.default.readSync(descriptor, buffer, 0, buffer.length, 0);
7777
7874
  if (count > MAX_BYTES) throw new Error("Capacity reservation snapshot exceeds byte limit.");
7778
7875
  const value = JSON.parse(buffer.subarray(0, count).toString("utf8"));
7779
7876
  validate(value);
7780
7877
  return value;
7781
7878
  } finally {
7782
- import_node_fs21.default.closeSync(descriptor);
7879
+ import_node_fs22.default.closeSync(descriptor);
7783
7880
  }
7784
7881
  }
7785
7882
  /**
@@ -7966,7 +8063,7 @@ var CapacityStore = class {
7966
8063
 
7967
8064
  // src/core/controller-client.ts
7968
8065
  var import_node_crypto12 = require("crypto");
7969
- var import_node_fs22 = __toESM(require("fs"));
8066
+ var import_node_fs23 = __toESM(require("fs"));
7970
8067
  var import_node_net2 = __toESM(require("net"));
7971
8068
  var import_node_path28 = __toESM(require("path"));
7972
8069
 
@@ -7981,7 +8078,7 @@ var CONTROLLER_PROFILE_RE = new RegExp(
7981
8078
 
7982
8079
  // src/core/controller-store.ts
7983
8080
  var import_node_crypto13 = require("crypto");
7984
- var import_node_fs23 = __toESM(require("fs"));
8081
+ var import_node_fs24 = __toESM(require("fs"));
7985
8082
  var import_node_path29 = __toESM(require("path"));
7986
8083
  var CONTROLLER_SNAPSHOT_BYTES = 1048576;
7987
8084
  function fail() {
@@ -8094,22 +8191,22 @@ var ControllerStore = class {
8094
8191
  read() {
8095
8192
  let fd;
8096
8193
  try {
8097
- fd = import_node_fs23.default.openSync(
8194
+ fd = import_node_fs24.default.openSync(
8098
8195
  this.file,
8099
- import_node_fs23.default.constants.O_RDONLY | import_node_fs23.default.constants.O_NOFOLLOW | import_node_fs23.default.constants.O_NONBLOCK
8196
+ import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW | import_node_fs24.default.constants.O_NONBLOCK
8100
8197
  );
8101
8198
  } catch (error) {
8102
8199
  if (error.code === "ENOENT") return void 0;
8103
8200
  throw new Error("Controller snapshot cannot be opened.");
8104
8201
  }
8105
8202
  try {
8106
- const stat = import_node_fs23.default.fstatSync(fd);
8203
+ const stat = import_node_fs24.default.fstatSync(fd);
8107
8204
  if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > CONTROLLER_SNAPSHOT_BYTES)
8108
8205
  fail();
8109
8206
  const bytes2 = Buffer.alloc(CONTROLLER_SNAPSHOT_BYTES + 1);
8110
8207
  let length = 0;
8111
8208
  while (length < bytes2.length) {
8112
- const count = import_node_fs23.default.readSync(fd, bytes2, length, bytes2.length - length, null);
8209
+ const count = import_node_fs24.default.readSync(fd, bytes2, length, bytes2.length - length, null);
8113
8210
  if (!count) break;
8114
8211
  length += count;
8115
8212
  }
@@ -8120,7 +8217,7 @@ var ControllerStore = class {
8120
8217
  } catch {
8121
8218
  throw new Error("Controller snapshot is unreadable or invalid.");
8122
8219
  } finally {
8123
- import_node_fs23.default.closeSync(fd);
8220
+ import_node_fs24.default.closeSync(fd);
8124
8221
  }
8125
8222
  }
8126
8223
  persist(snapshot) {
@@ -8136,11 +8233,11 @@ var ControllerStore = class {
8136
8233
  ` !== contents)
8137
8234
  throw new Error("Controller snapshot commit failed.");
8138
8235
  for (const file of [this.file, this.directory]) {
8139
- const fd = import_node_fs23.default.openSync(file, import_node_fs23.default.constants.O_RDONLY | import_node_fs23.default.constants.O_NOFOLLOW);
8236
+ const fd = import_node_fs24.default.openSync(file, import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW);
8140
8237
  try {
8141
- import_node_fs23.default.fsyncSync(fd);
8238
+ import_node_fs24.default.fsyncSync(fd);
8142
8239
  } finally {
8143
- import_node_fs23.default.closeSync(fd);
8240
+ import_node_fs24.default.closeSync(fd);
8144
8241
  }
8145
8242
  }
8146
8243
  }
@@ -8936,11 +9033,11 @@ function stepReliability(state, event, nowMs) {
8936
9033
 
8937
9034
  // src/core/reliability-operation-store.ts
8938
9035
  var import_node_crypto14 = require("crypto");
8939
- var import_node_fs24 = __toESM(require("fs"));
9036
+ var import_node_fs25 = __toESM(require("fs"));
8940
9037
  var import_node_path30 = __toESM(require("path"));
8941
9038
  var import_node_util5 = require("util");
8942
9039
  function installedCliVersion() {
8943
- return true ? "0.0.68" : "0.0.0-dev";
9040
+ return true ? "0.0.70" : "0.0.0-dev";
8944
9041
  }
8945
9042
  function compareCliVersions(left, right) {
8946
9043
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -9275,25 +9372,25 @@ function readReliabilityOperation(identity2) {
9275
9372
  function readBoundedPrivateJson(file, label) {
9276
9373
  let descriptor;
9277
9374
  try {
9278
- descriptor = import_node_fs24.default.openSync(
9375
+ descriptor = import_node_fs25.default.openSync(
9279
9376
  file,
9280
- import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW | import_node_fs24.default.constants.O_NONBLOCK
9377
+ import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NOFOLLOW | import_node_fs25.default.constants.O_NONBLOCK
9281
9378
  );
9282
9379
  } catch (error) {
9283
9380
  if (error.code === "ENOENT") return void 0;
9284
9381
  throw error;
9285
9382
  }
9286
9383
  try {
9287
- const stat = import_node_fs24.default.fstatSync(descriptor);
9384
+ const stat = import_node_fs25.default.fstatSync(descriptor);
9288
9385
  if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
9289
9386
  throw new Error(`${label} is not a bounded private file.`);
9290
9387
  }
9291
9388
  const bytes2 = Buffer.alloc(MAX_RECORD_BYTES + 1);
9292
- const count = import_node_fs24.default.readSync(descriptor, bytes2, 0, bytes2.length, 0);
9389
+ const count = import_node_fs25.default.readSync(descriptor, bytes2, 0, bytes2.length, 0);
9293
9390
  if (count > MAX_RECORD_BYTES) throw new Error(`${label} exceeds its byte limit.`);
9294
9391
  return JSON.parse(bytes2.subarray(0, count).toString("utf8"));
9295
9392
  } finally {
9296
- import_node_fs24.default.closeSync(descriptor);
9393
+ import_node_fs25.default.closeSync(descriptor);
9297
9394
  }
9298
9395
  }
9299
9396
  function persist(record5) {
@@ -9308,23 +9405,23 @@ function persist(record5) {
9308
9405
  } catch (error) {
9309
9406
  const reread = readReliabilityOperation(record5.identity);
9310
9407
  if (!reread || JSON.stringify(reread) !== JSON.stringify(record5)) throw error;
9311
- const descriptor = import_node_fs24.default.openSync(file, import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NOFOLLOW);
9408
+ const descriptor = import_node_fs25.default.openSync(file, import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NOFOLLOW);
9312
9409
  try {
9313
- import_node_fs24.default.fsyncSync(descriptor);
9410
+ import_node_fs25.default.fsyncSync(descriptor);
9314
9411
  } finally {
9315
- import_node_fs24.default.closeSync(descriptor);
9412
+ import_node_fs25.default.closeSync(descriptor);
9316
9413
  }
9317
- const directory = import_node_fs24.default.openSync(import_node_path30.default.dirname(file), "r");
9414
+ const directory = import_node_fs25.default.openSync(import_node_path30.default.dirname(file), "r");
9318
9415
  try {
9319
- import_node_fs24.default.fsyncSync(directory);
9416
+ import_node_fs25.default.fsyncSync(directory);
9320
9417
  } finally {
9321
- import_node_fs24.default.closeSync(directory);
9418
+ import_node_fs25.default.closeSync(directory);
9322
9419
  }
9323
9420
  }
9324
9421
  }
9325
9422
  function updateReliabilityOperation(identity2, operation) {
9326
9423
  const file = reliabilityOperationPath(identity2);
9327
- import_node_fs24.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true, mode: 448 });
9424
+ import_node_fs25.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true, mode: 448 });
9328
9425
  return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
9329
9426
  const record5 = readReliabilityOperation(identity2) ?? {
9330
9427
  version: 1,
@@ -9365,7 +9462,7 @@ function clearStartupWitness(identity2, fence) {
9365
9462
  // src/core/reliability-worker.ts
9366
9463
  var import_node_child_process16 = require("child_process");
9367
9464
  var import_node_crypto15 = require("crypto");
9368
- var import_node_fs25 = __toESM(require("fs"));
9465
+ var import_node_fs26 = __toESM(require("fs"));
9369
9466
  var import_node_path31 = __toESM(require("path"));
9370
9467
  var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
9371
9468
  function workerGroupAbsent(pid) {
@@ -9379,7 +9476,7 @@ function workerGroupAbsent(pid) {
9379
9476
 
9380
9477
  // src/core/traefik-route-health.ts
9381
9478
  var import_node_child_process17 = require("child_process");
9382
- var import_node_fs26 = __toESM(require("fs"));
9479
+ var import_node_fs27 = __toESM(require("fs"));
9383
9480
  var import_node_path32 = __toESM(require("path"));
9384
9481
  var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
9385
9482
  var ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
@@ -9651,7 +9748,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
9651
9748
  throw new Error(
9652
9749
  "Traefik routes did not reload; shared router restart is disabled for this operation."
9653
9750
  );
9654
- import_node_fs26.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9751
+ import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9655
9752
  return withFileLock(
9656
9753
  ROUTER_RELOAD_LOCK_FILE,
9657
9754
  {
@@ -9702,20 +9799,20 @@ function assertTraefikRoutesRemoved(routes) {
9702
9799
  }
9703
9800
 
9704
9801
  // src/core/workspace-ensure.ts
9705
- var import_node_child_process26 = require("child_process");
9706
- var import_node_fs32 = __toESM(require("fs"));
9707
- var import_node_path37 = __toESM(require("path"));
9802
+ var import_node_child_process27 = require("child_process");
9803
+ var import_node_fs33 = __toESM(require("fs"));
9804
+ var import_node_path38 = __toESM(require("path"));
9708
9805
 
9709
9806
  // src/core/devpod-mutation.ts
9710
9807
  var import_node_child_process18 = require("child_process");
9711
- var import_node_fs27 = __toESM(require("fs"));
9808
+ var import_node_fs28 = __toESM(require("fs"));
9712
9809
  var import_node_path33 = __toESM(require("path"));
9713
9810
  var DEVPOD_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
9714
9811
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
9715
9812
  var DevpodStartPostconditionError = class extends Error {
9716
9813
  };
9717
9814
  function withMutationLock2(activity, target, operation) {
9718
- import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9815
+ import_node_fs28.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9719
9816
  return withFileLockSync(
9720
9817
  DEVPOD_MUTATION_LOCK_FILE,
9721
9818
  {
@@ -9916,8 +10013,274 @@ async function startDevpodWorkspace(options) {
9916
10013
  });
9917
10014
  }
9918
10015
 
9919
- // src/core/http-route-probe.ts
10016
+ // src/core/host-port-claims.ts
9920
10017
  var import_node_child_process19 = require("child_process");
10018
+ var import_node_path34 = __toESM(require("path"));
10019
+ var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
10020
+ var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
10021
+ var DOCKER_TIMEOUT_MS = 1e4;
10022
+ var DOCKER_MAX_BUFFER = 1024 * 1024;
10023
+ var MAX_DESIRED_HOST_PORTS = 1024;
10024
+ var MAX_RUNNING_HOLDERS = 512;
10025
+ function isRecord4(value) {
10026
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10027
+ }
10028
+ function optionalLabel(labels, key) {
10029
+ const value = isRecord4(labels) ? labels[key] : void 0;
10030
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10031
+ }
10032
+ function normalizeHostIp(value) {
10033
+ if (typeof value !== "string") return null;
10034
+ const trimmed = value.trim();
10035
+ if (trimmed.length === 0 || trimmed === "0.0.0.0" || trimmed === "::") return null;
10036
+ return trimmed;
10037
+ }
10038
+ function parseSinglePortValue(value, service, label) {
10039
+ if (typeof value === "number") {
10040
+ if (!Number.isInteger(value) || value <= 0 || value > 65535) {
10041
+ throw new Error(`Service '${service}' has an out-of-range ${label} port.`);
10042
+ }
10043
+ return value;
10044
+ }
10045
+ if (typeof value === "string") {
10046
+ const parsed = Number(value);
10047
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
10048
+ throw new Error(`Service '${service}' has an out-of-range ${label} port '${value}'.`);
10049
+ }
10050
+ return parsed;
10051
+ }
10052
+ throw new Error(`Service '${service}' has an unsupported ${label} port value.`);
10053
+ }
10054
+ function parseFixedPublishedPort(value, service) {
10055
+ if (value === void 0 || value === null || value === "" || value === 0 || value === "0") {
10056
+ return null;
10057
+ }
10058
+ if (typeof value === "string" && value.includes("-")) {
10059
+ return null;
10060
+ }
10061
+ return parseSinglePortValue(value, service, "published host");
10062
+ }
10063
+ function parsePublishedRange(value, service) {
10064
+ const match = /^(\d+)-(\d+)$/.exec(value.trim());
10065
+ if (!match) return void 0;
10066
+ const lower = Number(match[1]);
10067
+ const upper = Number(match[2]);
10068
+ if (!Number.isInteger(lower) || !Number.isInteger(upper) || lower <= 0 || upper < lower || upper > 65535) {
10069
+ throw new Error(`Service '${service}' publishes an invalid host port range '${value}'.`);
10070
+ }
10071
+ return [lower, upper];
10072
+ }
10073
+ function resolveFixedPublishedHostPorts(renderedConfig) {
10074
+ const services = isRecord4(renderedConfig) ? renderedConfig.services : void 0;
10075
+ const bindings = [];
10076
+ const pushBinding = (binding) => {
10077
+ bindings.push(binding);
10078
+ if (bindings.length > MAX_DESIRED_HOST_PORTS) {
10079
+ throw new Error(
10080
+ `The managed Compose model declares more than ${MAX_DESIRED_HOST_PORTS} fixed host ports.`
10081
+ );
10082
+ }
10083
+ };
10084
+ for (const [service, serviceValue] of Object.entries(isRecord4(services) ? services : {})) {
10085
+ const serviceRecord = isRecord4(serviceValue) ? serviceValue : {};
10086
+ if (!Array.isArray(serviceRecord.ports)) continue;
10087
+ for (const port of serviceRecord.ports) {
10088
+ if (!isRecord4(port)) {
10089
+ throw new Error(`Service '${service}' declares an unsupported port entry.`);
10090
+ }
10091
+ parseSinglePortValue(port.target, service, "container target");
10092
+ const hostIp = normalizeHostIp(port.host_ip);
10093
+ const protocol = typeof port.protocol === "string" && port.protocol ? port.protocol : "tcp";
10094
+ if (typeof port.published !== "string" || !port.published.includes("-")) {
10095
+ const hostPort = parseFixedPublishedPort(port.published, service);
10096
+ if (hostPort === null) continue;
10097
+ pushBinding({ service, hostIp, hostPort, protocol });
10098
+ continue;
10099
+ }
10100
+ const range = parsePublishedRange(port.published, service);
10101
+ if (!range) {
10102
+ throw new Error(
10103
+ `Service '${service}' publishes an unsupported host port value '${String(port.published)}'.`
10104
+ );
10105
+ }
10106
+ for (let hostPort = range[0]; hostPort <= range[1]; hostPort += 1) {
10107
+ pushBinding({ service, hostIp, hostPort, protocol });
10108
+ }
10109
+ }
10110
+ }
10111
+ return bindings;
10112
+ }
10113
+ function hostPortBindingsConflict(desired, holder) {
10114
+ return desired.hostPort === holder.hostPort && desired.protocol === holder.protocol && // Wildcards collide with everything in their protocol family; specific
10115
+ // addresses only collide with themselves. IPv6 nuance intentionally
10116
+ // over-approximates: a loud refusal beats a missed kernel collision.
10117
+ (desired.hostIp === null || holder.hostIp === null || desired.hostIp === holder.hostIp);
10118
+ }
10119
+ var HOST_PORT_INSPECT_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}}},"ports":{{json .NetworkSettings.Ports}}}';
10120
+ function runDocker(args) {
10121
+ const result = (0, import_node_child_process19.spawnSync)("docker", args, {
10122
+ ...networkDockerOptions(),
10123
+ encoding: "utf-8",
10124
+ timeout: DOCKER_TIMEOUT_MS,
10125
+ maxBuffer: DOCKER_MAX_BUFFER
10126
+ });
10127
+ if (result.error || result.status !== 0) {
10128
+ const reason2 = (result.stderr || result.stdout || result.error?.message || "unknown error").toString().trim();
10129
+ throw new Error(`docker ${String(args[0])} failed: ${reason2}`);
10130
+ }
10131
+ if (typeof result.stdout !== "string") {
10132
+ throw new Error(`docker ${String(args[0])} returned invalid output.`);
10133
+ }
10134
+ return result.stdout;
10135
+ }
10136
+ function parseHolderPortMap(value) {
10137
+ const ports = [];
10138
+ for (const [key, entries] of Object.entries(isRecord4(value) ? value : {})) {
10139
+ const separator = key.lastIndexOf("/");
10140
+ if (separator <= 0) continue;
10141
+ const protocol = key.slice(separator + 1);
10142
+ if (!Array.isArray(entries)) continue;
10143
+ for (const entry of entries) {
10144
+ if (!isRecord4(entry) || entry.HostPort === void 0 || entry.HostPort === null) continue;
10145
+ const bound = Number(entry.HostPort);
10146
+ if (!Number.isInteger(bound) || bound <= 0) continue;
10147
+ ports.push({ hostIp: normalizeHostIp(entry.HostIp), hostPort: bound, protocol });
10148
+ }
10149
+ }
10150
+ const unique = /* @__PURE__ */ new Map();
10151
+ for (const port of ports) {
10152
+ unique.set(`${port.hostIp ?? ""}|${port.hostPort}|${port.protocol}`, port);
10153
+ }
10154
+ return [...unique.values()];
10155
+ }
10156
+ function parseHolderSnapshots(stdout) {
10157
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
10158
+ let parsed;
10159
+ try {
10160
+ parsed = JSON.parse(line);
10161
+ } catch {
10162
+ throw new Error("Host-port holder inspection returned malformed JSON.");
10163
+ }
10164
+ if (!isRecord4(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) {
10165
+ throw new Error("Host-port holder inspection returned an invalid container record.");
10166
+ }
10167
+ const name = typeof parsed.name === "string" ? parsed.name.replace(/^\//, "") : parsed.id;
10168
+ return {
10169
+ id: parsed.id,
10170
+ name,
10171
+ composeProject: optionalLabel(parsed.labels, "com.docker.compose.project"),
10172
+ workingDir: optionalLabel(parsed.labels, "com.docker.compose.project.working_dir"),
10173
+ ports: parseHolderPortMap(parsed.ports)
10174
+ };
10175
+ });
10176
+ }
10177
+ function listHolderSnapshots() {
10178
+ const listed = runDocker(["ps", "--filter", "status=running", "--format", "{{.ID}}"]);
10179
+ const ids2 = listed.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
10180
+ if (ids2.length === 0) return [];
10181
+ if (ids2.length > MAX_RUNNING_HOLDERS) {
10182
+ throw new Error(
10183
+ `Host-port holder inspection found ${ids2.length} running containers, above its bound.`
10184
+ );
10185
+ }
10186
+ const inspected = runDocker(["inspect", "--format", HOST_PORT_INSPECT_TEMPLATE, ...ids2]);
10187
+ return parseHolderSnapshots(inspected);
10188
+ }
10189
+ function renderManagedComposeModel(plan, workspace) {
10190
+ const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
10191
+ const result = (0, import_node_child_process19.spawnSync)(
10192
+ "docker",
10193
+ [
10194
+ "compose",
10195
+ "--profile",
10196
+ "*",
10197
+ "--project-directory",
10198
+ plan.composeDirectory,
10199
+ ...fileArgs,
10200
+ "config",
10201
+ "--format",
10202
+ "json"
10203
+ ],
10204
+ {
10205
+ cwd: plan.composeDirectory,
10206
+ encoding: "utf-8",
10207
+ env: managedComposeEnvironment(workspace),
10208
+ stdio: ["ignore", "pipe", "pipe"],
10209
+ timeout: COMPOSE_RENDER_TIMEOUT_MS,
10210
+ maxBuffer: COMPOSE_RENDER_MAX_BUFFER
10211
+ }
10212
+ );
10213
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
10214
+ const reason2 = (result.stderr || result.error?.message || "unknown error").toString().trim().split(/\r?\n/).slice(-3).join("; ");
10215
+ throw new Error(
10216
+ `Host-port claims could not render the managed Compose model that the start would bind: ${reason2}`
10217
+ );
10218
+ }
10219
+ try {
10220
+ return JSON.parse(result.stdout);
10221
+ } catch {
10222
+ throw new Error("Host-port claims received a malformed rendered Compose model.");
10223
+ }
10224
+ }
10225
+ function attributeHolderWorktree(workingDir) {
10226
+ if (import_node_path34.default.basename(workingDir) !== ".devcontainer") return void 0;
10227
+ return comparableWorkspacePath(import_node_path34.default.dirname(workingDir));
10228
+ }
10229
+ function holderAttribution(holder, repoPath) {
10230
+ const attribution = {};
10231
+ if (holder.composeProject) attribution.holderComposeProject = holder.composeProject;
10232
+ const worktree = holder.workingDir ? attributeHolderWorktree(holder.workingDir) : void 0;
10233
+ if (!worktree) return attribution;
10234
+ attribution.holderWorktreePath = worktree;
10235
+ try {
10236
+ const owner = listWorkspaceOwnership(repoPath).find(
10237
+ (record5) => comparableWorkspacePath(record5.worktreePath) === worktree
10238
+ );
10239
+ if (owner) {
10240
+ attribution.holderWorkspace = owner.workspace;
10241
+ if (owner.branch) attribution.holderBranch = owner.branch;
10242
+ }
10243
+ } catch {
10244
+ }
10245
+ return attribution;
10246
+ }
10247
+ function remediationFor(holder, attribution) {
10248
+ if (attribution.holderWorktreePath) {
10249
+ return `Stop the holding workspace with 'devrouter stop ${attribution.holderWorktreePath}', or override the consumer's published host binding through its own configuration, then run ensure again.`;
10250
+ }
10251
+ return `Stop or reconfigure the holding container '${holder.name}' and run ensure again; devrouter never rewrites consumer-declared host bindings.`;
10252
+ }
10253
+ function detectHostPortClaimConflicts(options) {
10254
+ const desired = resolveFixedPublishedHostPorts(
10255
+ renderManagedComposeModel(options.plan, options.workspace)
10256
+ );
10257
+ if (desired.length === 0) return [];
10258
+ const holders = listHolderSnapshots();
10259
+ const devcontainerDir = import_node_path34.default.join(options.repoPath, ".devcontainer");
10260
+ const conflicts = [];
10261
+ for (const binding of desired) {
10262
+ for (const holder of holders) {
10263
+ for (const holderPort of holder.ports) {
10264
+ if (!hostPortBindingsConflict(binding, holderPort)) continue;
10265
+ if (holder.workingDir && sameWorkspacePath(holder.workingDir, devcontainerDir)) continue;
10266
+ const attribution = holderAttribution(holder, options.repoPath);
10267
+ conflicts.push({
10268
+ service: binding.service,
10269
+ hostIp: binding.hostIp,
10270
+ hostPort: binding.hostPort,
10271
+ protocol: binding.protocol,
10272
+ holderContainer: holder.name,
10273
+ ...attribution,
10274
+ remediation: remediationFor(holder, attribution)
10275
+ });
10276
+ }
10277
+ }
10278
+ }
10279
+ return conflicts;
10280
+ }
10281
+
10282
+ // src/core/http-route-probe.ts
10283
+ var import_node_child_process20 = require("child_process");
9921
10284
  var DEFAULT_MAX_TIME_SECONDS = 5;
9922
10285
  var PARENT_TIMEOUT_PADDING_MS = 250;
9923
10286
  var CONTRACT_MAX_BUFFER = 1024;
@@ -9950,7 +10313,7 @@ function probeHttpRoute(host, options = {}) {
9950
10313
  args.push("--cacert", CERT_FILE);
9951
10314
  }
9952
10315
  args.push(url);
9953
- const result = (0, import_node_child_process19.spawnSync)("curl", args, { encoding: "utf-8" });
10316
+ const result = (0, import_node_child_process20.spawnSync)("curl", args, { encoding: "utf-8" });
9954
10317
  const stdout = result.stdout?.trim() ?? "";
9955
10318
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
9956
10319
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
@@ -9969,7 +10332,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
9969
10332
  }
9970
10333
  let result;
9971
10334
  try {
9972
- result = (0, import_node_child_process19.spawnSync)("curl", args, {
10335
+ result = (0, import_node_child_process20.spawnSync)("curl", args, {
9973
10336
  encoding: "utf-8",
9974
10337
  maxBuffer: CONTRACT_MAX_BUFFER,
9975
10338
  timeout: Math.ceil(parentTimeoutMs)
@@ -10053,8 +10416,8 @@ function transportFailure() {
10053
10416
  }
10054
10417
 
10055
10418
  // src/core/managed-host-preparation.ts
10056
- var import_node_child_process20 = require("child_process");
10057
- var import_node_fs28 = __toESM(require("fs"));
10419
+ var import_node_child_process21 = require("child_process");
10420
+ var import_node_fs29 = __toESM(require("fs"));
10058
10421
  var PREPARATION_TIMEOUT_MS = 6e4;
10059
10422
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
10060
10423
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -10065,7 +10428,7 @@ function runPreparationCommand(argv, cwd) {
10065
10428
  return new Promise((resolve) => {
10066
10429
  let child;
10067
10430
  try {
10068
- child = (0, import_node_child_process20.spawn)(argv[0], argv.slice(1), {
10431
+ child = (0, import_node_child_process21.spawn)(argv[0], argv.slice(1), {
10069
10432
  cwd,
10070
10433
  detached: true,
10071
10434
  shell: false,
@@ -10097,7 +10460,7 @@ function runPreparationCommand(argv, cwd) {
10097
10460
  }
10098
10461
  function readConfigBytes(configPath) {
10099
10462
  try {
10100
- return import_node_fs28.default.readFileSync(configPath);
10463
+ return import_node_fs29.default.readFileSync(configPath);
10101
10464
  } catch {
10102
10465
  return void 0;
10103
10466
  }
@@ -10131,10 +10494,10 @@ async function runManagedHostPreparation(repoPath, config) {
10131
10494
  }
10132
10495
 
10133
10496
  // src/core/managed-post-start.ts
10134
- var import_node_child_process21 = require("child_process");
10497
+ var import_node_child_process22 = require("child_process");
10135
10498
  var import_node_crypto16 = require("crypto");
10136
- var import_node_fs29 = __toESM(require("fs"));
10137
- var import_node_path34 = __toESM(require("path"));
10499
+ var import_node_fs30 = __toESM(require("fs"));
10500
+ var import_node_path35 = __toESM(require("path"));
10138
10501
  var MANAGED_MARKER = "devrouter:managed devcontainer";
10139
10502
  var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
10140
10503
  var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
@@ -10147,7 +10510,7 @@ function renderDeliveryScript(targetPath) {
10147
10510
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
10148
10511
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
10149
10512
  }
10150
- const temporaryPrefix = import_node_path34.default.posix.basename(targetPath);
10513
+ const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
10151
10514
  return `set -eu
10152
10515
  umask 077
10153
10516
  runtime_root=/tmp/devrouter
@@ -10171,7 +10534,7 @@ function commandFailure3(result) {
10171
10534
  return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
10172
10535
  }
10173
10536
  function deliverRuntimeFile(containerId, targetPath, contents, label) {
10174
- const result = (0, import_node_child_process21.spawnSync)(
10537
+ const result = (0, import_node_child_process22.spawnSync)(
10175
10538
  "docker",
10176
10539
  ["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
10177
10540
  { input: contents, encoding: "utf-8", ...networkDockerOptions() }
@@ -10182,8 +10545,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
10182
10545
  }
10183
10546
  }
10184
10547
  function readRegularFileBytes(filePath) {
10185
- if (!import_node_fs29.default.existsSync(filePath) || !import_node_fs29.default.lstatSync(filePath).isFile()) return void 0;
10186
- return import_node_fs29.default.readFileSync(filePath);
10548
+ if (!import_node_fs30.default.existsSync(filePath) || !import_node_fs30.default.lstatSync(filePath).isFile()) return void 0;
10549
+ return import_node_fs30.default.readFileSync(filePath);
10187
10550
  }
10188
10551
  function readRegularFile(filePath) {
10189
10552
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -10193,21 +10556,21 @@ function adapterFingerprint(adapter) {
10193
10556
  }
10194
10557
  function resolveProcessHelperPath() {
10195
10558
  const candidates = [
10196
- import_node_path34.default.resolve(__dirname, "..", "bin", "devrouter-process"),
10197
- import_node_path34.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
10559
+ import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
10560
+ import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
10198
10561
  ];
10199
- const helperPath = candidates.find((candidate) => import_node_fs29.default.existsSync(candidate));
10562
+ const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
10200
10563
  if (!helperPath) {
10201
10564
  throw new Error("Could not locate the packaged devrouter-process helper.");
10202
10565
  }
10203
10566
  return helperPath;
10204
10567
  }
10205
10568
  function resolveManagedPostStartPlan(repoPath) {
10206
- const adapterPath = import_node_path34.default.join(repoPath, MANAGED_ADAPTER_PATH);
10569
+ const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
10207
10570
  const adapterBytes = readRegularFileBytes(adapterPath);
10208
10571
  const adapterText = adapterBytes?.toString("utf-8");
10209
- const dockerfilePath = import_node_path34.default.join(repoPath, ".devcontainer", "Dockerfile");
10210
- const devcontainerPath = import_node_path34.default.join(repoPath, ".devcontainer", "devcontainer.json");
10572
+ const dockerfilePath = import_node_path35.default.join(repoPath, ".devcontainer", "Dockerfile");
10573
+ const devcontainerPath = import_node_path35.default.join(repoPath, ".devcontainer", "devcontainer.json");
10211
10574
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
10212
10575
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
10213
10576
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -10248,7 +10611,7 @@ function resolveManagedPostStartPlan(repoPath) {
10248
10611
  }
10249
10612
  function runManagedPostStart(options) {
10250
10613
  if (options.plan.kind !== "runtime") return;
10251
- const helper = import_node_fs29.default.readFileSync(resolveProcessHelperPath());
10614
+ const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
10252
10615
  deliverRuntimeFile(
10253
10616
  options.container.id,
10254
10617
  RUNTIME_HELPER_PATH,
@@ -10262,7 +10625,7 @@ function runManagedPostStart(options) {
10262
10625
  options.plan.adapterContents,
10263
10626
  "the managed post-start adapter"
10264
10627
  );
10265
- const started2 = (0, import_node_child_process21.spawnSync)(
10628
+ const started2 = (0, import_node_child_process22.spawnSync)(
10266
10629
  "docker",
10267
10630
  [
10268
10631
  "exec",
@@ -10303,7 +10666,7 @@ function assertSafeProcessName(name) {
10303
10666
  }
10304
10667
  function runManagedProcessAction(options) {
10305
10668
  assertSafeProcessName(options.name);
10306
- const result = (0, import_node_child_process21.spawnSync)(
10669
+ const result = (0, import_node_child_process22.spawnSync)(
10307
10670
  "docker",
10308
10671
  [
10309
10672
  "exec",
@@ -10768,7 +11131,7 @@ function collectManagedRuntimeStatus(options) {
10768
11131
  }
10769
11132
 
10770
11133
  // src/core/network-inventory.ts
10771
- var import_node_child_process22 = require("child_process");
11134
+ var import_node_child_process23 = require("child_process");
10772
11135
  var NetworkInventoryError = class extends Error {
10773
11136
  };
10774
11137
  var MAX_IDS = 4096;
@@ -10780,7 +11143,7 @@ function readDocker(endpoint, args, timeoutMs) {
10780
11143
  const env = { ...process.env };
10781
11144
  delete env.DOCKER_CONTEXT;
10782
11145
  delete env.DOCKER_HOST;
10783
- const result = (0, import_node_child_process22.spawnSync)("docker", ["--host", endpoint, ...args], {
11146
+ const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
10784
11147
  env,
10785
11148
  encoding: "utf-8",
10786
11149
  timeout: timeoutMs,
@@ -10937,37 +11300,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
10937
11300
  }
10938
11301
 
10939
11302
  // src/core/network-routes.ts
10940
- var import_node_child_process23 = require("child_process");
11303
+ var import_node_child_process24 = require("child_process");
10941
11304
  var import_node_net3 = require("net");
10942
11305
 
10943
11306
  // src/core/network-local-daemon.ts
10944
- var import_node_fs30 = __toESM(require("fs"));
10945
- var import_node_path35 = __toESM(require("path"));
11307
+ var import_node_fs31 = __toESM(require("fs"));
11308
+ var import_node_path36 = __toESM(require("path"));
10946
11309
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
10947
11310
  const deadline = Date.now() + 2e3;
10948
11311
  try {
10949
11312
  if (!endpoint.startsWith("unix:///")) return false;
10950
11313
  const socket = endpoint.slice("unix://".length);
10951
- const table = import_node_fs30.default.readFileSync(import_node_path35.default.join(procRoot, "net/unix"), "utf8");
11314
+ const table = import_node_fs31.default.readFileSync(import_node_path36.default.join(procRoot, "net/unix"), "utf8");
10952
11315
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
10953
11316
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
10954
11317
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
10955
11318
  const inode = `socket:[${rows2[0][6]}]`;
10956
- const ownNamespace = import_node_fs30.default.readlinkSync(import_node_path35.default.join(procRoot, "self/ns/net"));
10957
- const pids = import_node_fs30.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
11319
+ const ownNamespace = import_node_fs31.default.readlinkSync(import_node_path36.default.join(procRoot, "self/ns/net"));
11320
+ const pids = import_node_fs31.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
10958
11321
  if (pids.length > 4096) return false;
10959
11322
  for (const pid of pids) {
10960
11323
  if (Date.now() > deadline) return false;
10961
11324
  try {
10962
- const root = import_node_path35.default.join(procRoot, pid);
10963
- if (import_node_path35.default.basename(import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "exe"))) !== "dockerd") continue;
10964
- if (import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "ns/net")) !== ownNamespace) continue;
10965
- const descriptors = import_node_fs30.default.readdirSync(import_node_path35.default.join(root, "fd"));
11325
+ const root = import_node_path36.default.join(procRoot, pid);
11326
+ if (import_node_path36.default.basename(import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "exe"))) !== "dockerd") continue;
11327
+ if (import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "ns/net")) !== ownNamespace) continue;
11328
+ const descriptors = import_node_fs31.default.readdirSync(import_node_path36.default.join(root, "fd"));
10966
11329
  if (descriptors.length > 4096) continue;
10967
11330
  if (descriptors.some((fd) => {
10968
11331
  if (Date.now() > deadline) return false;
10969
11332
  try {
10970
- return import_node_fs30.default.readlinkSync(import_node_path35.default.join(root, "fd", fd)) === inode;
11333
+ return import_node_fs31.default.readlinkSync(import_node_path36.default.join(root, "fd", fd)) === inode;
10971
11334
  } catch {
10972
11335
  return false;
10973
11336
  }
@@ -11006,7 +11369,7 @@ function parseLinuxNetworkRoutes(output) {
11006
11369
  function collectNetworkRoutes(options) {
11007
11370
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
11008
11371
  return { status: "unknown", routes: [] };
11009
- const result = (0, import_node_child_process23.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
11372
+ const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
11010
11373
  encoding: "utf8",
11011
11374
  timeout: 5e3,
11012
11375
  maxBuffer: 1024 * 1024
@@ -11047,7 +11410,7 @@ var import_node_crypto19 = require("crypto");
11047
11410
  // src/core/network-compose.ts
11048
11411
  var import_node_crypto17 = require("crypto");
11049
11412
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
11050
- function isRecord4(value) {
11413
+ function isRecord5(value) {
11051
11414
  return value !== null && typeof value === "object" && !Array.isArray(value);
11052
11415
  }
11053
11416
  function hasOwn(record5, key) {
@@ -11065,13 +11428,13 @@ function sortedUnique2(values) {
11065
11428
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
11066
11429
  }
11067
11430
  function readMap(value) {
11068
- return isRecord4(value) ? value : void 0;
11431
+ return isRecord5(value) ? value : void 0;
11069
11432
  }
11070
11433
  function readMode(input) {
11071
11434
  if (input.mode !== void 0) {
11072
11435
  return input.mode;
11073
11436
  }
11074
- if (!isRecord4(input.metadata)) {
11437
+ if (!isRecord5(input.metadata)) {
11075
11438
  return void 0;
11076
11439
  }
11077
11440
  for (const key of ["mode", "runtimeMode", "checkoutMode"]) {
@@ -11088,7 +11451,7 @@ function readExternal(value) {
11088
11451
  if (typeof value === "boolean") {
11089
11452
  return { value, known: true };
11090
11453
  }
11091
- if (isRecord4(value)) {
11454
+ if (isRecord5(value)) {
11092
11455
  return { value: true, known: true };
11093
11456
  }
11094
11457
  return { known: false };
@@ -11103,7 +11466,7 @@ function readDriver(value) {
11103
11466
  return { value: value.toLowerCase(), known: true };
11104
11467
  }
11105
11468
  function inspectNetworks(input, reasons) {
11106
- const compose = isRecord4(input.compose) ? input.compose : {};
11469
+ const compose = isRecord5(input.compose) ? input.compose : {};
11107
11470
  const normalizedValue2 = compose.networks;
11108
11471
  const authoredValue2 = input.authoredNetworks;
11109
11472
  const normalized = normalizedValue2 === void 0 ? {} : readMap(normalizedValue2);
@@ -11132,7 +11495,7 @@ function inspectNetworks(input, reasons) {
11132
11495
  );
11133
11496
  } else {
11134
11497
  for (const [serviceName, rawService] of Object.entries(services)) {
11135
- if (!isRecord4(rawService)) {
11498
+ if (!isRecord5(rawService)) {
11136
11499
  addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
11137
11500
  continue;
11138
11501
  }
@@ -11177,7 +11540,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
11177
11540
  }
11178
11541
  return { names: sortedUnique2(names), known: true };
11179
11542
  }
11180
- if (!isRecord4(value)) {
11543
+ if (!isRecord5(value)) {
11181
11544
  addReason(
11182
11545
  reasons,
11183
11546
  reason(
@@ -11197,7 +11560,7 @@ function parseServiceNetworks(value, serviceName, reasons) {
11197
11560
  );
11198
11561
  continue;
11199
11562
  }
11200
- if (attachment !== null && !isRecord4(attachment)) {
11563
+ if (attachment !== null && !isRecord5(attachment)) {
11201
11564
  known = false;
11202
11565
  addReason(
11203
11566
  reasons,
@@ -11212,27 +11575,27 @@ function parseServiceNetworks(value, serviceName, reasons) {
11212
11575
  }
11213
11576
  function authoredHas(authored, name, key) {
11214
11577
  const declaration = authored[name];
11215
- return isRecord4(declaration) && hasOwn(declaration, key);
11578
+ return isRecord5(declaration) && hasOwn(declaration, key);
11216
11579
  }
11217
11580
  function authoredValue(authored, name, key) {
11218
11581
  const declaration = authored[name];
11219
- return isRecord4(declaration) ? declaration[key] : void 0;
11582
+ return isRecord5(declaration) ? declaration[key] : void 0;
11220
11583
  }
11221
11584
  function normalizedValue(normalized, name, key) {
11222
11585
  const declaration = normalized[name];
11223
- return isRecord4(declaration) ? declaration[key] : void 0;
11586
+ return isRecord5(declaration) ? declaration[key] : void 0;
11224
11587
  }
11225
11588
  function classifyNetwork(name, inspection, reasons) {
11226
11589
  const authoredDeclaration = inspection.authored[name];
11227
11590
  const normalizedDeclaration = inspection.normalized[name];
11228
- if (authoredDeclaration !== void 0 && !isRecord4(authoredDeclaration)) {
11591
+ if (authoredDeclaration !== void 0 && !isRecord5(authoredDeclaration)) {
11229
11592
  addReason(
11230
11593
  reasons,
11231
11594
  reason("malformed-authored-network", `Authored network ${name} is not an object.`)
11232
11595
  );
11233
11596
  return { external: false, private: false };
11234
11597
  }
11235
- if (normalizedDeclaration !== void 0 && !isRecord4(normalizedDeclaration)) {
11598
+ if (normalizedDeclaration !== void 0 && !isRecord5(normalizedDeclaration)) {
11236
11599
  addReason(
11237
11600
  reasons,
11238
11601
  reason("malformed-normalized-network", `Normalized network ${name} is not an object.`)
@@ -11313,7 +11676,7 @@ function classifyNetwork(name, inspection, reasons) {
11313
11676
  } else {
11314
11677
  const normalizedIpam = normalizedValue(inspection.normalized, name, "ipam");
11315
11678
  if (normalizedIpam !== void 0) {
11316
- if (!isRecord4(normalizedIpam)) {
11679
+ if (!isRecord5(normalizedIpam)) {
11317
11680
  addReason(
11318
11681
  reasons,
11319
11682
  reason("malformed-normalized-ipam", `Network ${name} has malformed normalized IPAM.`)
@@ -11415,7 +11778,7 @@ function parseCount(value, field, reasons) {
11415
11778
  if (value === void 0) {
11416
11779
  return { value: 0, known: true };
11417
11780
  }
11418
- const candidate = isRecord4(value) && hasOwn(value, "count") ? value.count : value;
11781
+ const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
11419
11782
  if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 0) {
11420
11783
  addReason(reasons, reason("malformed-count", `${field} must be a non-negative integer.`));
11421
11784
  return { value: 0, known: false };
@@ -11431,7 +11794,7 @@ function parseReplicas(service, serviceName, reasons) {
11431
11794
  candidates.push({ field: "scale", value: service.scale });
11432
11795
  }
11433
11796
  if (hasOwn(service, "deploy")) {
11434
- if (!isRecord4(service.deploy)) {
11797
+ if (!isRecord5(service.deploy)) {
11435
11798
  addReason(
11436
11799
  reasons,
11437
11800
  reason("malformed-replicas", `Service ${serviceName} deploy metadata is malformed.`)
@@ -11486,7 +11849,7 @@ function parseUpperBound(input, reasons) {
11486
11849
  const raw = input.endpointUpperBound;
11487
11850
  let value = raw;
11488
11851
  let semantics = input.endpointUpperBoundSemantics;
11489
- if (isRecord4(raw)) {
11852
+ if (isRecord5(raw)) {
11490
11853
  value = raw.value ?? raw.endpoints ?? raw.count;
11491
11854
  semantics = raw.semantics ?? semantics;
11492
11855
  }
@@ -11551,7 +11914,7 @@ function deriveComposeNetworkDemand(input) {
11551
11914
  )
11552
11915
  );
11553
11916
  }
11554
- const compose = isRecord4(input.compose) ? input.compose : {};
11917
+ const compose = isRecord5(input.compose) ? input.compose : {};
11555
11918
  const services = readMap(compose.services);
11556
11919
  const serviceEndpointsByService = {};
11557
11920
  const replicasByService = {};
@@ -11569,7 +11932,7 @@ function deriveComposeNetworkDemand(input) {
11569
11932
  serviceDemandKnown = false;
11570
11933
  } else {
11571
11934
  for (const [serviceName, rawService] of Object.entries(services)) {
11572
- if (!isRecord4(rawService)) {
11935
+ if (!isRecord5(rawService)) {
11573
11936
  addReason(reasons, reason("malformed-service", `Service ${serviceName} is not an object.`));
11574
11937
  serviceDemandKnown = false;
11575
11938
  continue;
@@ -11671,7 +12034,7 @@ function deriveComposeNetworkOverlay(subnet) {
11671
12034
  };
11672
12035
  }
11673
12036
  function extendDockerComposeFile(nativeConfig, overlayPath) {
11674
- if (!isRecord4(nativeConfig)) {
12037
+ if (!isRecord5(nativeConfig)) {
11675
12038
  return {
11676
12039
  status: "unknown",
11677
12040
  reasons: [
@@ -11722,7 +12085,7 @@ function replicaProjection(service) {
11722
12085
  return replicas.known ? replicas.value : "unknown";
11723
12086
  }
11724
12087
  function redactedNetworkProjection(value) {
11725
- if (!isRecord4(value)) {
12088
+ if (!isRecord5(value)) {
11726
12089
  return value === void 0 ? void 0 : "unknown";
11727
12090
  }
11728
12091
  const projection = {};
@@ -11741,7 +12104,7 @@ function redactedNetworkProjection(value) {
11741
12104
  const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
11742
12105
  projection.ipam = {
11743
12106
  config: config === void 0 ? "unknown" : config.map(
11744
- (entry) => isRecord4(entry) && typeof entry.subnet === "string" ? entry.subnet : "unknown"
12107
+ (entry) => isRecord5(entry) && typeof entry.subnet === "string" ? entry.subnet : "unknown"
11745
12108
  )
11746
12109
  };
11747
12110
  } else if (key === "enable_ipv6") {
@@ -11757,7 +12120,7 @@ function redactedCountProjection(value) {
11757
12120
  if (value === void 0) {
11758
12121
  return 0;
11759
12122
  }
11760
- const candidate = isRecord4(value) && hasOwn(value, "count") ? value.count : value;
12123
+ const candidate = isRecord5(value) && hasOwn(value, "count") ? value.count : value;
11761
12124
  return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : "unknown";
11762
12125
  }
11763
12126
  function redactedUpperBoundProjection(value) {
@@ -11767,7 +12130,7 @@ function redactedUpperBoundProjection(value) {
11767
12130
  if (typeof value === "number") {
11768
12131
  return Number.isSafeInteger(value) && value >= 0 ? value : "unknown";
11769
12132
  }
11770
- if (!isRecord4(value)) {
12133
+ if (!isRecord5(value)) {
11771
12134
  return "unknown";
11772
12135
  }
11773
12136
  const candidate = value.value ?? value.endpoints ?? value.count;
@@ -11777,12 +12140,12 @@ function redactedUpperBoundProjection(value) {
11777
12140
  };
11778
12141
  }
11779
12142
  function normalizedFingerprintProjection(input) {
11780
- const compose = isRecord4(input.compose) ? input.compose : {};
12143
+ const compose = isRecord5(input.compose) ? input.compose : {};
11781
12144
  const services = readMap(compose.services);
11782
12145
  const serviceProjection = {};
11783
12146
  for (const serviceName of Object.keys(services ?? {}).sort()) {
11784
12147
  const service = services?.[serviceName];
11785
- serviceProjection[serviceName] = isRecord4(service) ? {
12148
+ serviceProjection[serviceName] = isRecord5(service) ? {
11786
12149
  networks: projectionNetworkMembership(service.networks),
11787
12150
  replicas: replicaProjection(service)
11788
12151
  } : "unknown";
@@ -11814,14 +12177,14 @@ function normalizedFingerprintProjection(input) {
11814
12177
  };
11815
12178
  }
11816
12179
  function redactedOverlayProjection(value) {
11817
- if (!isRecord4(value)) {
12180
+ if (!isRecord5(value)) {
11818
12181
  return value === void 0 ? void 0 : "unknown";
11819
12182
  }
11820
12183
  const networks = readMap(value.networks);
11821
12184
  const defaultNetwork = readMap(networks?.default);
11822
12185
  const ipam = readMap(defaultNetwork?.ipam);
11823
12186
  const config = Array.isArray(ipam?.config) ? ipam.config : void 0;
11824
- if (config === void 0 || config.some((entry) => !isRecord4(entry) || typeof entry.subnet !== "string")) {
12187
+ if (config === void 0 || config.some((entry) => !isRecord5(entry) || typeof entry.subnet !== "string")) {
11825
12188
  return "unknown";
11826
12189
  }
11827
12190
  return {
@@ -11844,7 +12207,7 @@ function stableValue(value) {
11844
12207
  if (Array.isArray(value)) {
11845
12208
  return `[${value.map((item) => stableValue(item)).join(",")}]`;
11846
12209
  }
11847
- if (isRecord4(value)) {
12210
+ if (isRecord5(value)) {
11848
12211
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableValue(value[key])}`).join(",")}}`;
11849
12212
  }
11850
12213
  return JSON.stringify(String(value));
@@ -11855,10 +12218,10 @@ function fingerprintComposeNetworkModel(input) {
11855
12218
  }
11856
12219
 
11857
12220
  // src/core/network-compose-files.ts
11858
- var import_node_child_process24 = require("child_process");
12221
+ var import_node_child_process25 = require("child_process");
11859
12222
  var import_node_crypto18 = require("crypto");
11860
- var import_node_fs31 = __toESM(require("fs"));
11861
- var import_node_path36 = __toESM(require("path"));
12223
+ var import_node_fs32 = __toESM(require("fs"));
12224
+ var import_node_path37 = __toESM(require("path"));
11862
12225
  var import_yaml4 = __toESM(require("yaml"));
11863
12226
  var OVERLAY = "docker-compose.devrouter-network.yml";
11864
12227
  var MARKER = "# devrouter:managed workspace network";
@@ -11871,12 +12234,12 @@ function object3(value) {
11871
12234
  function inspectNetworkComposeFiles(input) {
11872
12235
  const authoredNetworks = {};
11873
12236
  for (const file of input.plan.composeFiles) {
11874
- const stat = import_node_fs31.default.lstatSync(file);
12237
+ const stat = import_node_fs32.default.lstatSync(file);
11875
12238
  if (!stat.isFile() || stat.size > MAX_BYTES2)
11876
12239
  throw new Error("Network Compose source is unavailable or too large.");
11877
12240
  let source;
11878
12241
  try {
11879
- source = object3(import_yaml4.default.parse(import_node_fs31.default.readFileSync(file, "utf8")));
12242
+ source = object3(import_yaml4.default.parse(import_node_fs32.default.readFileSync(file, "utf8")));
11880
12243
  } catch {
11881
12244
  throw new Error("Network Compose source could not be parsed safely.");
11882
12245
  }
@@ -11892,7 +12255,7 @@ function inspectNetworkComposeFiles(input) {
11892
12255
  const env = managedComposeEnvironment(input.workspace);
11893
12256
  delete env.DOCKER_CONTEXT;
11894
12257
  delete env.DOCKER_HOST;
11895
- const result = (0, import_node_child_process24.spawnSync)(
12258
+ const result = (0, import_node_child_process25.spawnSync)(
11896
12259
  "docker",
11897
12260
  [
11898
12261
  "--host",
@@ -11926,8 +12289,8 @@ function inspectNetworkComposeFiles(input) {
11926
12289
  function prepareNetworkComposeFiles(plan, subnet) {
11927
12290
  const overlay = deriveComposeNetworkOverlay(subnet);
11928
12291
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
11929
- const overlayPath = import_node_path36.default.join(plan.composeDirectory, OVERLAY);
11930
- const ignored = (0, import_node_child_process24.spawnSync)("git", [
12292
+ const overlayPath = import_node_path37.default.join(plan.composeDirectory, OVERLAY);
12293
+ const ignored = (0, import_node_child_process25.spawnSync)("git", [
11931
12294
  "-C",
11932
12295
  plan.composeDirectory,
11933
12296
  "check-ignore",
@@ -11941,9 +12304,9 @@ function prepareNetworkComposeFiles(plan, subnet) {
11941
12304
  const bytes2 = `${MARKER}
11942
12305
  ${import_yaml4.default.stringify(overlay.overlay)}`;
11943
12306
  const assertOwnership = () => {
11944
- if (!import_node_fs31.default.existsSync(overlayPath)) return;
11945
- const stat = import_node_fs31.default.lstatSync(overlayPath);
11946
- if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs31.default.readFileSync(overlayPath, "utf8") !== bytes2)
12307
+ if (!import_node_fs32.default.existsSync(overlayPath)) return;
12308
+ const stat = import_node_fs32.default.lstatSync(overlayPath);
12309
+ if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs32.default.readFileSync(overlayPath, "utf8") !== bytes2)
11947
12310
  throw new Error("Retained network overlay changed; repair is required.");
11948
12311
  };
11949
12312
  assertOwnership();
@@ -11969,7 +12332,7 @@ ${JSON.stringify(extended.config, null, 2)}
11969
12332
  }
11970
12333
 
11971
12334
  // src/core/network-connected-route.ts
11972
- var import_node_child_process25 = require("child_process");
12335
+ var import_node_child_process26 = require("child_process");
11973
12336
  function bounds(cidr) {
11974
12337
  const canonical = parseIPv4Cidr(cidr);
11975
12338
  if (!canonical) throw new Error("Invalid connected route.");
@@ -11994,7 +12357,7 @@ function inspectClaimedBridge(claim2, networkId) {
11994
12357
  const env = { ...process.env };
11995
12358
  delete env.DOCKER_CONTEXT;
11996
12359
  delete env.DOCKER_HOST;
11997
- const result = (0, import_node_child_process25.spawnSync)(
12360
+ const result = (0, import_node_child_process26.spawnSync)(
11998
12361
  "docker",
11999
12362
  [
12000
12363
  "--host",
@@ -12021,7 +12384,7 @@ function inspectClaimedBridge(claim2, networkId) {
12021
12384
  const [first, last] = bounds(claim2.subnet);
12022
12385
  const [address] = bounds(gateway);
12023
12386
  if (address <= first || address >= last) throw new Error();
12024
- const interfaces = (0, import_node_child_process25.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
12387
+ const interfaces = (0, import_node_child_process26.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
12025
12388
  encoding: "utf8",
12026
12389
  timeout: 5e3,
12027
12390
  maxBuffer: 65536
@@ -12459,11 +12822,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
12459
12822
  var POLL_INTERVAL_MS = 1e3;
12460
12823
  function assertOverlay(container, repoPath) {
12461
12824
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
12462
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))) {
12825
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))) {
12463
12826
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
12464
12827
  }
12465
12828
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
12466
- const expectedOverlay = import_node_path37.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
12829
+ const expectedOverlay = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
12467
12830
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
12468
12831
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
12469
12832
  }
@@ -12658,7 +13021,7 @@ function hasExactManagedComposeProject(repoPath, state) {
12658
13021
  }).some((container) => {
12659
13022
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
12660
13023
  return Boolean(
12661
- workingDir && sameWorkspacePath(workingDir, import_node_path37.default.join(repoPath, ".devcontainer"))
13024
+ workingDir && sameWorkspacePath(workingDir, import_node_path38.default.join(repoPath, ".devcontainer"))
12662
13025
  );
12663
13026
  });
12664
13027
  } catch {
@@ -12766,7 +13129,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
12766
13129
  target
12767
13130
  });
12768
13131
  if (target.kind === "linked") {
12769
- const workspaceEnv = (0, import_node_child_process26.spawnSync)(
13132
+ const workspaceEnv = (0, import_node_child_process27.spawnSync)(
12770
13133
  "docker",
12771
13134
  ["exec", appContainer.id, "printenv", "WORKSPACE"],
12772
13135
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -12776,7 +13139,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
12776
13139
  `Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
12777
13140
  );
12778
13141
  }
12779
- const devrouterWorkspaceEnv = (0, import_node_child_process26.spawnSync)(
13142
+ const devrouterWorkspaceEnv = (0, import_node_child_process27.spawnSync)(
12780
13143
  "docker",
12781
13144
  ["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
12782
13145
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -12787,7 +13150,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
12787
13150
  );
12788
13151
  }
12789
13152
  }
12790
- const gitCheck = (0, import_node_child_process26.spawnSync)(
13153
+ const gitCheck = (0, import_node_child_process27.spawnSync)(
12791
13154
  "docker",
12792
13155
  [
12793
13156
  "exec",
@@ -13042,14 +13405,14 @@ function resolvePrimaryTarget(repoPath) {
13042
13405
  }
13043
13406
  function isPrimaryCheckout(repoPath) {
13044
13407
  try {
13045
- return import_node_fs32.default.statSync(import_node_path37.default.join(repoPath, ".git")).isDirectory();
13408
+ return import_node_fs33.default.statSync(import_node_path38.default.join(repoPath, ".git")).isDirectory();
13046
13409
  } catch {
13047
13410
  return false;
13048
13411
  }
13049
13412
  }
13050
13413
  function openUrls(urls) {
13051
13414
  for (const url of urls) {
13052
- const opened = (0, import_node_child_process26.spawnSync)("open", [url], { encoding: "utf-8" });
13415
+ const opened = (0, import_node_child_process27.spawnSync)("open", [url], { encoding: "utf-8" });
13053
13416
  if (opened.status !== 0) {
13054
13417
  process.stderr.write(`Warning: could not open '${url}'.
13055
13418
  `);
@@ -13105,8 +13468,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
13105
13468
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
13106
13469
  let devpodId = target.devpodId;
13107
13470
  if (target.kind === "linked") {
13108
- const overlayPath = import_node_path37.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
13109
- if (!import_node_fs32.default.existsSync(overlayPath)) {
13471
+ const overlayPath = import_node_path38.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
13472
+ if (!import_node_fs33.default.existsSync(overlayPath)) {
13110
13473
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
13111
13474
  }
13112
13475
  }
@@ -13174,9 +13537,38 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
13174
13537
  });
13175
13538
  }
13176
13539
  }
13540
+ if (managedPlan) {
13541
+ let hostPortConflicts = [];
13542
+ try {
13543
+ hostPortConflicts = detectHostPortClaimConflicts({
13544
+ repoPath,
13545
+ plan: managedPlan,
13546
+ workspace: managedWorkspaceEnv
13547
+ });
13548
+ } catch (error) {
13549
+ throw new Error(
13550
+ `Managed admission could not verify fixed host-port claims before start: ${error instanceof Error ? error.message : String(error)}`
13551
+ );
13552
+ }
13553
+ if (hostPortConflicts.length > 0) {
13554
+ return {
13555
+ kind: target.kind,
13556
+ repoPath,
13557
+ ...target.kind === "linked" ? { workspace: target.workspace } : {},
13558
+ profile: runtime.profile,
13559
+ // A refusal precedes the provider start, so the exact id may not
13560
+ // be known yet on a primary checkout.
13561
+ devpodId: devpodId ?? "(absent)",
13562
+ urls: [],
13563
+ recreated: false,
13564
+ tlsRefreshed: false,
13565
+ hostPortConflicts
13566
+ };
13567
+ }
13568
+ }
13177
13569
  const apps = proxyAppsFromConfig(runtime.config);
13178
13570
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
13179
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path37.default.basename(repoPath)) ?? "app";
13571
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path38.default.basename(repoPath)) ?? "app";
13180
13572
  for (const [index, app] of apps.entries()) {
13181
13573
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
13182
13574
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -13291,7 +13683,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
13291
13683
  repairMutationStarted = true;
13292
13684
  transitionPhase = "service-start";
13293
13685
  claimLifecycleEffect();
13294
- const result = (0, import_node_child_process26.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
13686
+ const result = (0, import_node_child_process27.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
13295
13687
  ...networkDockerOptions(),
13296
13688
  encoding: "utf-8",
13297
13689
  timeout: DEFAULT_READINESS_TIMEOUT_MS,
@@ -13996,9 +14388,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
13996
14388
  }
13997
14389
  const repairLocked = () => {
13998
14390
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
13999
- import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
14391
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
14000
14392
  return withFileLock(
14001
- import_node_path37.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
14393
+ import_node_path38.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
14002
14394
  {
14003
14395
  activity: "Managed runtime repair",
14004
14396
  target: repoPath,
@@ -14205,7 +14597,7 @@ async function executeLifecycleWorker(request, operation) {
14205
14597
  const owned = containers.filter(
14206
14598
  (container) => sameWorkspacePath(
14207
14599
  container.labels["com.docker.compose.project.working_dir"] ?? "",
14208
- import_node_path38.default.join(request.repoPath, ".devcontainer")
14600
+ import_node_path39.default.join(request.repoPath, ".devcontainer")
14209
14601
  )
14210
14602
  );
14211
14603
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -14306,7 +14698,7 @@ function proveLifecycleStopped() {
14306
14698
  const remaining = inspectWorkspaceContainers().filter(
14307
14699
  (container) => sameWorkspacePath(
14308
14700
  container.labels["com.docker.compose.project.working_dir"] ?? "",
14309
- import_node_path38.default.join(request.repoPath, ".devcontainer")
14701
+ import_node_path39.default.join(request.repoPath, ".devcontainer")
14310
14702
  )
14311
14703
  );
14312
14704
  if (remaining.some(
@@ -14335,7 +14727,7 @@ function proveLifecycleStopped() {
14335
14727
  return void 0;
14336
14728
  });
14337
14729
  if (settlement) {
14338
- const capacity = new CapacityStore(import_node_path38.default.join(DEVROUTER_HOME, "controller"));
14730
+ const capacity = new CapacityStore(import_node_path39.default.join(DEVROUTER_HOME, "controller"));
14339
14731
  for (let attempt = 0; ; attempt++) {
14340
14732
  try {
14341
14733
  capacity.settleEnvironmentAfterStop(settlement, capacity.read().revision);
@@ -14401,7 +14793,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
14401
14793
  const launch = () => {
14402
14794
  if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
14403
14795
  claimLifecycleEffect();
14404
- return (0, import_node_child_process27.spawn)("devsy", args, { stdio: "inherit" });
14796
+ return (0, import_node_child_process28.spawn)("devsy", args, { stdio: "inherit" });
14405
14797
  };
14406
14798
  child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
14407
14799
  } catch (error) {
@@ -14449,7 +14841,7 @@ function ensureGuidance2(repoPath) {
14449
14841
  return `Run 'devrouter ensure ${repoPath}' first.`;
14450
14842
  }
14451
14843
  function assertRunningDevpod(devpodId, repoPath) {
14452
- const result = (0, import_node_child_process28.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
14844
+ const result = (0, import_node_child_process29.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
14453
14845
  encoding: "utf-8"
14454
14846
  });
14455
14847
  if (result.status !== 0) {
@@ -14628,7 +15020,7 @@ async function devpodExecOutcome(repoPath, command) {
14628
15020
  let child;
14629
15021
  try {
14630
15022
  claimLifecycleEffect();
14631
- child = (0, import_node_child_process28.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
15023
+ child = (0, import_node_child_process29.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
14632
15024
  } catch (error) {
14633
15025
  settled = true;
14634
15026
  reject(
@@ -14685,12 +15077,12 @@ async function devpodExecOutcome(repoPath, command) {
14685
15077
  }
14686
15078
 
14687
15079
  // src/core/workspace-lifecycle.ts
14688
- var import_node_child_process29 = require("child_process");
14689
- var import_node_fs34 = __toESM(require("fs"));
14690
- var import_node_path39 = __toESM(require("path"));
15080
+ var import_node_child_process30 = require("child_process");
15081
+ var import_node_fs35 = __toESM(require("fs"));
15082
+ var import_node_path40 = __toESM(require("path"));
14691
15083
 
14692
15084
  // src/core/route-state.ts
14693
- var import_node_fs33 = __toESM(require("fs"));
15085
+ var import_node_fs34 = __toESM(require("fs"));
14694
15086
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
14695
15087
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
14696
15088
  }
@@ -14900,7 +15292,7 @@ process.once("message", async (message) => {
14900
15292
  }
14901
15293
  const ensured = await workspaceEnsure(request.repoPath, request.options);
14902
15294
  recordLifecycleCompletion(
14903
- ensured.applicationReadiness?.status === "application-error" ? 1 : 0,
15295
+ ensured.applicationReadiness?.status === "application-error" || (ensured.hostPortConflicts?.length ?? 0) > 0 ? 1 : 0,
14904
15296
  ensured.managedRuntime?.status === "ready" ? ensured.profile : void 0,
14905
15297
  { ok: true, value: ensured }
14906
15298
  );