@devrouter/cli 0.0.73 → 0.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/devrouter.js CHANGED
@@ -1177,6 +1177,55 @@ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
1177
1177
  assertUniqueContainerIds(ids2);
1178
1178
  return ids2;
1179
1179
  }
1180
+ function assertManagedStopCheckoutAbsent(endpoint, repoPath) {
1181
+ const list2 = () => {
1182
+ const ids3 = parseDockerLines(
1183
+ runManagedStopDocker(["ps", "-a", "--no-trunc", "--format", "{{.ID}}"], endpoint)
1184
+ );
1185
+ if (ids3.length > 256) throw new Error("Managed stop population exceeds its bound.");
1186
+ ids3.forEach(assertFullContainerId);
1187
+ assertUniqueContainerIds(ids3);
1188
+ return ids3;
1189
+ };
1190
+ const ids2 = list2();
1191
+ const seen = [];
1192
+ const labels = [
1193
+ "com.docker.compose.project.working_dir",
1194
+ "devcontainer.local_folder",
1195
+ "vsch.local.folder"
1196
+ ];
1197
+ const template = '{"id":{{json .Id}},"labels":{"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"devcontainer.local_folder":{{json (index .Config.Labels "devcontainer.local_folder")}},"vsch.local.folder":{{json (index .Config.Labels "vsch.local.folder")}}},"mounts":[{{range $i, $m := .Mounts}}{{if $i}},{{end}}{"Type":{{json $m.Type}},"Source":{{json $m.Source}}}{{end}}]}';
1198
+ if (ids2.length) {
1199
+ for (const line of parseDockerLines(
1200
+ runManagedStopDocker(["inspect", "--format", template, ...ids2], endpoint)
1201
+ )) {
1202
+ const value = parseManagedStopJson(line);
1203
+ if (!isRecord(value) || !isRecord(value.labels) || !Array.isArray(value.mounts))
1204
+ throw new Error("Managed stop checkout inspection is malformed.");
1205
+ assertFullContainerId(value.id);
1206
+ seen.push(value.id);
1207
+ for (const label2 of labels) {
1208
+ const field = value.labels[label2];
1209
+ if (field !== null && typeof field !== "string")
1210
+ throw new Error("Managed stop checkout labels are incomplete.");
1211
+ if (typeof field === "string" && sameWorkspacePath(
1212
+ field,
1213
+ label2 === labels[0] ? import_node_path4.default.join(repoPath, ".devcontainer") : repoPath
1214
+ ))
1215
+ throw new Error("Managed stop observed a remaining checkout container.");
1216
+ }
1217
+ for (const mount of value.mounts) {
1218
+ if (!isRecord(mount) || typeof mount.Type !== "string" || typeof mount.Source !== "string")
1219
+ throw new Error("Managed stop checkout mounts are incomplete.");
1220
+ if (mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath))
1221
+ throw new Error("Managed stop observed a remaining checkout mount.");
1222
+ }
1223
+ }
1224
+ }
1225
+ assertUniqueContainerIds(seen);
1226
+ requireSameContainerPopulation(ids2, seen);
1227
+ requireSameContainerPopulation(ids2, list2());
1228
+ }
1180
1229
  function hasExactManagedStopMissingObjectError(stderr, id5) {
1181
1230
  if (typeof stderr !== "string") return false;
1182
1231
  const line = stderr.endsWith("\r\n") ? stderr.slice(0, -2) : stderr.endsWith("\n") ? stderr.slice(0, -1) : stderr;
@@ -4207,7 +4256,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4207
4256
  const config = parseConfig(parsed ?? {}, configPath);
4208
4257
  const requiredVersion = config.devrouter?.version;
4209
4258
  if (requiredVersion && !hasWarnedVersionMismatch) {
4210
- const cliVersion = true ? "0.0.73" : "0.0.0-dev";
4259
+ const cliVersion = true ? "0.0.75" : "0.0.0-dev";
4211
4260
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4212
4261
  hasWarnedVersionMismatch = true;
4213
4262
  process.stderr.write(
@@ -5460,7 +5509,7 @@ var init_reliability_contract = __esm({
5460
5509
 
5461
5510
  // src/core/reliability-operation-store.ts
5462
5511
  function installedCliVersion() {
5463
- return true ? "0.0.73" : "0.0.0-dev";
5512
+ return true ? "0.0.75" : "0.0.0-dev";
5464
5513
  }
5465
5514
  function compareCliVersions(left, right) {
5466
5515
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7671,10 +7720,11 @@ function inspectDevsyRuntimeAbsence(devsyId) {
7671
7720
  encoding: "utf-8",
7672
7721
  timeout: 1e4
7673
7722
  });
7674
- if (!result.error && result.status === 0) {
7723
+ if (result.error || result.status === null) return false;
7724
+ if (result.status === 0) {
7675
7725
  return parseDevsyRuntimeStatus(result.stdout, devsyId) === "not-found";
7676
7726
  }
7677
- const output2 = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n");
7727
+ const output2 = [result.stdout, result.stderr].filter(Boolean).join("\n");
7678
7728
  return /workspace not found/i.test(output2);
7679
7729
  }
7680
7730
  function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
@@ -8363,6 +8413,12 @@ function isWildcard(values) {
8363
8413
  function sha256(contents) {
8364
8414
  return (0, import_node_crypto11.createHash)("sha256").update(contents, "utf-8").digest("hex");
8365
8415
  }
8416
+ function safeComposeProject(value) {
8417
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
8418
+ throw new Error(`Docker Compose project '${value}' is not a safe exact identifier.`);
8419
+ }
8420
+ return value;
8421
+ }
8366
8422
  function inspectManagedDevcontainerConfig(options) {
8367
8423
  const managedRuntime = options.config.managedRuntime;
8368
8424
  if (!managedRuntime) {
@@ -8481,6 +8537,91 @@ function managedComposeEnvironment(workspace) {
8481
8537
  }
8482
8538
  return env;
8483
8539
  }
8540
+ function assertReadableComposeFile(file) {
8541
+ try {
8542
+ if (!import_node_fs18.default.statSync(file).isFile()) throw new Error("not a regular file");
8543
+ import_node_fs18.default.accessSync(file, import_node_fs18.default.constants.R_OK);
8544
+ } catch {
8545
+ throw new Error("Managed container Compose configuration contains an unreadable file.");
8546
+ }
8547
+ }
8548
+ function assertManagedContainerConfigUnchanged(options) {
8549
+ if (options.containers.length === 0) {
8550
+ throw new Error("Managed container configuration cannot be verified without containers.");
8551
+ }
8552
+ for (const container of options.containers) {
8553
+ const project = container.labels["com.docker.compose.project"];
8554
+ const service = container.labels["com.docker.compose.service"];
8555
+ const workingDirectory = container.labels["com.docker.compose.project.working_dir"];
8556
+ const recordedConfigFiles = container.labels["com.docker.compose.project.config_files"];
8557
+ const expectedHash = container.labels["com.docker.compose.config-hash"];
8558
+ if (!project || !service || !workingDirectory || !recordedConfigFiles || !expectedHash || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(service) || !/^[a-f0-9]{64}$/.test(expectedHash)) {
8559
+ throw new Error("Managed container is missing valid Compose identity and hash labels.");
8560
+ }
8561
+ const composeProject = safeComposeProject(project);
8562
+ const configFiles2 = recordedConfigFiles.split(",").map((file) => file.trim());
8563
+ if (configFiles2.some((file) => file.length === 0)) {
8564
+ throw new Error("Managed container has an invalid recorded Compose file list.");
8565
+ }
8566
+ if (options.plan.composeFiles.some(
8567
+ (expected) => !configFiles2.some((actual) => sameWorkspacePath(actual, expected))
8568
+ )) {
8569
+ throw new Error("Managed container Compose files do not include the managed plan files.");
8570
+ }
8571
+ for (const file of configFiles2) assertReadableComposeFile(file);
8572
+ const fileArgs = configFiles2.flatMap((file) => ["-f", file]);
8573
+ const result = (() => {
8574
+ try {
8575
+ const composeArgs = [
8576
+ "compose",
8577
+ "--project-name",
8578
+ composeProject,
8579
+ "--project-directory",
8580
+ workingDirectory
8581
+ ];
8582
+ const env = managedComposeEnvironment(options.workspace);
8583
+ const rendered = (0, import_node_child_process8.spawnSync)(
8584
+ "docker",
8585
+ [...composeArgs, ...fileArgs, "config", "--format", "json"],
8586
+ {
8587
+ cwd: workingDirectory,
8588
+ encoding: "utf-8",
8589
+ env,
8590
+ stdio: ["ignore", "pipe", "ignore"],
8591
+ timeout: COMPOSE_HASH_TIMEOUT_MS,
8592
+ maxBuffer: 1024 * 1024
8593
+ }
8594
+ );
8595
+ if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
8596
+ throw new Error("Compose configuration rendering failed.");
8597
+ }
8598
+ return (0, import_node_child_process8.spawnSync)(
8599
+ "docker",
8600
+ [...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
8601
+ {
8602
+ cwd: workingDirectory,
8603
+ encoding: "utf-8",
8604
+ env,
8605
+ input: rendered.stdout,
8606
+ stdio: ["pipe", "pipe", "ignore"],
8607
+ timeout: COMPOSE_HASH_TIMEOUT_MS,
8608
+ maxBuffer: 1024 * 1024
8609
+ }
8610
+ );
8611
+ } catch {
8612
+ throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
8613
+ }
8614
+ })();
8615
+ if (result.status !== 0 || result.error) {
8616
+ throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
8617
+ }
8618
+ const output2 = typeof result.stdout === "string" ? result.stdout.trim() : "";
8619
+ const match = /^(\S+)\s+([a-f0-9]{64})$/.exec(output2);
8620
+ if (!match || match[1] !== service || match[2] !== expectedHash) {
8621
+ throw new Error(`Managed Compose configuration changed for service '${service}'.`);
8622
+ }
8623
+ }
8624
+ }
8484
8625
  function stopExactManagedService(containerId, service, options) {
8485
8626
  assertSafeContainerId(containerId);
8486
8627
  const result = (0, import_node_child_process8.spawnSync)("docker", ["stop", containerId], {
@@ -8493,7 +8634,7 @@ function stopExactManagedService(containerId, service, options) {
8493
8634
  throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
8494
8635
  }
8495
8636
  }
8496
- var import_node_child_process8, import_node_crypto11, import_node_fs18, import_node_path25, import_yaml3, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
8637
+ var import_node_child_process8, import_node_crypto11, import_node_fs18, import_node_path25, import_yaml3, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER, COMPOSE_HASH_TIMEOUT_MS;
8497
8638
  var init_devcontainer_profile = __esm({
8498
8639
  "src/core/devcontainer-profile.ts"() {
8499
8640
  "use strict";
@@ -8509,6 +8650,7 @@ var init_devcontainer_profile = __esm({
8509
8650
  init_workspace();
8510
8651
  MANAGED_DEVCONTAINER_PATH = ".devcontainer/devcontainer.devrouter.json";
8511
8652
  MANAGED_DEVCONTAINER_MARKER = "// devrouter:managed devcontainer profile";
8653
+ COMPOSE_HASH_TIMEOUT_MS = 1e4;
8512
8654
  }
8513
8655
  });
8514
8656
 
@@ -8517,8 +8659,10 @@ function listDevpodWorkspacesRaw(options = {}) {
8517
8659
  const result = (0, import_node_child_process9.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
8518
8660
  encoding: "utf-8"
8519
8661
  });
8520
- if (options.allowMissingExecutable && result.error?.code === "ENOENT")
8521
- return [];
8662
+ if (result.error?.code === "ENOENT") {
8663
+ if (options.readLocalWhenMissing) return readLocalDevpodRegistry();
8664
+ if (options.allowMissingExecutable) return [];
8665
+ }
8522
8666
  if (result.error || result.status !== 0) {
8523
8667
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
8524
8668
  throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
@@ -8551,11 +8695,110 @@ function listDevpodWorkspacesRaw(options = {}) {
8551
8695
  return workspace;
8552
8696
  });
8553
8697
  }
8554
- var import_node_child_process9;
8698
+ function devpodRegistryRoot() {
8699
+ return import_node_path26.default.resolve(process.env.DEVPOD_HOME || import_node_path26.default.join(import_node_os3.default.homedir(), ".devpod"));
8700
+ }
8701
+ function readLocalDevpodRegistry() {
8702
+ const fail4 = () => new Error("Local DevPod registry evidence is incomplete, unreadable, or changed.");
8703
+ const stamps = /* @__PURE__ */ new Map();
8704
+ const stamp = (stat, identityOnly = false) => JSON.stringify([
8705
+ stat.dev,
8706
+ stat.ino,
8707
+ stat.mode,
8708
+ stat.uid,
8709
+ ...identityOnly ? [] : [stat.size, stat.mtimeMs, stat.ctimeMs]
8710
+ ]);
8711
+ const inspect = (file) => {
8712
+ try {
8713
+ return import_node_fs19.default.lstatSync(file);
8714
+ } catch (error) {
8715
+ if (error.code === "ENOENT") return void 0;
8716
+ throw fail4();
8717
+ }
8718
+ };
8719
+ const directory = (dir, optional = false, identityOnly = false) => {
8720
+ const stat = inspect(dir);
8721
+ if (!stat && !optional || stat && !stat.isDirectory()) throw fail4();
8722
+ stamps.set(dir, { value: stat && stamp(stat, identityOnly), identityOnly });
8723
+ return !!stat;
8724
+ };
8725
+ const entries = (dir) => {
8726
+ const names = import_node_fs19.default.readdirSync(dir).sort();
8727
+ if (names.length > 256) throw fail4();
8728
+ return names;
8729
+ };
8730
+ const result = [];
8731
+ try {
8732
+ const root = devpodRegistryRoot();
8733
+ let ancestor = import_node_path26.default.parse(root).root;
8734
+ let present = directory(ancestor, false, ancestor !== root);
8735
+ for (const part of root.slice(ancestor.length).split(import_node_path26.default.sep).filter(Boolean)) {
8736
+ if (!present) break;
8737
+ ancestor = import_node_path26.default.join(ancestor, part);
8738
+ present = directory(ancestor, true, ancestor !== root);
8739
+ }
8740
+ const contexts = import_node_path26.default.join(root, "contexts");
8741
+ if (present && directory(contexts, true)) {
8742
+ for (const context of entries(contexts)) {
8743
+ const contextDir = import_node_path26.default.join(contexts, context);
8744
+ directory(contextDir);
8745
+ const workspaces = import_node_path26.default.join(contextDir, "workspaces");
8746
+ if (!directory(workspaces, true)) continue;
8747
+ for (const id5 of entries(workspaces)) {
8748
+ if (result.length >= 256) throw fail4();
8749
+ const workspace = import_node_path26.default.join(workspaces, id5);
8750
+ directory(workspace);
8751
+ const file = import_node_path26.default.join(workspace, "workspace.json");
8752
+ const stat = inspect(file);
8753
+ if (!stat?.isFile() || stat.size > 1024 * 1024) throw fail4();
8754
+ stamps.set(file, { value: stamp(stat), identityOnly: false });
8755
+ const fd = import_node_fs19.default.openSync(file, import_node_fs19.default.constants.O_RDONLY | import_node_fs19.default.constants.O_NOFOLLOW);
8756
+ let raw;
8757
+ try {
8758
+ if (stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
8759
+ const buffer = Buffer.alloc(stat.size + 1);
8760
+ const bytes2 = import_node_fs19.default.readSync(fd, buffer, 0, buffer.length, 0);
8761
+ if (bytes2 !== stat.size || stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
8762
+ raw = JSON.parse(buffer.subarray(0, bytes2).toString("utf8"));
8763
+ } finally {
8764
+ import_node_fs19.default.closeSync(fd);
8765
+ }
8766
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw fail4();
8767
+ const value = raw;
8768
+ if (value.id !== id5 || !value.source || typeof value.source !== "object" || Array.isArray(value.source))
8769
+ throw fail4();
8770
+ const source = value.source;
8771
+ if (source.localFolder !== void 0 && (typeof source.localFolder !== "string" || source.localFolder !== "" && !import_node_path26.default.isAbsolute(source.localFolder)))
8772
+ throw fail4();
8773
+ const remote = ["gitRepository", "image", "container"];
8774
+ if (remote.some((key) => source[key] !== void 0 && typeof source[key] !== "string"))
8775
+ throw fail4();
8776
+ if (!source.localFolder && !remote.some((key) => typeof source[key] === "string" && source[key]))
8777
+ throw fail4();
8778
+ result.push({
8779
+ id: id5,
8780
+ source: { localFolder: source.localFolder ?? "" }
8781
+ });
8782
+ }
8783
+ }
8784
+ }
8785
+ for (const [file, expected] of stamps) {
8786
+ const actual = inspect(file);
8787
+ if ((actual && stamp(actual, expected.identityOnly)) !== expected.value) throw fail4();
8788
+ }
8789
+ return result;
8790
+ } catch {
8791
+ throw fail4();
8792
+ }
8793
+ }
8794
+ var import_node_child_process9, import_node_fs19, import_node_os3, import_node_path26;
8555
8795
  var init_devpod_registry = __esm({
8556
8796
  "src/core/devpod-registry.ts"() {
8557
8797
  "use strict";
8558
8798
  import_node_child_process9 = require("child_process");
8799
+ import_node_fs19 = __toESM(require("fs"));
8800
+ import_node_os3 = __toESM(require("os"));
8801
+ import_node_path26 = __toESM(require("path"));
8559
8802
  }
8560
8803
  });
8561
8804
 
@@ -8573,13 +8816,13 @@ function proveManagedComposePopulation(options) {
8573
8816
  }
8574
8817
  services.add(service);
8575
8818
  const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
8576
- if (files.some((file) => !file || !import_node_path26.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path26.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
8819
+ if (files.some((file) => !file || !import_node_path27.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path27.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
8577
8820
  throw new Error("Managed stop Compose file identity changed.");
8578
8821
  }
8579
8822
  for (const file of files.slice(plan.composeFiles.length)) {
8580
- if (import_node_path26.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
8581
- import_node_path26.default.basename(file)
8582
- ) || import_node_fs19.default.realpathSync(file) !== import_node_path26.default.join(import_node_fs19.default.realpathSync(providerRoot), import_node_path26.default.relative(providerRoot, file))) {
8823
+ if (import_node_path27.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
8824
+ import_node_path27.default.basename(file)
8825
+ ) || import_node_fs20.default.realpathSync(file) !== import_node_path27.default.join(import_node_fs20.default.realpathSync(providerRoot), import_node_path27.default.relative(providerRoot, file))) {
8583
8826
  throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
8584
8827
  }
8585
8828
  }
@@ -8597,12 +8840,12 @@ function proveManagedComposePopulation(options) {
8597
8840
  }
8598
8841
  return primary;
8599
8842
  }
8600
- var import_node_fs19, import_node_path26;
8843
+ var import_node_fs20, import_node_path27;
8601
8844
  var init_managed_compose_population = __esm({
8602
8845
  "src/core/managed-compose-population.ts"() {
8603
8846
  "use strict";
8604
- import_node_fs19 = __toESM(require("fs"));
8605
- import_node_path26 = __toESM(require("path"));
8847
+ import_node_fs20 = __toESM(require("fs"));
8848
+ import_node_path27 = __toESM(require("path"));
8606
8849
  init_workspace();
8607
8850
  }
8608
8851
  });
@@ -8638,7 +8881,7 @@ function isRuntimeInstalled(runtime) {
8638
8881
  function readWorkspaceRuntimeConfig() {
8639
8882
  let raw;
8640
8883
  try {
8641
- raw = JSON.parse(import_node_fs20.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
8884
+ raw = JSON.parse(import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
8642
8885
  } catch {
8643
8886
  return {};
8644
8887
  }
@@ -8659,7 +8902,7 @@ function readWorkspaceRuntimeConfig() {
8659
8902
  function inspectWorkspaceRuntimeConfig() {
8660
8903
  let rawText;
8661
8904
  try {
8662
- rawText = import_node_fs20.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
8905
+ rawText = import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
8663
8906
  } catch {
8664
8907
  return { exists: false, config: {}, problems: [] };
8665
8908
  }
@@ -8710,7 +8953,7 @@ function writeWorkspaceRuntimeConfig(config) {
8710
8953
  }
8711
8954
  next.devsyInactivityTimeout = timeout;
8712
8955
  }
8713
- import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8956
+ import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8714
8957
  writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
8715
8958
  `);
8716
8959
  }
@@ -8818,20 +9061,20 @@ function resetWorkspaceRuntimeCaches() {
8818
9061
  cachedRuntime = void 0;
8819
9062
  cachedSnapshots = void 0;
8820
9063
  }
8821
- var import_node_child_process10, import_node_fs20, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
9064
+ var import_node_child_process10, import_node_fs21, import_node_path28, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
8822
9065
  var init_workspace_runtime = __esm({
8823
9066
  "src/core/workspace-runtime.ts"() {
8824
9067
  "use strict";
8825
9068
  import_node_child_process10 = require("child_process");
8826
- import_node_fs20 = __toESM(require("fs"));
8827
- import_node_path27 = __toESM(require("path"));
9069
+ import_node_fs21 = __toESM(require("fs"));
9070
+ import_node_path28 = __toESM(require("path"));
8828
9071
  init_atomic_file();
8829
9072
  init_devpod_registry();
8830
9073
  init_devsy_workspaces();
8831
9074
  init_router();
8832
9075
  init_workspace();
8833
9076
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
8834
- RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
9077
+ RUNTIME_CONFIG_FILE = import_node_path28.default.join(DEVROUTER_HOME, "workspace-runtime.json");
8835
9078
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
8836
9079
  UnsupportedWorkspaceRuntimeError = class extends Error {
8837
9080
  };
@@ -8958,7 +9201,7 @@ function resolveGitCommonDir(repoPath) {
8958
9201
  if (result.status !== 0 || !output2) {
8959
9202
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
8960
9203
  }
8961
- return comparableWorkspacePath(import_node_path28.default.isAbsolute(output2) ? output2 : import_node_path28.default.resolve(repoPath, output2));
9204
+ return comparableWorkspacePath(import_node_path29.default.isAbsolute(output2) ? output2 : import_node_path29.default.resolve(repoPath, output2));
8962
9205
  }
8963
9206
  function resolveGitTopLevel(repoPath) {
8964
9207
  const result = (0, import_node_child_process12.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -9009,7 +9252,7 @@ function listGitWorktrees(repoPath) {
9009
9252
  return worktrees;
9010
9253
  }
9011
9254
  function ownershipDirectory(repoPath) {
9012
- return import_node_path28.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
9255
+ return import_node_path29.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
9013
9256
  }
9014
9257
  function validateWorkspace(value, label2) {
9015
9258
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -9037,7 +9280,7 @@ function validateRecord(value, expectedWorkspace) {
9037
9280
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
9038
9281
  );
9039
9282
  }
9040
- if (typeof candidate.worktreePath !== "string" || !import_node_path28.default.isAbsolute(candidate.worktreePath)) {
9283
+ if (typeof candidate.worktreePath !== "string" || !import_node_path29.default.isAbsolute(candidate.worktreePath)) {
9041
9284
  throw new Error("invalid workspace ownership worktreePath");
9042
9285
  }
9043
9286
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -9055,7 +9298,7 @@ function validateRecord(value, expectedWorkspace) {
9055
9298
  };
9056
9299
  }
9057
9300
  function recordPath(repoPath, workspace) {
9058
- return import_node_path28.default.join(
9301
+ return import_node_path29.default.join(
9059
9302
  ownershipDirectory(repoPath),
9060
9303
  `${validateWorkspace(workspace, "workspace")}.json`
9061
9304
  );
@@ -9063,7 +9306,7 @@ function recordPath(repoPath, workspace) {
9063
9306
  function readRecordFile(filePath, expectedWorkspace) {
9064
9307
  let parsed;
9065
9308
  try {
9066
- parsed = JSON.parse(import_node_fs21.default.readFileSync(filePath, "utf-8"));
9309
+ parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
9067
9310
  } catch (error) {
9068
9311
  if (error instanceof SyntaxError) {
9069
9312
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -9088,7 +9331,7 @@ function listWorkspaceOwnership(repoPath) {
9088
9331
  function listWorkspaceOwnershipInDirectory(directory) {
9089
9332
  let entries;
9090
9333
  try {
9091
- entries = import_node_fs21.default.readdirSync(directory, { withFileTypes: true });
9334
+ entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
9092
9335
  } catch (error) {
9093
9336
  if (error.code === "ENOENT") return [];
9094
9337
  throw error;
@@ -9096,14 +9339,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
9096
9339
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
9097
9340
  const workspace = entry.name.slice(0, -".json".length);
9098
9341
  validateWorkspace(workspace, "filename");
9099
- return readRecordFile(import_node_path28.default.join(directory, entry.name), workspace);
9342
+ return readRecordFile(import_node_path29.default.join(directory, entry.name), workspace);
9100
9343
  });
9101
9344
  }
9102
9345
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
9103
9346
  const workspace = validateWorkspace(input2.workspace, "workspace");
9104
9347
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
9105
9348
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
9106
- const filePath = import_node_path28.default.join(directory, `${workspace}.json`);
9349
+ const filePath = import_node_path29.default.join(directory, `${workspace}.json`);
9107
9350
  const now = (/* @__PURE__ */ new Date()).toISOString();
9108
9351
  const records = listWorkspaceOwnershipInDirectory(directory);
9109
9352
  const existing = records.find((record6) => record6.workspace === workspace);
@@ -9139,9 +9382,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
9139
9382
  return record5;
9140
9383
  }
9141
9384
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
9142
- const filePath = import_node_path28.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
9385
+ const filePath = import_node_path29.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
9143
9386
  try {
9144
- import_node_fs21.default.rmSync(filePath);
9387
+ import_node_fs22.default.rmSync(filePath);
9145
9388
  return true;
9146
9389
  } catch (error) {
9147
9390
  if (error.code === "ENOENT") return false;
@@ -9152,7 +9395,7 @@ function sameOwnershipRecord(left, right) {
9152
9395
  return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
9153
9396
  }
9154
9397
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
9155
- const filePath = import_node_path28.default.join(
9398
+ const filePath = import_node_path29.default.join(
9156
9399
  directory,
9157
9400
  `${validateWorkspace(expected.workspace, "workspace")}.json`
9158
9401
  );
@@ -9164,14 +9407,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
9164
9407
  throw error;
9165
9408
  }
9166
9409
  if (!sameOwnershipRecord(current, expected)) return "changed";
9167
- import_node_fs21.default.rmSync(filePath);
9410
+ import_node_fs22.default.rmSync(filePath);
9168
9411
  return "removed";
9169
9412
  }
9170
9413
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
9171
9414
  const directory = ownershipDirectory(repoPath);
9172
- import_node_fs21.default.mkdirSync(directory, { recursive: true });
9415
+ import_node_fs22.default.mkdirSync(directory, { recursive: true });
9173
9416
  return withFileLockSync(
9174
- import_node_path28.default.join(directory, ".lock"),
9417
+ import_node_path29.default.join(directory, ".lock"),
9175
9418
  {
9176
9419
  activity: "workspace ownership transaction",
9177
9420
  target: `'${repoPath}'`,
@@ -9204,7 +9447,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
9204
9447
  try {
9205
9448
  workspace = readPersistedWorkspace(worktree.path);
9206
9449
  } catch (error) {
9207
- if (!import_node_fs21.default.existsSync(worktree.path)) continue;
9450
+ if (!import_node_fs22.default.existsSync(worktree.path)) continue;
9208
9451
  throw error;
9209
9452
  }
9210
9453
  if (!workspace) continue;
@@ -9367,7 +9610,7 @@ function inspectWorkspaceOwnership(record5, worktrees, devpods) {
9367
9610
  if (worktree?.locked) {
9368
9611
  return { ownerStatus: "locked", devpodStatus, worktree };
9369
9612
  }
9370
- if ((!worktree || worktree.prunable) && import_node_fs21.default.existsSync(record5.worktreePath)) {
9613
+ if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record5.worktreePath)) {
9371
9614
  return { ownerStatus: "conflict", devpodStatus, worktree };
9372
9615
  }
9373
9616
  if (!worktree || worktree.prunable) {
@@ -9391,20 +9634,20 @@ function listMissingWorkspaceOwnership(repoPath) {
9391
9634
  (record5) => inspectWorkspaceOwnership(record5, worktrees, void 0).ownerStatus === "missing"
9392
9635
  );
9393
9636
  }
9394
- var import_node_child_process12, import_node_fs21, import_node_path28, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
9637
+ var import_node_child_process12, import_node_fs22, import_node_path29, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
9395
9638
  var init_workspace_ownership = __esm({
9396
9639
  "src/core/workspace-ownership.ts"() {
9397
9640
  "use strict";
9398
9641
  import_node_child_process12 = require("child_process");
9399
- import_node_fs21 = __toESM(require("fs"));
9400
- import_node_path28 = __toESM(require("path"));
9642
+ import_node_fs22 = __toESM(require("fs"));
9643
+ import_node_path29 = __toESM(require("path"));
9401
9644
  init_atomic_file();
9402
9645
  init_devpod_workspaces();
9403
9646
  init_file_lock();
9404
9647
  init_workspace();
9405
9648
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
9406
9649
  OWNERSHIP_VERSION = 1;
9407
- OWNERSHIP_DIR = import_node_path28.default.join("devrouter", "workspaces");
9650
+ OWNERSHIP_DIR = import_node_path29.default.join("devrouter", "workspaces");
9408
9651
  }
9409
9652
  });
9410
9653
 
@@ -9653,12 +9896,12 @@ function managedStopRouteReferences(state) {
9653
9896
  (name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
9654
9897
  );
9655
9898
  }
9656
- var import_node_os3, import_node_path29, import_node_util8;
9899
+ var import_node_os4, import_node_path30, import_node_util8;
9657
9900
  var init_managed_stop_recovery = __esm({
9658
9901
  "src/core/managed-stop-recovery.ts"() {
9659
9902
  "use strict";
9660
- import_node_os3 = __toESM(require("os"));
9661
- import_node_path29 = __toESM(require("path"));
9903
+ import_node_os4 = __toESM(require("os"));
9904
+ import_node_path30 = __toESM(require("path"));
9662
9905
  import_node_util8 = require("util");
9663
9906
  init_devpod_environment();
9664
9907
  init_devpod_registry();
@@ -9805,8 +10048,8 @@ function tlsSetupCommand(repoPath) {
9805
10048
  }
9806
10049
  function getMkcertRootCAPath(options = {}) {
9807
10050
  ensureMkcert();
9808
- const rootCAPath = import_node_path30.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
9809
- if (!import_node_fs22.default.existsSync(rootCAPath)) {
10051
+ const rootCAPath = import_node_path31.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
10052
+ if (!import_node_fs23.default.existsSync(rootCAPath)) {
9810
10053
  throw new Error(
9811
10054
  `mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
9812
10055
  );
@@ -9907,11 +10150,11 @@ function compactTLSCertificateHosts(hosts) {
9907
10150
  );
9908
10151
  }
9909
10152
  function readCurrentCertificateHosts(options = {}) {
9910
- if (!import_node_fs22.default.existsSync(CERT_FILE)) {
10153
+ if (!import_node_fs23.default.existsSync(CERT_FILE)) {
9911
10154
  return [];
9912
10155
  }
9913
10156
  try {
9914
- const pem = import_node_fs22.default.readFileSync(CERT_FILE, "utf-8");
10157
+ const pem = import_node_fs23.default.readFileSync(CERT_FILE, "utf-8");
9915
10158
  return parseCertificateDnsHosts(pem);
9916
10159
  } catch (error) {
9917
10160
  if (options.replaceMalformed) {
@@ -10039,20 +10282,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
10039
10282
  );
10040
10283
  }
10041
10284
  }
10042
- var import_node_child_process14, import_node_crypto12, import_node_fs22, import_node_path30, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
10285
+ var import_node_child_process14, import_node_crypto12, import_node_fs23, import_node_path31, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
10043
10286
  var init_tls = __esm({
10044
10287
  "src/core/tls.ts"() {
10045
10288
  "use strict";
10046
10289
  import_node_child_process14 = require("child_process");
10047
10290
  import_node_crypto12 = require("crypto");
10048
- import_node_fs22 = __toESM(require("fs"));
10049
- import_node_path30 = __toESM(require("path"));
10291
+ import_node_fs23 = __toESM(require("fs"));
10292
+ import_node_path31 = __toESM(require("path"));
10050
10293
  init_docker();
10051
10294
  init_file_lock();
10052
10295
  init_host_routes();
10053
10296
  init_router();
10054
10297
  DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
10055
- TLS_CERTIFICATE_LOCK_FILE = import_node_path30.default.join(DEVROUTER_HOME, "tls-certificate.lock");
10298
+ TLS_CERTIFICATE_LOCK_FILE = import_node_path31.default.join(DEVROUTER_HOME, "tls-certificate.lock");
10056
10299
  TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
10057
10300
  TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
10058
10301
  }
@@ -10133,6 +10376,174 @@ var init_route_publication = __esm({
10133
10376
  }
10134
10377
  });
10135
10378
 
10379
+ // src/core/traefik-route-health.ts
10380
+ function sleep(ms) {
10381
+ return new Promise((resolve) => setTimeout(resolve, ms));
10382
+ }
10383
+ function inspectRouterApi(protocol) {
10384
+ const result = (0, import_node_child_process15.spawnSync)(
10385
+ "curl",
10386
+ [
10387
+ "--silent",
10388
+ "--show-error",
10389
+ "--fail",
10390
+ "--max-time",
10391
+ ROUTER_API_REQUEST_TIMEOUT_SECONDS,
10392
+ `${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
10393
+ ],
10394
+ { encoding: "utf-8" }
10395
+ );
10396
+ if (result.status !== 0) {
10397
+ return {
10398
+ ok: false,
10399
+ details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
10400
+ };
10401
+ }
10402
+ let value;
10403
+ try {
10404
+ value = JSON.parse(result.stdout);
10405
+ } catch {
10406
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
10407
+ }
10408
+ if (!Array.isArray(value)) {
10409
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
10410
+ }
10411
+ if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
10412
+ return {
10413
+ ok: false,
10414
+ details: `${protocol.toUpperCase()} router API returned malformed entries`
10415
+ };
10416
+ return {
10417
+ ok: true,
10418
+ reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
10419
+ names: new Set(
10420
+ value.flatMap((item) => {
10421
+ if (!item || typeof item !== "object") return [];
10422
+ const name = item.name;
10423
+ return typeof name === "string" ? [name] : [];
10424
+ })
10425
+ )
10426
+ };
10427
+ }
10428
+ function isRecord5(value) {
10429
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10430
+ }
10431
+ function inspectExpectedRoutes(routes, expectation) {
10432
+ const mismatched = [];
10433
+ const failures = [];
10434
+ for (const protocol of ["http", "tcp"]) {
10435
+ const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
10436
+ if (expected.length === 0) continue;
10437
+ const result = inspectRouterApi(protocol);
10438
+ if (!result.ok) {
10439
+ failures.push(result.details);
10440
+ mismatched.push(...expected);
10441
+ continue;
10442
+ }
10443
+ const unexpected = expected.filter(
10444
+ (name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
10445
+ );
10446
+ const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
10447
+ if (boundedAbsenceIsUncertain) {
10448
+ failures.push(
10449
+ `${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
10450
+ );
10451
+ }
10452
+ mismatched.push(...unexpected);
10453
+ if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
10454
+ mismatched.push(...expected);
10455
+ }
10456
+ }
10457
+ return mismatched.length === 0 ? { ok: true } : {
10458
+ ok: false,
10459
+ routes: mismatched,
10460
+ details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
10461
+ };
10462
+ }
10463
+ async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
10464
+ const deadline = Date.now() + timeoutMs;
10465
+ let result;
10466
+ do {
10467
+ result = inspectExpectedRoutes(routes, expectation);
10468
+ if (result.ok) return result;
10469
+ if (Date.now() < deadline) {
10470
+ await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
10471
+ }
10472
+ } while (Date.now() < deadline);
10473
+ return result;
10474
+ }
10475
+ async function ensureTraefikRouteExpectation(routes, expectation, options) {
10476
+ if (routes.length === 0) return { restarted: false };
10477
+ const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
10478
+ const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
10479
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
10480
+ const initial = await waitForExpectedRoutes(
10481
+ routes,
10482
+ expectation,
10483
+ initialTimeoutMs,
10484
+ pollIntervalMs
10485
+ );
10486
+ if (initial.ok) return { restarted: false };
10487
+ if (options.allowRestart === false)
10488
+ throw new Error(
10489
+ "Traefik routes did not reload; shared router restart is disabled for this operation."
10490
+ );
10491
+ import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
10492
+ return withFileLock(
10493
+ ROUTER_RELOAD_LOCK_FILE,
10494
+ {
10495
+ activity: "Traefik route reload recovery",
10496
+ waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
10497
+ fair: true,
10498
+ onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
10499
+ },
10500
+ async () => {
10501
+ const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
10502
+ if (recheck.ok) return { restarted: false };
10503
+ claimLifecycleEffect();
10504
+ restartRouterStack();
10505
+ const recovered = await waitForExpectedRoutes(
10506
+ routes,
10507
+ expectation,
10508
+ recoveryTimeoutMs,
10509
+ pollIntervalMs
10510
+ );
10511
+ if (recovered.ok) return { restarted: true };
10512
+ const action2 = expectation === "loaded" ? "load" : "remove";
10513
+ throw new Error(
10514
+ `Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
10515
+ );
10516
+ }
10517
+ );
10518
+ }
10519
+ async function ensureTraefikRoutesRemoved(routes, options = {}) {
10520
+ return ensureTraefikRouteExpectation(routes, "removed", options);
10521
+ }
10522
+ function assertTraefikRoutesRemoved(routes) {
10523
+ const result = inspectExpectedRoutes(routes, "removed");
10524
+ if (!result.ok) throw new Error(`Workspace route cessation is not proven: ${result.details}`);
10525
+ }
10526
+ var import_node_child_process15, import_node_fs24, import_node_path32, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
10527
+ var init_traefik_route_health = __esm({
10528
+ "src/core/traefik-route-health.ts"() {
10529
+ "use strict";
10530
+ import_node_child_process15 = require("child_process");
10531
+ import_node_fs24 = __toESM(require("fs"));
10532
+ import_node_path32 = __toESM(require("path"));
10533
+ init_file_lock();
10534
+ init_host_routes();
10535
+ init_reliability_context();
10536
+ init_router();
10537
+ ROUTER_API_BASE = "http://127.0.0.1:8080/api";
10538
+ ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
10539
+ ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
10540
+ DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
10541
+ DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
10542
+ DEFAULT_POLL_INTERVAL_MS = 250;
10543
+ ROUTER_API_PAGE_SIZE = 1e3;
10544
+ }
10545
+ });
10546
+
10136
10547
  // src/core/managed-devsy-stop.ts
10137
10548
  function sameSet(left, right) {
10138
10549
  return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
@@ -10145,10 +10556,92 @@ function containerIdentity(container) {
10145
10556
  mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
10146
10557
  });
10147
10558
  }
10559
+ function proveInitialManagedDevsyAbsence(repoPath, expectedId) {
10560
+ if (!isLinkedWorktree(repoPath)) return void 0;
10561
+ const workspace = resolveWorktreeWorkspace(repoPath);
10562
+ if (!workspace || readManagedRuntimeState(repoPath, workspace)) return void 0;
10563
+ if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return void 0;
10564
+ const runtime = loadRuntimeConfig(repoPath, workspace);
10565
+ if (!runtime.config.managedRuntime) return void 0;
10566
+ const routes = proxyAppsFromConfig(runtime.config).flatMap(
10567
+ (app) => ["http", "tcp"].map((protocol) => ({ repoPath, name: app.name, protocol }))
10568
+ );
10569
+ const record5 = readWorkspaceOwnership(repoPath, workspace);
10570
+ if (!record5 || expectedId !== void 0 && record5.devpodId !== expectedId || record5.workspace !== workspace || !sameWorkspacePath(record5.worktreePath, repoPath))
10571
+ throw new Error("Initial managed stop requires exact linked ownership.");
10572
+ const devsyId = record5.devpodId;
10573
+ const ownership = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
10574
+ if (ownership.status === "conflict") throw new Error(ownership.reason);
10575
+ if (ownership.status !== "absent") return void 0;
10576
+ const gitCommonDir = resolveGitCommonDir(repoPath);
10577
+ const endpoint = resolveManagedStopEndpoint();
10578
+ if (!supportsManagedStopBaseline(endpoint))
10579
+ throw new Error("Initial managed stop requires a local Docker endpoint.");
10580
+ const daemon = inspectManagedStopDaemon(endpoint);
10581
+ const legacyHome = devpodRegistryRoot();
10582
+ const readLegacy = () => listDevpodWorkspacesRaw({ readLocalWhenMissing: true }).map(({ id: id5, source }) => ({ id: id5, source: { localFolder: source.localFolder } })).sort(
10583
+ (a, b) => a.id.localeCompare(b.id) || a.source.localFolder.localeCompare(b.source.localFolder)
10584
+ );
10585
+ const legacyWorkspaces = readLegacy();
10586
+ const observe = () => {
10587
+ resetWorkspaceRuntimeCaches();
10588
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy" || !isLinkedWorktree(repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== gitCommonDir || !(0, import_node_util9.isDeepStrictEqual)(readWorkspaceOwnership(repoPath, workspace), record5) || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present" || readManagedRuntimeState(repoPath, workspace))
10589
+ throw new Error("Initial managed stop ownership or retained state changed.");
10590
+ if (devpodRegistryRoot() !== legacyHome || !(0, import_node_util9.isDeepStrictEqual)(readLegacy(), legacyWorkspaces))
10591
+ throw new Error("Initial managed stop legacy registry evidence changed.");
10592
+ if (inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath).status !== "absent" || legacyWorkspaces.some(
10593
+ (entry) => entry.id === devsyId || entry.source.localFolder !== "" && sameWorkspacePath(entry.source.localFolder, repoPath)
10594
+ ))
10595
+ throw new Error("Initial managed stop requires both provider registrations absent.");
10596
+ if (!inspectDevsyRuntimeAbsence(devsyId))
10597
+ throw new Error("Initial managed stop requires positive runtime not-found.");
10598
+ if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10599
+ throw new Error("Initial managed stop Docker identity changed.");
10600
+ if (inspectProviderRunnerContainers(endpoint, devsyId).length)
10601
+ throw new Error("Initial managed stop observed a remaining provider runner.");
10602
+ assertManagedStopCheckoutAbsent(endpoint, repoPath);
10603
+ if (listHostRouteState().some((route) => sameWorkspacePath(route.repoPath, repoPath)))
10604
+ throw new Error("Initial managed stop observed remaining workspace routes.");
10605
+ assertTraefikRoutesRemoved(routes);
10606
+ if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10607
+ throw new Error("Initial managed stop Docker identity changed.");
10608
+ };
10609
+ observe();
10610
+ observe();
10611
+ return {
10612
+ workspace,
10613
+ record: record5,
10614
+ gitCommonDir,
10615
+ endpoint,
10616
+ daemon,
10617
+ routes,
10618
+ legacyHome,
10619
+ legacyWorkspaces
10620
+ };
10621
+ }
10148
10622
  function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10149
- if (!import_node_fs23.default.existsSync(import_node_path31.default.join(repoPath, ".devrouter.yml"))) return false;
10623
+ if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return false;
10624
+ if (!loadRepoConfig(repoPath).managedRuntime) return false;
10150
10625
  const linked = isLinkedWorktree(repoPath);
10151
10626
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10627
+ const identity2 = { repoPath, workspace: workspace ?? null, provider: "devsy" };
10628
+ const readAuthority = () => {
10629
+ const record5 = readReliabilityOperation(identity2);
10630
+ const operation = record5?.state.operation;
10631
+ const entries = record5?.state.operationHistory.filter((entry2) => entry2.id === operation?.id);
10632
+ const entry = entries?.[0];
10633
+ if (!record5 || !(0, import_node_util9.isDeepStrictEqual)(record5.identity, identity2) || record5.worker || !operation || operation.kind !== "ensure" || !operation.drained || !["COMPLETED", "INTERRUPTED", "COMPLETION_UNKNOWN"].includes(operation.status) || record5.state.phase !== "stopping" || record5.state.desired !== "stopped-by-user" || entries?.length !== 1 || !entry || entry.kind !== "ensure" || !entry.profile || !entry.drained || entry.status !== operation.status)
10634
+ throw new Error("Initial managed stop requires the drained ensure's recorded profile.");
10635
+ return {
10636
+ identity: record5.identity,
10637
+ fence: reliabilityFence(record5.state),
10638
+ operation,
10639
+ entry
10640
+ };
10641
+ };
10642
+ const authority = readAuthority();
10643
+ const gitCommonDir = linked ? resolveGitCommonDir(repoPath) : void 0;
10644
+ const workspaceEnv = workspace && gitCommonDir ? { token: workspace, gitCommonDir } : void 0;
10152
10645
  const registration2 = () => {
10153
10646
  resetWorkspaceRuntimeCaches();
10154
10647
  if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy")
@@ -10158,14 +10651,20 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10158
10651
  throw new Error("Initial managed stop requires one exact Devsy registration.");
10159
10652
  if (linked) {
10160
10653
  const record5 = workspace ? readWorkspaceOwnership(repoPath, workspace) : void 0;
10161
- if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace)
10654
+ if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== gitCommonDir || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present")
10162
10655
  throw new Error("Initial managed stop workspace ownership changed.");
10163
10656
  }
10164
10657
  return owner2.workspace;
10165
10658
  };
10166
- const runtime = loadRuntimeConfig(repoPath, workspace ?? "");
10167
- if (!runtime.config.managedRuntime) return false;
10659
+ const runtime = loadRuntimeConfig(repoPath, workspace ?? "", authority.entry.profile);
10660
+ if (!runtime.config.managedRuntime || runtime.profile !== authority.entry.profile)
10661
+ throw new Error("Initial managed stop requires the recorded managed profile.");
10168
10662
  const owner = registration2();
10663
+ proveLocalDockerSelection(owner);
10664
+ const endpoint = resolveManagedStopEndpoint();
10665
+ if (!supportsManagedStopBaseline(endpoint))
10666
+ throw new Error("Initial managed stop requires a pinned local Docker endpoint.");
10667
+ const daemon = inspectManagedStopDaemon(endpoint);
10169
10668
  if (!owner.context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId))
10170
10669
  throw new Error("Initial managed stop requires exact provider context.");
10171
10670
  const plan = inspectManagedDevcontainerConfig({
@@ -10174,8 +10673,8 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10174
10673
  profile: runtime.resolvedProfile,
10175
10674
  linked
10176
10675
  });
10177
- const providerRoot = import_node_path31.default.resolve(process.env.DEVSY_HOME || import_node_path31.default.join(import_node_os4.default.homedir(), ".devsy"));
10178
- const featureDirectory = import_node_path31.default.join(
10676
+ const providerRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
10677
+ const featureDirectory = import_node_path33.default.join(
10179
10678
  providerRoot,
10180
10679
  "contexts",
10181
10680
  owner.context,
@@ -10185,27 +10684,51 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10185
10684
  ".docker-compose"
10186
10685
  );
10187
10686
  const observe = () => {
10188
- if (!(0, import_node_util9.isDeepStrictEqual)(registration2(), owner) || readManagedRuntimeState(repoPath, workspace))
10189
- throw new Error("Initial managed stop ownership or runtime state changed.");
10687
+ if (!(0, import_node_util9.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util9.isDeepStrictEqual)(registration2(), owner) || readManagedRuntimeState(repoPath, workspace) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10688
+ throw new Error("Initial managed stop authority or runtime state changed.");
10689
+ proveLocalDockerSelection(owner);
10690
+ if (listDevpodWorkspacesRaw({ readLocalWhenMissing: true }).some(
10691
+ (entry) => entry.id === devsyId || entry.source.localFolder !== "" && sameWorkspacePath(entry.source.localFolder, repoPath)
10692
+ ))
10693
+ throw new Error("Initial managed stop found conflicting provider ownership.");
10694
+ const currentRuntime = loadRuntimeConfig(repoPath, workspace ?? "", authority.entry.profile);
10695
+ const currentPlan = inspectManagedDevcontainerConfig({
10696
+ repoPath,
10697
+ config: currentRuntime.config,
10698
+ profile: currentRuntime.resolvedProfile,
10699
+ linked
10700
+ });
10701
+ if (currentRuntime.profile !== authority.entry.profile || !(0, import_node_util9.isDeepStrictEqual)(currentRuntime.resolvedProfile, runtime.resolvedProfile) || !(0, import_node_util9.isDeepStrictEqual)(currentPlan, plan) || inspectManagedDevcontainerGeneratedConfig(currentPlan).status !== "valid")
10702
+ throw new Error("Initial managed stop configuration changed.");
10190
10703
  const matches = inspectWorkspaceContainers().filter(
10191
10704
  (c) => sameWorkspacePath(
10192
10705
  c.labels["com.docker.compose.project.working_dir"] ?? "",
10193
10706
  plan.composeDirectory
10194
10707
  )
10195
10708
  );
10196
- if (!matches.length) return [];
10709
+ if (!matches.length)
10710
+ throw new Error("Initial managed stop requires its complete selected population.");
10197
10711
  const projects = new Set(matches.map((c) => c.labels["com.docker.compose.project"]));
10198
10712
  if (projects.size !== 1 || !matches[0]?.labels["com.docker.compose.project"])
10199
10713
  throw new Error("Initial managed stop requires one exact Compose project.");
10200
10714
  const population = inspectManagedStopContainers(
10201
- matches[0].labels["com.docker.compose.project"]
10715
+ matches[0].labels["com.docker.compose.project"],
10716
+ endpoint
10202
10717
  );
10203
10718
  if (!sameSet(
10719
+ inspectManagedStopWorkspaceIds(endpoint, plan.composeDirectory),
10720
+ population.map((c) => c.id)
10721
+ ) || !sameSet(
10204
10722
  matches.map((c) => c.id),
10205
10723
  population.map((c) => c.id)
10206
10724
  ))
10207
10725
  throw new Error("Initial managed stop population changed.");
10208
- proveManagedComposePopulation({
10726
+ if (!sameSet(
10727
+ population.map((c) => c.labels["com.docker.compose.service"] ?? ""),
10728
+ plan.desiredServices
10729
+ ))
10730
+ throw new Error("Initial managed stop requires its complete selected population.");
10731
+ const primary = proveManagedComposePopulation({
10209
10732
  plan,
10210
10733
  repoPath,
10211
10734
  composeProject: matches[0].labels["com.docker.compose.project"],
@@ -10213,9 +10736,23 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10213
10736
  featureDirectory,
10214
10737
  containers: population
10215
10738
  });
10739
+ const uidBytes = Buffer.byteLength(owner.uid ?? "");
10740
+ const runnerId = (uidBytes === 16 || uidBytes === 40 ? owner.uid : void 0) ?? devsyId;
10741
+ if (owner.source.container && owner.source.container !== primary.id || !owner.source.container && inspectManagedStopRunnerId(endpoint, primary.id) !== runnerId)
10742
+ throw new Error("Initial managed stop provider-to-container binding changed.");
10743
+ if (!owner.source.container && !sameSet(inspectProviderRunnerContainers(endpoint, runnerId), [primary.id]))
10744
+ throw new Error("Initial managed stop provider runner population changed.");
10745
+ assertManagedContainerConfigUnchanged({
10746
+ plan,
10747
+ containers: population,
10748
+ workspace: workspaceEnv
10749
+ });
10750
+ if (!(0, import_node_util9.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util9.isDeepStrictEqual)(registration2(), owner) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10751
+ throw new Error("Initial managed stop authority changed during inspection.");
10216
10752
  return population;
10217
10753
  };
10218
10754
  const initial = observe();
10755
+ const stopped = new Set(initial.filter((c) => !c.state.Running).map((c) => c.id));
10219
10756
  const stable = () => {
10220
10757
  const current = observe();
10221
10758
  if (!sameSet(
@@ -10223,19 +10760,21 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10223
10760
  current.map((c) => c.id)
10224
10761
  ) || current.some((c) => {
10225
10762
  const prior = initial.find((p) => p.id === c.id);
10226
- return !prior || containerIdentity(prior) !== containerIdentity(c) || !prior.state.Running && c.state.Running;
10763
+ return !prior || containerIdentity(prior) !== containerIdentity(c) || stopped.has(c.id) && c.state.Running;
10227
10764
  }))
10228
10765
  throw new Error("Initial managed stop population or identity changed.");
10766
+ for (const c of current) if (!c.state.Running) stopped.add(c.id);
10229
10767
  return current;
10230
10768
  };
10231
10769
  stable();
10232
- if (!initial.length) return false;
10233
10770
  for (const container of initial) {
10234
10771
  const current = stable().find((c) => c.id === container.id);
10235
- if (current?.state.Running)
10236
- stopExactManagedService(current.id, current.labels["com.docker.compose.service"] ?? "", {
10237
- timeoutMs: 3e4
10238
- });
10772
+ if (current?.state.Running) {
10773
+ claimLifecycleEffect();
10774
+ stopPinnedManagedContainer(endpoint, current.id);
10775
+ if (stable().find((c) => c.id === current.id)?.state.Running)
10776
+ throw new Error("Initial managed stop container cessation is not proven.");
10777
+ }
10239
10778
  }
10240
10779
  if (stable().some((c) => c.state.Running))
10241
10780
  throw new Error("Initial managed stop left a workload running.");
@@ -10246,7 +10785,10 @@ function stopRetainedManagedDevsyWorkspace(options) {
10246
10785
  const linked = isLinkedWorktree(repoPath);
10247
10786
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10248
10787
  const retainedState = readManagedRuntimeState(repoPath, workspace);
10249
- if (!retainedState) return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
10788
+ if (!retainedState) {
10789
+ if (proveInitialManagedDevsyAbsence(repoPath, devsyId)) return "proven-absent";
10790
+ return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
10791
+ }
10250
10792
  const state = retainedState;
10251
10793
  if (state.devpodId !== devsyId || linked && !workspace) {
10252
10794
  throw new Error("Managed stop requires the exact retained workspace identity.");
@@ -10307,7 +10849,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10307
10849
  const owned = inspectWorkspaceContainers().filter(
10308
10850
  (container) => sameWorkspacePath(
10309
10851
  container.labels["com.docker.compose.project.working_dir"] ?? "",
10310
- import_node_path31.default.join(repoPath, ".devcontainer")
10852
+ import_node_path33.default.join(repoPath, ".devcontainer")
10311
10853
  )
10312
10854
  );
10313
10855
  if (owned.length !== 0) {
@@ -10336,8 +10878,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
10336
10878
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
10337
10879
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
10338
10880
  }
10339
- const devsyRoot = import_node_path31.default.resolve(process.env.DEVSY_HOME || import_node_path31.default.join(import_node_os4.default.homedir(), ".devsy"));
10340
- const featureDirectory = import_node_path31.default.join(
10881
+ const devsyRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
10882
+ const featureDirectory = import_node_path33.default.join(
10341
10883
  devsyRoot,
10342
10884
  "contexts",
10343
10885
  context,
@@ -10441,23 +10983,29 @@ function stopRetainedManagedDevsyWorkspace(options) {
10441
10983
  if (providerFailed) throw providerError;
10442
10984
  return true;
10443
10985
  }
10444
- var import_node_fs23, import_node_os4, import_node_path31, import_node_util9;
10986
+ var import_node_fs25, import_node_os5, import_node_path33, import_node_util9;
10445
10987
  var init_managed_devsy_stop = __esm({
10446
10988
  "src/core/managed-devsy-stop.ts"() {
10447
10989
  "use strict";
10448
- import_node_fs23 = __toESM(require("fs"));
10449
- import_node_os4 = __toESM(require("os"));
10450
- import_node_path31 = __toESM(require("path"));
10990
+ import_node_fs25 = __toESM(require("fs"));
10991
+ import_node_os5 = __toESM(require("os"));
10992
+ import_node_path33 = __toESM(require("path"));
10451
10993
  import_node_util9 = require("util");
10452
10994
  init_devcontainer_profile();
10453
10995
  init_devpod_environment();
10454
10996
  init_devpod_registry();
10997
+ init_devsy_exec_proof();
10455
10998
  init_devsy_workspaces();
10999
+ init_host_routes();
10456
11000
  init_managed_compose_population();
10457
11001
  init_managed_runtime_state();
10458
11002
  init_managed_stop_recovery();
11003
+ init_reliability_context();
11004
+ init_reliability_contract();
11005
+ init_reliability_operation_store();
10459
11006
  init_repo_config();
10460
11007
  init_route_publication();
11008
+ init_traefik_route_health();
10461
11009
  init_workspace();
10462
11010
  init_workspace_ownership();
10463
11011
  init_workspace_runtime();
@@ -11003,7 +11551,7 @@ function hasControlCharacter2(value) {
11003
11551
  function readPolicyFile(filePath) {
11004
11552
  let initialStats;
11005
11553
  try {
11006
- initialStats = import_node_fs24.default.lstatSync(filePath);
11554
+ initialStats = import_node_fs26.default.lstatSync(filePath);
11007
11555
  } catch (error) {
11008
11556
  if (isErrno(error, "ENOENT")) return { status: "absent" };
11009
11557
  return { status: "invalid", error: "network policy could not be read" };
@@ -11016,16 +11564,16 @@ function readPolicyFile(filePath) {
11016
11564
  }
11017
11565
  let fileDescriptor;
11018
11566
  try {
11019
- fileDescriptor = import_node_fs24.default.openSync(
11567
+ fileDescriptor = import_node_fs26.default.openSync(
11020
11568
  filePath,
11021
- import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NONBLOCK | (import_node_fs24.default.constants.O_NOFOLLOW ?? 0)
11569
+ import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | (import_node_fs26.default.constants.O_NOFOLLOW ?? 0)
11022
11570
  );
11023
11571
  } catch (error) {
11024
11572
  if (isErrno(error, "ENOENT")) return { status: "absent" };
11025
11573
  return { status: "invalid", error: "network policy could not be read" };
11026
11574
  }
11027
11575
  try {
11028
- const openedStats = import_node_fs24.default.fstatSync(fileDescriptor);
11576
+ const openedStats = import_node_fs26.default.fstatSync(fileDescriptor);
11029
11577
  if (!openedStats.isFile()) {
11030
11578
  return { status: "invalid", error: "network policy must be a regular file" };
11031
11579
  }
@@ -11038,12 +11586,12 @@ function readPolicyFile(filePath) {
11038
11586
  const buffer = Buffer.alloc(openedStats.size);
11039
11587
  let offset = 0;
11040
11588
  while (offset < buffer.length) {
11041
- const bytesRead = import_node_fs24.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
11589
+ const bytesRead = import_node_fs26.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
11042
11590
  if (bytesRead === 0)
11043
11591
  return { status: "invalid", error: "network policy changed during read" };
11044
11592
  offset += bytesRead;
11045
11593
  }
11046
- const finalStats = import_node_fs24.default.fstatSync(fileDescriptor);
11594
+ const finalStats = import_node_fs26.default.fstatSync(fileDescriptor);
11047
11595
  if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
11048
11596
  return { status: "invalid", error: "network policy changed during read" };
11049
11597
  }
@@ -11052,7 +11600,7 @@ function readPolicyFile(filePath) {
11052
11600
  return { status: "invalid", error: "network policy could not be read" };
11053
11601
  } finally {
11054
11602
  try {
11055
- import_node_fs24.default.closeSync(fileDescriptor);
11603
+ import_node_fs26.default.closeSync(fileDescriptor);
11056
11604
  } catch {
11057
11605
  }
11058
11606
  }
@@ -11069,16 +11617,16 @@ function assertDisjoint(cidrs, message) {
11069
11617
  function isErrno(error, code) {
11070
11618
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
11071
11619
  }
11072
- var import_node_fs24, import_node_path32, NETWORK_POLICY_VERSION, NETWORK_POLICY_FILE, DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE, NETWORK_POLICY_PREFIX_LENGTHS, MAX_NETWORK_POLICY_FILE_BYTES, MAX_NETWORK_POLICY_ARRAY_ENTRIES, MAX_NETWORK_POLICY_DAEMON_ID_LENGTH, NetworkPolicyValidationError, NETWORK_POLICY_KEYS, PRIVATE_IPV4_RANGES;
11620
+ var import_node_fs26, import_node_path34, NETWORK_POLICY_VERSION, NETWORK_POLICY_FILE, DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE, NETWORK_POLICY_PREFIX_LENGTHS, MAX_NETWORK_POLICY_FILE_BYTES, MAX_NETWORK_POLICY_ARRAY_ENTRIES, MAX_NETWORK_POLICY_DAEMON_ID_LENGTH, NetworkPolicyValidationError, NETWORK_POLICY_KEYS, PRIVATE_IPV4_RANGES;
11073
11621
  var init_network_policy = __esm({
11074
11622
  "src/core/network-policy.ts"() {
11075
11623
  "use strict";
11076
- import_node_fs24 = __toESM(require("fs"));
11077
- import_node_path32 = __toESM(require("path"));
11624
+ import_node_fs26 = __toESM(require("fs"));
11625
+ import_node_path34 = __toESM(require("path"));
11078
11626
  init_network_capacity();
11079
11627
  init_router();
11080
11628
  NETWORK_POLICY_VERSION = 1;
11081
- NETWORK_POLICY_FILE = import_node_path32.default.join(DEVROUTER_HOME, "network-policy.json");
11629
+ NETWORK_POLICY_FILE = import_node_path34.default.join(DEVROUTER_HOME, "network-policy.json");
11082
11630
  DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE = 8;
11083
11631
  NETWORK_POLICY_PREFIX_LENGTHS = [24, 25, 26];
11084
11632
  MAX_NETWORK_POLICY_FILE_BYTES = 64 * 1024;
@@ -11179,27 +11727,27 @@ function assertExactKeys(value, keys2) {
11179
11727
  function readDocument(store) {
11180
11728
  let descriptor;
11181
11729
  try {
11182
- descriptor = import_node_fs25.default.openSync(
11730
+ descriptor = import_node_fs27.default.openSync(
11183
11731
  store.filePath,
11184
- import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NONBLOCK | (import_node_fs25.default.constants.O_NOFOLLOW ?? 0)
11732
+ import_node_fs27.default.constants.O_RDONLY | import_node_fs27.default.constants.O_NONBLOCK | (import_node_fs27.default.constants.O_NOFOLLOW ?? 0)
11185
11733
  );
11186
11734
  } catch (error) {
11187
11735
  if (isErrno2(error, "ENOENT")) return void 0;
11188
11736
  throw claimError("invalid-state", "network claim state could not be read");
11189
11737
  }
11190
11738
  try {
11191
- const stat = import_node_fs25.default.fstatSync(descriptor);
11739
+ const stat = import_node_fs27.default.fstatSync(descriptor);
11192
11740
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
11193
11741
  throw claimError("invalid-state", "network claim state is not a bounded private file");
11194
11742
  }
11195
11743
  const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11196
11744
  let count = 0;
11197
11745
  while (count < buffer.length) {
11198
- const bytesRead = import_node_fs25.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11746
+ const bytesRead = import_node_fs27.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11199
11747
  if (bytesRead === 0) break;
11200
11748
  count += bytesRead;
11201
11749
  }
11202
- const finalStat = import_node_fs25.default.fstatSync(descriptor);
11750
+ const finalStat = import_node_fs27.default.fstatSync(descriptor);
11203
11751
  if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
11204
11752
  throw claimError("invalid-state", "network claim state changed during read");
11205
11753
  }
@@ -11211,7 +11759,7 @@ function readDocument(store) {
11211
11759
  }
11212
11760
  return validateDocument(parsed, store.daemonId);
11213
11761
  } finally {
11214
- import_node_fs25.default.closeSync(descriptor);
11762
+ import_node_fs27.default.closeSync(descriptor);
11215
11763
  }
11216
11764
  }
11217
11765
  function validateDocument(value, daemonId) {
@@ -11278,16 +11826,16 @@ function createStore(daemonId, options) {
11278
11826
  return {
11279
11827
  root,
11280
11828
  daemonId,
11281
- filePath: import_node_path33.default.join(root, `${key}.json`),
11282
- lockPath: import_node_path33.default.join(root, `${key}.lock`),
11829
+ filePath: import_node_path35.default.join(root, `${key}.json`),
11830
+ lockPath: import_node_path35.default.join(root, `${key}.lock`),
11283
11831
  writeAtomic: options.writeAtomic ?? writeFileAtomically
11284
11832
  };
11285
11833
  }
11286
11834
  function validateRoot(root) {
11287
- if (typeof root !== "string" || !import_node_path33.default.isAbsolute(root) || root.includes("\0")) {
11835
+ if (typeof root !== "string" || !import_node_path35.default.isAbsolute(root) || root.includes("\0")) {
11288
11836
  throw claimError("invalid-input", "network claim root is invalid");
11289
11837
  }
11290
- const resolved = import_node_path33.default.resolve(root);
11838
+ const resolved = import_node_path35.default.resolve(root);
11291
11839
  if (resolved !== root) throw claimError("invalid-input", "network claim root is invalid");
11292
11840
  return resolved;
11293
11841
  }
@@ -11315,20 +11863,20 @@ function isErrno2(error, code) {
11315
11863
  function claimError(code, message) {
11316
11864
  return new NetworkClaimError(code, message);
11317
11865
  }
11318
- var import_node_crypto13, import_node_fs25, import_node_path33, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
11866
+ var import_node_crypto13, import_node_fs27, import_node_path35, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
11319
11867
  var init_network_claims = __esm({
11320
11868
  "src/core/network-claims.ts"() {
11321
11869
  "use strict";
11322
11870
  import_node_crypto13 = require("crypto");
11323
- import_node_fs25 = __toESM(require("fs"));
11324
- import_node_path33 = __toESM(require("path"));
11871
+ import_node_fs27 = __toESM(require("fs"));
11872
+ import_node_path35 = __toESM(require("path"));
11325
11873
  init_atomic_file();
11326
11874
  init_file_lock();
11327
11875
  init_network_capacity();
11328
11876
  init_network_policy();
11329
11877
  init_router();
11330
11878
  NETWORK_CLAIMS_VERSION = 1;
11331
- NETWORK_CLAIMS_ROOT = import_node_path33.default.join(DEVROUTER_HOME, "networks");
11879
+ NETWORK_CLAIMS_ROOT = import_node_path35.default.join(DEVROUTER_HOME, "networks");
11332
11880
  NETWORK_CLAIMS_MAX_RECORDS = 256;
11333
11881
  NETWORK_CLAIMS_MAX_FILE_BYTES = 512 * 1024;
11334
11882
  NetworkClaimError = class extends Error {
@@ -11370,14 +11918,14 @@ var init_network_claims = __esm({
11370
11918
 
11371
11919
  // src/core/network-claim-lookup.ts
11372
11920
  function networkOwnerKey(repoPath) {
11373
- if (!import_node_path34.default.isAbsolute(repoPath) || import_node_path34.default.resolve(repoPath) !== repoPath)
11921
+ if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
11374
11922
  throw new Error("Invalid network owner path.");
11375
11923
  return (0, import_node_crypto14.createHash)("sha256").update(repoPath).digest("hex");
11376
11924
  }
11377
11925
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11378
11926
  let names;
11379
11927
  try {
11380
- names = import_node_fs26.default.readdirSync(root);
11928
+ names = import_node_fs28.default.readdirSync(root);
11381
11929
  } catch (error) {
11382
11930
  if (error.code === "ENOENT") return void 0;
11383
11931
  throw new Error("Network claims cannot be inspected; repair is required.");
@@ -11386,21 +11934,21 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11386
11934
  if (files.length > 128) throw new Error("Network daemon inventory exceeds its bound.");
11387
11935
  let found;
11388
11936
  for (const file of files) {
11389
- const full = import_node_path34.default.join(root, file);
11390
- const stat = import_node_fs26.default.lstatSync(full);
11937
+ const full = import_node_path36.default.join(root, file);
11938
+ const stat = import_node_fs28.default.lstatSync(full);
11391
11939
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
11392
11940
  throw new Error("Invalid retained network claim file.");
11393
11941
  let value;
11394
- const descriptor = import_node_fs26.default.openSync(
11942
+ const descriptor = import_node_fs28.default.openSync(
11395
11943
  full,
11396
- import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | import_node_fs26.default.constants.O_NOFOLLOW
11944
+ import_node_fs28.default.constants.O_RDONLY | import_node_fs28.default.constants.O_NONBLOCK | import_node_fs28.default.constants.O_NOFOLLOW
11397
11945
  );
11398
11946
  try {
11399
- if (!import_node_fs26.default.fstatSync(descriptor).isFile()) throw new Error();
11947
+ if (!import_node_fs28.default.fstatSync(descriptor).isFile()) throw new Error();
11400
11948
  const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11401
11949
  let size = 0;
11402
11950
  while (size < bytes2.length) {
11403
- const count = import_node_fs26.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11951
+ const count = import_node_fs28.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11404
11952
  if (!count) break;
11405
11953
  size += count;
11406
11954
  }
@@ -11410,7 +11958,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11410
11958
  } catch {
11411
11959
  throw new Error("Retained network claim could not be read safely.");
11412
11960
  } finally {
11413
- import_node_fs26.default.closeSync(descriptor);
11961
+ import_node_fs28.default.closeSync(descriptor);
11414
11962
  }
11415
11963
  if (typeof value.daemonId !== "string" || `${(0, import_node_crypto14.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
11416
11964
  throw new Error("Retained network daemon identity is invalid.");
@@ -11426,13 +11974,13 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11426
11974
  }
11427
11975
  return found;
11428
11976
  }
11429
- var import_node_crypto14, import_node_fs26, import_node_path34;
11977
+ var import_node_crypto14, import_node_fs28, import_node_path36;
11430
11978
  var init_network_claim_lookup = __esm({
11431
11979
  "src/core/network-claim-lookup.ts"() {
11432
11980
  "use strict";
11433
11981
  import_node_crypto14 = require("crypto");
11434
- import_node_fs26 = __toESM(require("fs"));
11435
- import_node_path34 = __toESM(require("path"));
11982
+ import_node_fs28 = __toESM(require("fs"));
11983
+ import_node_path36 = __toESM(require("path"));
11436
11984
  init_network_claims();
11437
11985
  }
11438
11986
  });
@@ -11460,7 +12008,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
11460
12008
  return (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
11461
12009
  }
11462
12010
  function read(provider, args) {
11463
- const result = (0, import_node_child_process15.spawnSync)(provider, args, {
12011
+ const result = (0, import_node_child_process16.spawnSync)(provider, args, {
11464
12012
  encoding: "utf8",
11465
12013
  timeout: 5e3,
11466
12014
  maxBuffer: 2 * 1024 * 1024
@@ -11548,11 +12096,11 @@ function inspectNetworkProviderBinding(input2) {
11548
12096
  );
11549
12097
  }
11550
12098
  }
11551
- var import_node_child_process15, import_node_crypto15;
12099
+ var import_node_child_process16, import_node_crypto15;
11552
12100
  var init_network_provider_inspect = __esm({
11553
12101
  "src/core/network-provider-inspect.ts"() {
11554
12102
  "use strict";
11555
- import_node_child_process15 = require("child_process");
12103
+ import_node_child_process16 = require("child_process");
11556
12104
  import_node_crypto15 = require("crypto");
11557
12105
  init_devpod_environment();
11558
12106
  }
@@ -11591,7 +12139,7 @@ var init_network_provider_effect = __esm({
11591
12139
 
11592
12140
  // src/core/devsy-mutation.ts
11593
12141
  function withMutationLock(activity, target, operation) {
11594
- import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12142
+ import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
11595
12143
  return withFileLockSync(
11596
12144
  DEVSY_MUTATION_LOCK_FILE,
11597
12145
  {
@@ -11611,7 +12159,7 @@ function runDevsyAction(action2, devsyId, force = false, repoPath) {
11611
12159
  const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
11612
12160
  claimLifecycleEffect();
11613
12161
  const binding = repoPath ? networkProviderEffectOptions("devsy", devsyId, repoPath) : { args: [] };
11614
- const result = (0, import_node_child_process16.spawnSync)("devsy", ["workspace", ...args, ...binding.args], {
12162
+ const result = (0, import_node_child_process17.spawnSync)("devsy", ["workspace", ...args, ...binding.args], {
11615
12163
  encoding: "utf-8",
11616
12164
  ...binding.env ? { env: binding.env } : {}
11617
12165
  });
@@ -11672,13 +12220,13 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
11672
12220
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
11673
12221
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
11674
12222
  }
11675
- var import_node_child_process16, import_node_fs27, import_node_path35, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
12223
+ var import_node_child_process17, import_node_fs29, import_node_path37, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11676
12224
  var init_devsy_mutation = __esm({
11677
12225
  "src/core/devsy-mutation.ts"() {
11678
12226
  "use strict";
11679
- import_node_child_process16 = require("child_process");
11680
- import_node_fs27 = __toESM(require("fs"));
11681
- import_node_path35 = __toESM(require("path"));
12227
+ import_node_child_process17 = require("child_process");
12228
+ import_node_fs29 = __toESM(require("fs"));
12229
+ import_node_path37 = __toESM(require("path"));
11682
12230
  init_devsy_agent();
11683
12231
  init_devsy_workspaces();
11684
12232
  init_file_lock();
@@ -11687,7 +12235,7 @@ var init_devsy_mutation = __esm({
11687
12235
  init_network_provider_effect();
11688
12236
  init_reliability_context();
11689
12237
  init_router();
11690
- DEVSY_MUTATION_LOCK_FILE = import_node_path35.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
12238
+ DEVSY_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
11691
12239
  DEVSY_MUTATION_WAIT_MS = 18e5;
11692
12240
  }
11693
12241
  });
@@ -12459,10 +13007,10 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12459
13007
  throw new Error("Lifecycle invocation was cancelled before dispatch.");
12460
13008
  if (process.platform === "win32")
12461
13009
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
12462
- const workerPath = import_node_path36.default.join(__dirname, "devrouter-lifecycle-worker.js");
12463
- if (!import_node_fs28.default.existsSync(workerPath))
13010
+ const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
13011
+ if (!import_node_fs30.default.existsSync(workerPath))
12464
13012
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
12465
- const child = (0, import_node_child_process17.fork)(workerPath, [], {
13013
+ const child = (0, import_node_child_process18.fork)(workerPath, [], {
12466
13014
  detached: true,
12467
13015
  stdio: supervision ? ["ignore", "pipe", "pipe", "ipc"] : ["inherit", "inherit", "inherit", "ipc"],
12468
13016
  execArgv: []
@@ -12689,14 +13237,14 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12689
13237
  function newLifecycleIds() {
12690
13238
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
12691
13239
  }
12692
- var import_node_child_process17, import_node_crypto16, import_node_fs28, import_node_path36, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
13240
+ var import_node_child_process18, import_node_crypto16, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
12693
13241
  var init_reliability_worker = __esm({
12694
13242
  "src/core/reliability-worker.ts"() {
12695
13243
  "use strict";
12696
- import_node_child_process17 = require("child_process");
12697
- import_node_crypto16 = require("crypto");
12698
- import_node_fs28 = __toESM(require("fs"));
12699
- import_node_path36 = __toESM(require("path"));
13244
+ import_node_child_process18 = require("child_process");
13245
+ import_node_crypto16 = require("crypto");
13246
+ import_node_fs30 = __toESM(require("fs"));
13247
+ import_node_path38 = __toESM(require("path"));
12700
13248
  init_file_lock();
12701
13249
  init_reliability_contract();
12702
13250
  init_reliability_model();
@@ -12811,173 +13359,9 @@ var init_reliability_worker = __esm({
12811
13359
  }
12812
13360
  });
12813
13361
 
12814
- // src/core/traefik-route-health.ts
12815
- function sleep(ms) {
12816
- return new Promise((resolve) => setTimeout(resolve, ms));
12817
- }
12818
- function inspectRouterApi(protocol) {
12819
- const result = (0, import_node_child_process18.spawnSync)(
12820
- "curl",
12821
- [
12822
- "--silent",
12823
- "--show-error",
12824
- "--fail",
12825
- "--max-time",
12826
- ROUTER_API_REQUEST_TIMEOUT_SECONDS,
12827
- `${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
12828
- ],
12829
- { encoding: "utf-8" }
12830
- );
12831
- if (result.status !== 0) {
12832
- return {
12833
- ok: false,
12834
- details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
12835
- };
12836
- }
12837
- let value;
12838
- try {
12839
- value = JSON.parse(result.stdout);
12840
- } catch {
12841
- return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
12842
- }
12843
- if (!Array.isArray(value)) {
12844
- return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
12845
- }
12846
- if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
12847
- return {
12848
- ok: false,
12849
- details: `${protocol.toUpperCase()} router API returned malformed entries`
12850
- };
12851
- return {
12852
- ok: true,
12853
- reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
12854
- names: new Set(
12855
- value.flatMap((item) => {
12856
- if (!item || typeof item !== "object") return [];
12857
- const name = item.name;
12858
- return typeof name === "string" ? [name] : [];
12859
- })
12860
- )
12861
- };
12862
- }
12863
- function isRecord5(value) {
12864
- return value !== null && typeof value === "object" && !Array.isArray(value);
12865
- }
12866
- function inspectExpectedRoutes(routes, expectation) {
12867
- const mismatched = [];
12868
- const failures = [];
12869
- for (const protocol of ["http", "tcp"]) {
12870
- const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
12871
- if (expected.length === 0) continue;
12872
- const result = inspectRouterApi(protocol);
12873
- if (!result.ok) {
12874
- failures.push(result.details);
12875
- mismatched.push(...expected);
12876
- continue;
12877
- }
12878
- const unexpected = expected.filter(
12879
- (name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
12880
- );
12881
- const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
12882
- if (boundedAbsenceIsUncertain) {
12883
- failures.push(
12884
- `${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
12885
- );
12886
- }
12887
- mismatched.push(...unexpected);
12888
- if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
12889
- mismatched.push(...expected);
12890
- }
12891
- }
12892
- return mismatched.length === 0 ? { ok: true } : {
12893
- ok: false,
12894
- routes: mismatched,
12895
- details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
12896
- };
12897
- }
12898
- async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
12899
- const deadline = Date.now() + timeoutMs;
12900
- let result;
12901
- do {
12902
- result = inspectExpectedRoutes(routes, expectation);
12903
- if (result.ok) return result;
12904
- if (Date.now() < deadline) {
12905
- await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
12906
- }
12907
- } while (Date.now() < deadline);
12908
- return result;
12909
- }
12910
- async function ensureTraefikRouteExpectation(routes, expectation, options) {
12911
- if (routes.length === 0) return { restarted: false };
12912
- const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
12913
- const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
12914
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
12915
- const initial = await waitForExpectedRoutes(
12916
- routes,
12917
- expectation,
12918
- initialTimeoutMs,
12919
- pollIntervalMs
12920
- );
12921
- if (initial.ok) return { restarted: false };
12922
- if (options.allowRestart === false)
12923
- throw new Error(
12924
- "Traefik routes did not reload; shared router restart is disabled for this operation."
12925
- );
12926
- import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12927
- return withFileLock(
12928
- ROUTER_RELOAD_LOCK_FILE,
12929
- {
12930
- activity: "Traefik route reload recovery",
12931
- waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
12932
- fair: true,
12933
- onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
12934
- },
12935
- async () => {
12936
- const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
12937
- if (recheck.ok) return { restarted: false };
12938
- claimLifecycleEffect();
12939
- restartRouterStack();
12940
- const recovered = await waitForExpectedRoutes(
12941
- routes,
12942
- expectation,
12943
- recoveryTimeoutMs,
12944
- pollIntervalMs
12945
- );
12946
- if (recovered.ok) return { restarted: true };
12947
- const action2 = expectation === "loaded" ? "load" : "remove";
12948
- throw new Error(
12949
- `Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
12950
- );
12951
- }
12952
- );
12953
- }
12954
- async function ensureTraefikRoutesRemoved(routes, options = {}) {
12955
- return ensureTraefikRouteExpectation(routes, "removed", options);
12956
- }
12957
- var import_node_child_process18, import_node_fs29, import_node_path37, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
12958
- var init_traefik_route_health = __esm({
12959
- "src/core/traefik-route-health.ts"() {
12960
- "use strict";
12961
- import_node_child_process18 = require("child_process");
12962
- import_node_fs29 = __toESM(require("fs"));
12963
- import_node_path37 = __toESM(require("path"));
12964
- init_file_lock();
12965
- init_host_routes();
12966
- init_reliability_context();
12967
- init_router();
12968
- ROUTER_API_BASE = "http://127.0.0.1:8080/api";
12969
- ROUTER_RELOAD_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "router-reload.lock");
12970
- ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
12971
- DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
12972
- DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
12973
- DEFAULT_POLL_INTERVAL_MS = 250;
12974
- ROUTER_API_PAGE_SIZE = 1e3;
12975
- }
12976
- });
12977
-
12978
13362
  // src/core/devpod-mutation.ts
12979
13363
  function withMutationLock2(activity, target, operation) {
12980
- import_node_fs30.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13364
+ import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12981
13365
  return withFileLockSync(
12982
13366
  DEVPOD_MUTATION_LOCK_FILE,
12983
13367
  {
@@ -13067,13 +13451,13 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
13067
13451
  resetWorkspaceRuntimeCaches();
13068
13452
  return result;
13069
13453
  }
13070
- var import_node_child_process19, import_node_fs30, import_node_path38, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13454
+ var import_node_child_process19, import_node_fs31, import_node_path39, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13071
13455
  var init_devpod_mutation = __esm({
13072
13456
  "src/core/devpod-mutation.ts"() {
13073
13457
  "use strict";
13074
13458
  import_node_child_process19 = require("child_process");
13075
- import_node_fs30 = __toESM(require("fs"));
13076
- import_node_path38 = __toESM(require("path"));
13459
+ import_node_fs31 = __toESM(require("fs"));
13460
+ import_node_path39 = __toESM(require("path"));
13077
13461
  init_devpod_workspaces();
13078
13462
  init_devsy_mutation();
13079
13463
  init_file_lock();
@@ -13082,7 +13466,7 @@ var init_devpod_mutation = __esm({
13082
13466
  init_reliability_context();
13083
13467
  init_router();
13084
13468
  init_workspace_runtime();
13085
- DEVPOD_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13469
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13086
13470
  DEVPOD_MUTATION_WAIT_MS = 18e5;
13087
13471
  }
13088
13472
  });
@@ -13288,8 +13672,8 @@ function renderManagedComposeModel(plan, workspace) {
13288
13672
  }
13289
13673
  }
13290
13674
  function attributeHolderWorktree(workingDir) {
13291
- if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13292
- return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13675
+ if (import_node_path40.default.basename(workingDir) !== ".devcontainer") return void 0;
13676
+ return comparableWorkspacePath(import_node_path40.default.dirname(workingDir));
13293
13677
  }
13294
13678
  function holderAttribution(holder, repoPath) {
13295
13679
  const attribution = {};
@@ -13321,7 +13705,7 @@ function detectHostPortClaimConflicts(options) {
13321
13705
  );
13322
13706
  if (desired.length === 0) return [];
13323
13707
  const holders = listHolderSnapshots();
13324
- const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13708
+ const devcontainerDir = import_node_path40.default.join(options.repoPath, ".devcontainer");
13325
13709
  const conflicts = [];
13326
13710
  for (const binding of desired) {
13327
13711
  for (const holder of holders) {
@@ -13343,12 +13727,12 @@ function detectHostPortClaimConflicts(options) {
13343
13727
  }
13344
13728
  return conflicts;
13345
13729
  }
13346
- var import_node_child_process20, import_node_path39, COMPOSE_RENDER_TIMEOUT_MS, COMPOSE_RENDER_MAX_BUFFER, DOCKER_TIMEOUT_MS, DOCKER_MAX_BUFFER, MAX_DESIRED_HOST_PORTS, MAX_RUNNING_HOLDERS, HOST_PORT_INSPECT_TEMPLATE;
13730
+ var import_node_child_process20, import_node_path40, COMPOSE_RENDER_TIMEOUT_MS, COMPOSE_RENDER_MAX_BUFFER, DOCKER_TIMEOUT_MS, DOCKER_MAX_BUFFER, MAX_DESIRED_HOST_PORTS, MAX_RUNNING_HOLDERS, HOST_PORT_INSPECT_TEMPLATE;
13347
13731
  var init_host_port_claims = __esm({
13348
13732
  "src/core/host-port-claims.ts"() {
13349
13733
  "use strict";
13350
13734
  import_node_child_process20 = require("child_process");
13351
- import_node_path39 = __toESM(require("path"));
13735
+ import_node_path40 = __toESM(require("path"));
13352
13736
  init_devcontainer_profile();
13353
13737
  init_network_effect_scope();
13354
13738
  init_workspace();
@@ -13522,20 +13906,20 @@ var init_http_route_probe = __esm({
13522
13906
  });
13523
13907
 
13524
13908
  // src/core/managed-host-preparation.ts
13525
- var import_node_child_process22, import_node_fs31;
13909
+ var import_node_child_process22, import_node_fs32;
13526
13910
  var init_managed_host_preparation = __esm({
13527
13911
  "src/core/managed-host-preparation.ts"() {
13528
13912
  "use strict";
13529
13913
  import_node_child_process22 = require("child_process");
13530
- import_node_fs31 = __toESM(require("fs"));
13914
+ import_node_fs32 = __toESM(require("fs"));
13531
13915
  init_repo_config();
13532
13916
  }
13533
13917
  });
13534
13918
 
13535
13919
  // src/core/managed-post-start.ts
13536
13920
  function readRegularFileBytes(filePath) {
13537
- if (!import_node_fs32.default.existsSync(filePath) || !import_node_fs32.default.lstatSync(filePath).isFile()) return void 0;
13538
- return import_node_fs32.default.readFileSync(filePath);
13921
+ if (!import_node_fs33.default.existsSync(filePath) || !import_node_fs33.default.lstatSync(filePath).isFile()) return void 0;
13922
+ return import_node_fs33.default.readFileSync(filePath);
13539
13923
  }
13540
13924
  function readRegularFile(filePath) {
13541
13925
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -13544,11 +13928,11 @@ function adapterFingerprint(adapter) {
13544
13928
  return (0, import_node_crypto17.createHash)("sha256").update(adapter).digest("hex");
13545
13929
  }
13546
13930
  function resolveManagedPostStartPlan(repoPath) {
13547
- const adapterPath = import_node_path40.default.join(repoPath, MANAGED_ADAPTER_PATH);
13931
+ const adapterPath = import_node_path41.default.join(repoPath, MANAGED_ADAPTER_PATH);
13548
13932
  const adapterBytes = readRegularFileBytes(adapterPath);
13549
13933
  const adapterText = adapterBytes?.toString("utf-8");
13550
- const dockerfilePath = import_node_path40.default.join(repoPath, ".devcontainer", "Dockerfile");
13551
- const devcontainerPath = import_node_path40.default.join(repoPath, ".devcontainer", "devcontainer.json");
13934
+ const dockerfilePath = import_node_path41.default.join(repoPath, ".devcontainer", "Dockerfile");
13935
+ const devcontainerPath = import_node_path41.default.join(repoPath, ".devcontainer", "devcontainer.json");
13552
13936
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
13553
13937
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
13554
13938
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -13631,14 +14015,14 @@ function runManagedProcessAction(options) {
13631
14015
  }
13632
14016
  return "drifted";
13633
14017
  }
13634
- var import_node_child_process23, import_node_crypto17, import_node_fs32, import_node_path40, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
14018
+ var import_node_child_process23, import_node_crypto17, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13635
14019
  var init_managed_post_start = __esm({
13636
14020
  "src/core/managed-post-start.ts"() {
13637
14021
  "use strict";
13638
14022
  import_node_child_process23 = require("child_process");
13639
14023
  import_node_crypto17 = require("crypto");
13640
- import_node_fs32 = __toESM(require("fs"));
13641
- import_node_path40 = __toESM(require("path"));
14024
+ import_node_fs33 = __toESM(require("fs"));
14025
+ import_node_path41 = __toESM(require("path"));
13642
14026
  init_devcontainer_config();
13643
14027
  init_network_effect_scope();
13644
14028
  MANAGED_MARKER = "devrouter:managed devcontainer";
@@ -14577,26 +14961,26 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14577
14961
  try {
14578
14962
  if (!endpoint.startsWith("unix:///")) return false;
14579
14963
  const socket = endpoint.slice("unix://".length);
14580
- const table = import_node_fs33.default.readFileSync(import_node_path41.default.join(procRoot, "net/unix"), "utf8");
14964
+ const table = import_node_fs34.default.readFileSync(import_node_path42.default.join(procRoot, "net/unix"), "utf8");
14581
14965
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14582
14966
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14583
14967
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14584
14968
  const inode = `socket:[${rows2[0][6]}]`;
14585
- const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path41.default.join(procRoot, "self/ns/net"));
14586
- const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14969
+ const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path42.default.join(procRoot, "self/ns/net"));
14970
+ const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14587
14971
  if (pids.length > 4096) return false;
14588
14972
  for (const pid of pids) {
14589
14973
  if (Date.now() > deadline) return false;
14590
14974
  try {
14591
- const root = import_node_path41.default.join(procRoot, pid);
14592
- if (import_node_path41.default.basename(import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "exe"))) !== "dockerd") continue;
14593
- if (import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "ns/net")) !== ownNamespace) continue;
14594
- const descriptors = import_node_fs33.default.readdirSync(import_node_path41.default.join(root, "fd"));
14975
+ const root = import_node_path42.default.join(procRoot, pid);
14976
+ if (import_node_path42.default.basename(import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "exe"))) !== "dockerd") continue;
14977
+ if (import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "ns/net")) !== ownNamespace) continue;
14978
+ const descriptors = import_node_fs34.default.readdirSync(import_node_path42.default.join(root, "fd"));
14595
14979
  if (descriptors.length > 4096) continue;
14596
14980
  if (descriptors.some((fd) => {
14597
14981
  if (Date.now() > deadline) return false;
14598
14982
  try {
14599
- return import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "fd", fd)) === inode;
14983
+ return import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "fd", fd)) === inode;
14600
14984
  } catch {
14601
14985
  return false;
14602
14986
  }
@@ -14610,12 +14994,12 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14610
14994
  }
14611
14995
  return false;
14612
14996
  }
14613
- var import_node_fs33, import_node_path41;
14997
+ var import_node_fs34, import_node_path42;
14614
14998
  var init_network_local_daemon = __esm({
14615
14999
  "src/core/network-local-daemon.ts"() {
14616
15000
  "use strict";
14617
- import_node_fs33 = __toESM(require("fs"));
14618
- import_node_path41 = __toESM(require("path"));
15001
+ import_node_fs34 = __toESM(require("fs"));
15002
+ import_node_path42 = __toESM(require("path"));
14619
15003
  }
14620
15004
  });
14621
15005
 
@@ -14731,14 +15115,14 @@ var init_network_compose = __esm({
14731
15115
  });
14732
15116
 
14733
15117
  // src/core/network-compose-files.ts
14734
- var import_node_child_process26, import_node_crypto19, import_node_fs34, import_node_path42, import_yaml4, MAX_BYTES3;
15118
+ var import_node_child_process26, import_node_crypto19, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
14735
15119
  var init_network_compose_files = __esm({
14736
15120
  "src/core/network-compose-files.ts"() {
14737
15121
  "use strict";
14738
15122
  import_node_child_process26 = require("child_process");
14739
15123
  import_node_crypto19 = require("crypto");
14740
- import_node_fs34 = __toESM(require("fs"));
14741
- import_node_path42 = __toESM(require("path"));
15124
+ import_node_fs35 = __toESM(require("fs"));
15125
+ import_node_path43 = __toESM(require("path"));
14742
15126
  import_yaml4 = __toESM(require("yaml"));
14743
15127
  init_atomic_file();
14744
15128
  init_devcontainer_profile();
@@ -14842,13 +15226,13 @@ function resolveLinkedTarget(repoPath) {
14842
15226
  gitCommonDir: resolveGitCommonDir(repoPath)
14843
15227
  };
14844
15228
  }
14845
- var import_node_child_process28, import_node_fs35, import_node_path43;
15229
+ var import_node_child_process28, import_node_fs36, import_node_path44;
14846
15230
  var init_workspace_ensure = __esm({
14847
15231
  "src/core/workspace-ensure.ts"() {
14848
15232
  "use strict";
14849
15233
  import_node_child_process28 = require("child_process");
14850
- import_node_fs35 = __toESM(require("fs"));
14851
- import_node_path43 = __toESM(require("path"));
15234
+ import_node_fs36 = __toESM(require("fs"));
15235
+ import_node_path44 = __toESM(require("path"));
14852
15236
  init_devcontainer_profile();
14853
15237
  init_devpod_environment();
14854
15238
  init_devpod_mutation();
@@ -14984,7 +15368,7 @@ function prepareManagedLifecycleOperation(input2) {
14984
15368
  };
14985
15369
  });
14986
15370
  }
14987
- function assertCurrentCapacityController(expected, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
15371
+ function assertCurrentCapacityController(expected, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
14988
15372
  const current = new ControllerStore(directory).read();
14989
15373
  if (!expected || current?.store !== expected.store || current.epoch !== expected.epoch)
14990
15374
  throw new Error("Capacity controller incarnation changed.");
@@ -15036,7 +15420,7 @@ function prepareRecoveryLifecycleOperation(input2) {
15036
15420
  });
15037
15421
  }
15038
15422
  function settlePreparedLifecycleCapacity(input2) {
15039
- const directory = input2.directory ?? import_node_path44.default.join(DEVROUTER_HOME, "controller");
15423
+ const directory = input2.directory ?? import_node_path45.default.join(DEVROUTER_HOME, "controller");
15040
15424
  const store = new CapacityStore(directory);
15041
15425
  for (let attempt = 0; attempt < 3; attempt++) {
15042
15426
  const snapshot = store.read();
@@ -15250,7 +15634,7 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
15250
15634
  removeSignals();
15251
15635
  }
15252
15636
  }
15253
- function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15637
+ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15254
15638
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
15255
15639
  const capacity = new CapacityStore(directory);
15256
15640
  const snapshot = capacity.read();
@@ -15350,7 +15734,7 @@ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs,
15350
15734
  return decision;
15351
15735
  }
15352
15736
  function capacityPolicyTargets(repoPath) {
15353
- const policy = readCapacityPolicy(import_node_path44.default.join(DEVROUTER_HOME, "controller"));
15737
+ const policy = readCapacityPolicy(import_node_path45.default.join(DEVROUTER_HOME, "controller"));
15354
15738
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
15355
15739
  }
15356
15740
  function controllerWaitSeconds(deadline) {
@@ -15359,7 +15743,7 @@ function controllerWaitSeconds(deadline) {
15359
15743
  async function superviseThroughController(input2) {
15360
15744
  if (input2.kind === "exec" && !input2.command)
15361
15745
  throw new Error("Exec requires a command for controller admission.");
15362
- const directory = import_node_path44.default.join(DEVROUTER_HOME, "controller");
15746
+ const directory = import_node_path45.default.join(DEVROUTER_HOME, "controller");
15363
15747
  const cancellation = { requested: false };
15364
15748
  const removeSignals = installLifecycleWaitSignals(cancellation);
15365
15749
  const cancelled = () => {
@@ -15431,7 +15815,7 @@ async function superviseThroughController(input2) {
15431
15815
  removeSignals();
15432
15816
  }
15433
15817
  }
15434
- function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15818
+ function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15435
15819
  return updateReliabilityOperation(request2.identity, (record5) => {
15436
15820
  const binding = record5.capacity;
15437
15821
  if (!matchesFence(record5, request2.fence) || !binding || binding.operationId !== request2.operationId || binding.workerId !== request2.workerId || binding.controller?.store !== controller.store || binding.controller?.epoch !== controller.epoch)
@@ -15519,7 +15903,7 @@ function retireQueuedLifecycle(request2, supersededOnly = false) {
15519
15903
  return true;
15520
15904
  });
15521
15905
  }
15522
- function bindLifecycleCapacity(request2, binding, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
15906
+ function bindLifecycleCapacity(request2, binding, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
15523
15907
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
15524
15908
  updateReliabilityOperation(request2.identity, (record5) => {
15525
15909
  if (!matchesFence(record5, request2.fence) || record5.worker || record5.state.operation?.id !== request2.operationId || record5.state.operation.status !== "NOT_STARTED" || record5.state.operation.drained)
@@ -15534,12 +15918,12 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path44
15534
15918
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
15535
15919
  });
15536
15920
  }
15537
- var import_node_crypto21, import_node_path44, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
15921
+ var import_node_crypto21, import_node_path45, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
15538
15922
  var init_reliability_lifecycle = __esm({
15539
15923
  "src/core/reliability-lifecycle.ts"() {
15540
15924
  "use strict";
15541
15925
  import_node_crypto21 = require("crypto");
15542
- import_node_path44 = __toESM(require("path"));
15926
+ import_node_path45 = __toESM(require("path"));
15543
15927
  import_node_util10 = require("util");
15544
15928
  init_capacity_accounting();
15545
15929
  init_capacity_policy();
@@ -15552,6 +15936,7 @@ var init_reliability_lifecycle = __esm({
15552
15936
  init_devsy_mutation();
15553
15937
  init_file_lock();
15554
15938
  init_host_routes();
15939
+ init_managed_devsy_stop();
15555
15940
  init_managed_runtime_state();
15556
15941
  init_managed_stop_recovery();
15557
15942
  init_reliability_context();
@@ -16031,7 +16416,7 @@ function createCapacityController(options) {
16031
16416
  const record5 = readReliabilityOperation(identity2);
16032
16417
  if (!record5?.capacity || !record5.preparation && !record5.capacity.execSteady || record5.worker || !record5.state.operation?.drained || !["ensure", "exec"].includes(record5.state.operation.kind))
16033
16418
  continue;
16034
- const bytes2 = readControllerEvidence(import_node_path45.default.join(enrollment.repoPath, ".devrouter.yml"));
16419
+ const bytes2 = readControllerEvidence(import_node_path46.default.join(enrollment.repoPath, ".devrouter.yml"));
16035
16420
  const estimates = loadRepoConfig(enrollment.repoPath, () => bytes2).capacity;
16036
16421
  if (!estimates) continue;
16037
16422
  settlePreparedLifecycleCapacity({
@@ -16272,12 +16657,12 @@ function createCapacityController(options) {
16272
16657
  }
16273
16658
  };
16274
16659
  }
16275
- var import_node_crypto22, import_node_path45, import_node_util12;
16660
+ var import_node_crypto22, import_node_path46, import_node_util12;
16276
16661
  var init_capacity_controller = __esm({
16277
16662
  "src/core/capacity-controller.ts"() {
16278
16663
  "use strict";
16279
16664
  import_node_crypto22 = require("crypto");
16280
- import_node_path45 = __toESM(require("path"));
16665
+ import_node_path46 = __toESM(require("path"));
16281
16666
  import_node_util12 = require("util");
16282
16667
  init_capacity_docker_probe();
16283
16668
  init_capacity_enrollment();
@@ -16637,12 +17022,12 @@ async function containers(project, signal) {
16637
17022
  }
16638
17023
  return values.sort((a, b) => a.id.localeCompare(b.id));
16639
17024
  }
16640
- var import_node_crypto24, import_node_path46, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
17025
+ var import_node_crypto24, import_node_path47, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
16641
17026
  var init_controller_observation = __esm({
16642
17027
  "src/core/controller-observation.ts"() {
16643
17028
  "use strict";
16644
17029
  import_node_crypto24 = require("crypto");
16645
- import_node_path46 = __toESM(require("path"));
17030
+ import_node_path47 = __toESM(require("path"));
16646
17031
  init_controller_binding();
16647
17032
  init_controller_monitor();
16648
17033
  init_controller_probe();
@@ -16685,14 +17070,14 @@ var init_controller_observation = __esm({
16685
17070
  )).trim().split("\n");
16686
17071
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
16687
17072
  const [gitDir, commonDir] = metadata;
16688
- const sourcePath = import_node_path46.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
17073
+ const sourcePath = import_node_path47.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
16689
17074
  const files = [
16690
- import_node_path46.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
16691
- import_node_path46.default.join(environment.repoPath, ".devrouter.yml"),
16692
- import_node_path46.default.join(gitDir, "devrouter-workspace"),
17075
+ import_node_path47.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
17076
+ import_node_path47.default.join(environment.repoPath, ".devrouter.yml"),
17077
+ import_node_path47.default.join(gitDir, "devrouter-workspace"),
16693
17078
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
16694
17079
  sourcePath,
16695
- import_node_path46.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
17080
+ import_node_path47.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
16696
17081
  ];
16697
17082
  const persisted = captureControllerEvidence(files);
16698
17083
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -16717,7 +17102,7 @@ var init_controller_observation = __esm({
16717
17102
  if (!journal) throw new Error("Observation journal is unavailable.");
16718
17103
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
16719
17104
  const before = await containers(state.composeProject, signal);
16720
- const composeDirectory = import_node_path46.default.join(environment.repoPath, ".devcontainer");
17105
+ const composeDirectory = import_node_path47.default.join(environment.repoPath, ".devcontainer");
16721
17106
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
16722
17107
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
16723
17108
  throw new Error("Observation Compose source is unavailable.");
@@ -16734,7 +17119,7 @@ var init_controller_observation = __esm({
16734
17119
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
16735
17120
  throw new Error("Observation Compose ownership is unavailable.");
16736
17121
  const files2 = recorded.split(",").map((file) => file.trim());
16737
- if (files2.some((file) => !import_node_path46.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
17122
+ if (files2.some((file) => !import_node_path47.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16738
17123
  repoPath: environment.repoPath,
16739
17124
  service: container.labels["com.docker.compose.service"] ?? "",
16740
17125
  composeProject: state.composeProject,
@@ -17099,26 +17484,26 @@ var init_controller_sessions = __esm({
17099
17484
 
17100
17485
  // src/core/controller-server.ts
17101
17486
  function privateDirectory(directory) {
17102
- const absolute2 = import_node_path47.default.resolve(directory);
17103
- let current = import_node_path47.default.parse(absolute2).root;
17104
- for (const part of absolute2.slice(current.length).split(import_node_path47.default.sep)) {
17105
- current = import_node_path47.default.join(current, part);
17487
+ const absolute2 = import_node_path48.default.resolve(directory);
17488
+ let current = import_node_path48.default.parse(absolute2).root;
17489
+ for (const part of absolute2.slice(current.length).split(import_node_path48.default.sep)) {
17490
+ current = import_node_path48.default.join(current, part);
17106
17491
  try {
17107
- const stat2 = import_node_fs36.default.lstatSync(current);
17492
+ const stat2 = import_node_fs37.default.lstatSync(current);
17108
17493
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
17109
17494
  throw new Error("Unsafe controller directory.");
17110
17495
  } catch (error) {
17111
17496
  if (error.code !== "ENOENT") throw error;
17112
- import_node_fs36.default.mkdirSync(current, { mode: 448 });
17497
+ import_node_fs37.default.mkdirSync(current, { mode: 448 });
17113
17498
  }
17114
17499
  }
17115
- const stat = import_node_fs36.default.lstatSync(absolute2);
17500
+ const stat = import_node_fs37.default.lstatSync(absolute2);
17116
17501
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
17117
17502
  throw new Error("Controller directory is not private.");
17118
17503
  }
17119
17504
  function validateOwnedFile(file, socket) {
17120
17505
  try {
17121
- const stat = import_node_fs36.default.lstatSync(file);
17506
+ const stat = import_node_fs37.default.lstatSync(file);
17122
17507
  if (stat.uid !== process.getuid?.() || (socket ? !stat.isSocket() : !stat.isFile()) || (stat.mode & 63) !== 0)
17123
17508
  throw new Error("Unsafe controller artifact.");
17124
17509
  return true;
@@ -17130,19 +17515,19 @@ function validateOwnedFile(file, socket) {
17130
17515
  async function runController(options) {
17131
17516
  if (options.operations && options.createOperations)
17132
17517
  throw new Error("Controller operations have multiple owners.");
17133
- const socketPath = import_node_path47.default.join(options.directory, "control.sock");
17518
+ const socketPath = import_node_path48.default.join(options.directory, "control.sock");
17134
17519
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
17135
17520
  privateDirectory(options.directory);
17136
- const lockPath = import_node_path47.default.join(options.directory, "owner.lock");
17521
+ const lockPath = import_node_path48.default.join(options.directory, "owner.lock");
17137
17522
  try {
17138
- const stat = import_node_fs36.default.lstatSync(lockPath);
17523
+ const stat = import_node_fs37.default.lstatSync(lockPath);
17139
17524
  if (!stat.isFile() || stat.uid !== process.getuid?.())
17140
17525
  throw new Error("Unsafe controller owner lock.");
17141
17526
  } catch (error) {
17142
17527
  if (error.code !== "ENOENT") throw error;
17143
17528
  }
17144
17529
  await withFileLock(lockPath, { activity: "controller ownership", waitMs: 0 }, async () => {
17145
- if (validateOwnedFile(socketPath, true)) import_node_fs36.default.unlinkSync(socketPath);
17530
+ if (validateOwnedFile(socketPath, true)) import_node_fs37.default.unlinkSync(socketPath);
17146
17531
  const sessions = new ControllerSessions(new ControllerStore(options.directory));
17147
17532
  const incarnation = sessions.read();
17148
17533
  let startupAvailable = true;
@@ -17531,7 +17916,7 @@ async function runController(options) {
17531
17916
  resolve();
17532
17917
  });
17533
17918
  });
17534
- import_node_fs36.default.chmodSync(socketPath, 384);
17919
+ import_node_fs37.default.chmodSync(socketPath, 384);
17535
17920
  await new Promise((resolve) => {
17536
17921
  finish = resolve;
17537
17922
  options.signal.addEventListener("abort", shutdown, { once: true });
@@ -17550,13 +17935,13 @@ async function runController(options) {
17550
17935
  }
17551
17936
  });
17552
17937
  }
17553
- var import_node_fs36, import_node_net4, import_node_path47, FRAME_BYTES;
17938
+ var import_node_fs37, import_node_net4, import_node_path48, FRAME_BYTES;
17554
17939
  var init_controller_server = __esm({
17555
17940
  "src/core/controller-server.ts"() {
17556
17941
  "use strict";
17557
- import_node_fs36 = __toESM(require("fs"));
17942
+ import_node_fs37 = __toESM(require("fs"));
17558
17943
  import_node_net4 = __toESM(require("net"));
17559
- import_node_path47 = __toESM(require("path"));
17944
+ import_node_path48 = __toESM(require("path"));
17560
17945
  init_controller_monitor();
17561
17946
  init_controller_protocol();
17562
17947
  init_controller_sessions();
@@ -17573,7 +17958,7 @@ __export(controller_exports, {
17573
17958
  runControllerCommand: () => runControllerCommand
17574
17959
  });
17575
17960
  async function runControllerCommand(method, options, repo, dependencies = {}) {
17576
- const directory = import_node_path48.default.join(DEVROUTER_HOME, "controller");
17961
+ const directory = import_node_path49.default.join(DEVROUTER_HOME, "controller");
17577
17962
  try {
17578
17963
  if (method === "run") {
17579
17964
  const controller = new AbortController();
@@ -17608,7 +17993,7 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
17608
17993
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
17609
17994
  await controllerRequest(
17610
17995
  directory,
17611
- { method, ...fields2, ...repo ? { path: import_node_path48.default.resolve(repo) } : {} },
17996
+ { method, ...fields2, ...repo ? { path: import_node_path49.default.resolve(repo) } : {} },
17612
17997
  (value) => process.stdout.write(`${JSON.stringify(value)}
17613
17998
  `)
17614
17999
  );
@@ -17623,11 +18008,11 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
17623
18008
  process.exitCode = 1;
17624
18009
  }
17625
18010
  }
17626
- var import_node_path48;
18011
+ var import_node_path49;
17627
18012
  var init_controller = __esm({
17628
18013
  "src/commands/controller.ts"() {
17629
18014
  "use strict";
17630
- import_node_path48 = __toESM(require("path"));
18015
+ import_node_path49 = __toESM(require("path"));
17631
18016
  init_capacity_collector();
17632
18017
  init_capacity_controller();
17633
18018
  init_capacity_policy();
@@ -17678,28 +18063,28 @@ function replaceDevrouterSection(content) {
17678
18063
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
17679
18064
  }
17680
18065
  function writeRepoFile(repoPath, relPath, content) {
17681
- const absolutePath = (0, import_node_path49.join)(repoPath, relPath);
17682
- (0, import_node_fs37.mkdirSync)((0, import_node_path49.dirname)(absolutePath), { recursive: true });
17683
- (0, import_node_fs37.writeFileSync)(absolutePath, content, "utf-8");
18066
+ const absolutePath = (0, import_node_path50.join)(repoPath, relPath);
18067
+ (0, import_node_fs38.mkdirSync)((0, import_node_path50.dirname)(absolutePath), { recursive: true });
18068
+ (0, import_node_fs38.writeFileSync)(absolutePath, content, "utf-8");
17684
18069
  return absolutePath;
17685
18070
  }
17686
18071
  function ensureAgentsMdSection(repoPath) {
17687
- const filePath = (0, import_node_path49.join)(repoPath, AGENTS_MD);
17688
- if ((0, import_node_fs37.existsSync)(filePath)) {
17689
- const content = (0, import_node_fs37.readFileSync)(filePath, "utf-8");
18072
+ const filePath = (0, import_node_path50.join)(repoPath, AGENTS_MD);
18073
+ if ((0, import_node_fs38.existsSync)(filePath)) {
18074
+ const content = (0, import_node_fs38.readFileSync)(filePath, "utf-8");
17690
18075
  if (content.includes(DEVROUTER_SENTINEL)) {
17691
18076
  const updated = replaceDevrouterSection(content);
17692
18077
  if (updated === void 0 || updated === content) return { path: filePath, written: false };
17693
- (0, import_node_fs37.writeFileSync)(filePath, updated, "utf-8");
18078
+ (0, import_node_fs38.writeFileSync)(filePath, updated, "utf-8");
17694
18079
  return { path: filePath, written: true };
17695
18080
  }
17696
- (0, import_node_fs37.writeFileSync)(filePath, `${content.trimEnd()}
18081
+ (0, import_node_fs38.writeFileSync)(filePath, `${content.trimEnd()}
17697
18082
 
17698
18083
  ${buildDevrouterSection()}
17699
18084
  `, "utf-8");
17700
18085
  return { path: filePath, written: true };
17701
18086
  }
17702
- (0, import_node_fs37.writeFileSync)(filePath, `# AGENTS.md
18087
+ (0, import_node_fs38.writeFileSync)(filePath, `# AGENTS.md
17703
18088
 
17704
18089
  ${buildDevrouterSection()}
17705
18090
  `, "utf-8");
@@ -17709,12 +18094,12 @@ function ensureSkillFile(repoPath) {
17709
18094
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
17710
18095
  return { path: filePath, written: true };
17711
18096
  }
17712
- var import_node_fs37, import_node_path49, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
18097
+ var import_node_fs38, import_node_path50, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
17713
18098
  var init_agents_md = __esm({
17714
18099
  "src/core/agents-md.ts"() {
17715
18100
  "use strict";
17716
- import_node_fs37 = require("fs");
17717
- import_node_path49 = require("path");
18101
+ import_node_fs38 = require("fs");
18102
+ import_node_path50 = require("path");
17718
18103
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
17719
18104
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
17720
18105
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -18439,7 +18824,7 @@ var init_ai_prompt = __esm({
18439
18824
  },
18440
18825
  {
18441
18826
  command: "devrouter stop [path] [--delete] [--json]",
18442
- purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
18827
+ purpose: "Stop the exact checkout workspace runtime and routes, or prove absence after pre-registration failure; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
18443
18828
  },
18444
18829
  {
18445
18830
  command: "devrouter exec [path] -- <command...>",
@@ -18993,11 +19378,11 @@ function extractCurrentVersionFromRepoConfig(repoPath) {
18993
19378
  };
18994
19379
  }
18995
19380
  function readPromptDirectory(promptDirPath) {
18996
- if (!import_node_fs38.default.existsSync(promptDirPath)) {
19381
+ if (!import_node_fs39.default.existsSync(promptDirPath)) {
18997
19382
  throw new Error(`Missing ${UPGRADE_PROMPTS_DIR} directory at ${promptDirPath}.`);
18998
19383
  }
18999
19384
  const releases = [];
19000
- const files = import_node_fs38.default.readdirSync(promptDirPath, { withFileTypes: true });
19385
+ const files = import_node_fs39.default.readdirSync(promptDirPath, { withFileTypes: true });
19001
19386
  for (const file of files) {
19002
19387
  if (!file.isFile() || !file.name.endsWith(".md")) {
19003
19388
  continue;
@@ -19007,8 +19392,8 @@ function readPromptDirectory(promptDirPath) {
19007
19392
  continue;
19008
19393
  }
19009
19394
  const version = normalizeVersion(basename);
19010
- const promptPath = import_node_path50.default.join(promptDirPath, file.name);
19011
- const prompt = import_node_fs38.default.readFileSync(promptPath, "utf-8").trim();
19395
+ const promptPath = import_node_path51.default.join(promptDirPath, file.name);
19396
+ const prompt = import_node_fs39.default.readFileSync(promptPath, "utf-8").trim();
19012
19397
  releases.push({ version, prompt, promptPath });
19013
19398
  }
19014
19399
  const deduped = /* @__PURE__ */ new Map();
@@ -19029,19 +19414,19 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
19029
19414
  }
19030
19415
  function resolvePromptDirectory(explicitPath) {
19031
19416
  if (explicitPath) {
19032
- return import_node_path50.default.resolve(explicitPath);
19417
+ return import_node_path51.default.resolve(explicitPath);
19033
19418
  }
19034
- const entryFile = process.argv[1] ? import_node_path50.default.resolve(process.argv[1]) : __filename;
19035
- const entryDir = import_node_path50.default.dirname(entryFile);
19036
- const resolvedEntryDir = import_node_fs38.default.existsSync(entryFile) ? import_node_path50.default.dirname(import_node_fs38.default.realpathSync(entryFile)) : entryDir;
19419
+ const entryFile = process.argv[1] ? import_node_path51.default.resolve(process.argv[1]) : __filename;
19420
+ const entryDir = import_node_path51.default.dirname(entryFile);
19421
+ const resolvedEntryDir = import_node_fs39.default.existsSync(entryFile) ? import_node_path51.default.dirname(import_node_fs39.default.realpathSync(entryFile)) : entryDir;
19037
19422
  const candidates = [
19038
- import_node_path50.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
19039
- import_node_path50.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
19040
- import_node_path50.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
19041
- import_node_path50.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
19042
- import_node_path50.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
19423
+ import_node_path51.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
19424
+ import_node_path51.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
19425
+ import_node_path51.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
19426
+ import_node_path51.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
19427
+ import_node_path51.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
19043
19428
  ];
19044
- const existing = candidates.find((candidate) => import_node_fs38.default.existsSync(candidate));
19429
+ const existing = candidates.find((candidate) => import_node_fs39.default.existsSync(candidate));
19045
19430
  return existing ?? candidates[0];
19046
19431
  }
19047
19432
  function loadUpgradeCatalog(options = {}) {
@@ -19057,12 +19442,12 @@ function loadUpgradeCatalog(options = {}) {
19057
19442
  releases
19058
19443
  };
19059
19444
  }
19060
- var import_node_fs38, import_node_path50, SEMVER_RE, UPGRADE_PROMPTS_DIR;
19445
+ var import_node_fs39, import_node_path51, SEMVER_RE, UPGRADE_PROMPTS_DIR;
19061
19446
  var init_upgrade = __esm({
19062
19447
  "src/core/upgrade.ts"() {
19063
19448
  "use strict";
19064
- import_node_fs38 = __toESM(require("fs"));
19065
- import_node_path50 = __toESM(require("path"));
19449
+ import_node_fs39 = __toESM(require("fs"));
19450
+ import_node_path51 = __toESM(require("path"));
19066
19451
  init_repo_config();
19067
19452
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
19068
19453
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -19205,7 +19590,7 @@ function isLoopbackHost(host) {
19205
19590
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
19206
19591
  }
19207
19592
  function inspectCompose(composeFile, _workspace) {
19208
- if (!import_node_fs39.default.existsSync(composeFile)) {
19593
+ if (!import_node_fs40.default.existsSync(composeFile)) {
19209
19594
  return {
19210
19595
  aliases: [],
19211
19596
  publishedPorts: [],
@@ -19214,7 +19599,7 @@ function inspectCompose(composeFile, _workspace) {
19214
19599
  };
19215
19600
  }
19216
19601
  try {
19217
- const raw = import_node_fs39.default.readFileSync(composeFile, "utf-8");
19602
+ const raw = import_node_fs40.default.readFileSync(composeFile, "utf-8");
19218
19603
  const parsed = import_yaml5.default.parse(raw);
19219
19604
  const root = asRecord(parsed);
19220
19605
  const services = asRecord(root?.services);
@@ -19310,11 +19695,11 @@ function buildHostPortClaimsCheck(repoPath, workspace) {
19310
19695
  }
19311
19696
  }
19312
19697
  function buildDevcontainerChecks(repoPath, config, workspace) {
19313
- const devcontainerDir = import_node_path51.default.join(repoPath, ".devcontainer");
19314
- if (!import_node_fs39.default.existsSync(devcontainerDir)) {
19698
+ const devcontainerDir = import_node_path52.default.join(repoPath, ".devcontainer");
19699
+ if (!import_node_fs40.default.existsSync(devcontainerDir)) {
19315
19700
  return [];
19316
19701
  }
19317
- const compose = inspectCompose(import_node_path51.default.join(devcontainerDir, "docker-compose.yml"), workspace);
19702
+ const compose = inspectCompose(import_node_path52.default.join(devcontainerDir, "docker-compose.yml"), workspace);
19318
19703
  const checks = [];
19319
19704
  if (compose.parseError) {
19320
19705
  checks.push({
@@ -19341,9 +19726,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
19341
19726
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
19342
19727
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
19343
19728
  });
19344
- const dockerfilePath = import_node_path51.default.join(devcontainerDir, "Dockerfile");
19345
- const dockerfileExists = import_node_fs39.default.existsSync(dockerfilePath);
19346
- const dockerfile = dockerfileExists ? import_node_fs39.default.readFileSync(dockerfilePath, "utf-8") : "";
19729
+ const dockerfilePath = import_node_path52.default.join(devcontainerDir, "Dockerfile");
19730
+ const dockerfileExists = import_node_fs40.default.existsSync(dockerfilePath);
19731
+ const dockerfile = dockerfileExists ? import_node_fs40.default.readFileSync(dockerfilePath, "utf-8") : "";
19347
19732
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
19348
19733
  (artifact) => dockerfile.includes(artifact)
19349
19734
  );
@@ -19401,12 +19786,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
19401
19786
  });
19402
19787
  return checks;
19403
19788
  }
19404
- var import_node_fs39, import_node_path51, import_yaml5;
19789
+ var import_node_fs40, import_node_path52, import_yaml5;
19405
19790
  var init_devcontainer_diagnostics = __esm({
19406
19791
  "src/core/devcontainer-diagnostics.ts"() {
19407
19792
  "use strict";
19408
- import_node_fs39 = __toESM(require("fs"));
19409
- import_node_path51 = __toESM(require("path"));
19793
+ import_node_fs40 = __toESM(require("fs"));
19794
+ import_node_path52 = __toESM(require("path"));
19410
19795
  import_yaml5 = __toESM(require("yaml"));
19411
19796
  init_devcontainer_profile();
19412
19797
  init_host_port_claims();
@@ -19501,11 +19886,11 @@ function reconcileRouteRunConflict(repoPath, app) {
19501
19886
  }
19502
19887
  }
19503
19888
  }
19504
- var import_node_fs40;
19889
+ var import_node_fs41;
19505
19890
  var init_route_state = __esm({
19506
19891
  "src/core/route-state.ts"() {
19507
19892
  "use strict";
19508
- import_node_fs40 = __toESM(require("fs"));
19893
+ import_node_fs41 = __toESM(require("fs"));
19509
19894
  init_host_routes();
19510
19895
  init_workspace();
19511
19896
  }
@@ -19680,7 +20065,7 @@ function toRepoStatus(repoPath) {
19680
20065
  const resolvedRepoPath = resolveRepoPath(repoPath);
19681
20066
  const configPath = getRepoConfigPath(resolvedRepoPath);
19682
20067
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
19683
- const configExists = import_node_fs41.default.existsSync(configPath);
20068
+ const configExists = import_node_fs42.default.existsSync(configPath);
19684
20069
  if (!explicitRepo && !configExists) {
19685
20070
  return void 0;
19686
20071
  }
@@ -19828,11 +20213,11 @@ async function collectRouterStatus(repoPath) {
19828
20213
  }
19829
20214
  };
19830
20215
  }
19831
- var import_node_fs41;
20216
+ var import_node_fs42;
19832
20217
  var init_status = __esm({
19833
20218
  "src/core/status.ts"() {
19834
20219
  "use strict";
19835
- import_node_fs41 = __toESM(require("fs"));
20220
+ import_node_fs42 = __toESM(require("fs"));
19836
20221
  init_docker();
19837
20222
  init_managed_runtime_state();
19838
20223
  init_managed_runtime_status();
@@ -19906,12 +20291,12 @@ function parseMinimumNodeMajor(value) {
19906
20291
  return Number(match[1]);
19907
20292
  }
19908
20293
  function readPackageJson(repoPath) {
19909
- const packagePath = import_node_path52.default.join(repoPath, "package.json");
19910
- if (!import_node_fs42.default.existsSync(packagePath)) {
20294
+ const packagePath = import_node_path53.default.join(repoPath, "package.json");
20295
+ if (!import_node_fs43.default.existsSync(packagePath)) {
19911
20296
  return void 0;
19912
20297
  }
19913
20298
  try {
19914
- return JSON.parse(import_node_fs42.default.readFileSync(packagePath, "utf-8"));
20299
+ return JSON.parse(import_node_fs43.default.readFileSync(packagePath, "utf-8"));
19915
20300
  } catch {
19916
20301
  return void 0;
19917
20302
  }
@@ -20095,13 +20480,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
20095
20480
  checks.push(nodeToolchainCheck(repoPath));
20096
20481
  return checks;
20097
20482
  }
20098
- var import_node_child_process29, import_node_fs42, import_node_path52, DEVSY_DISABLE_TELEMETRY;
20483
+ var import_node_child_process29, import_node_fs43, import_node_path53, DEVSY_DISABLE_TELEMETRY;
20099
20484
  var init_tool_diagnostics = __esm({
20100
20485
  "src/core/tool-diagnostics.ts"() {
20101
20486
  "use strict";
20102
20487
  import_node_child_process29 = require("child_process");
20103
- import_node_fs42 = __toESM(require("fs"));
20104
- import_node_path52 = __toESM(require("path"));
20488
+ import_node_fs43 = __toESM(require("fs"));
20489
+ import_node_path53 = __toESM(require("path"));
20105
20490
  init_devsy_agent();
20106
20491
  init_workspace_runtime();
20107
20492
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -20118,10 +20503,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
20118
20503
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
20119
20504
  const comparableRepo = comparableWorkspacePath(repoPath);
20120
20505
  const comparableWorktree = comparableWorkspacePath(worktreePath);
20121
- const localRoot = import_node_path53.default.join(comparableRepo, "trees") + import_node_path53.default.sep;
20506
+ const localRoot = import_node_path54.default.join(comparableRepo, "trees") + import_node_path54.default.sep;
20122
20507
  if (comparableWorktree.startsWith(localRoot)) return true;
20123
- const legacyPrefix = `${import_node_path53.default.basename(comparableRepo)}-`;
20124
- return import_node_path53.default.dirname(comparableWorktree) === import_node_path53.default.dirname(comparableRepo) && import_node_path53.default.basename(comparableWorktree).startsWith(legacyPrefix);
20508
+ const legacyPrefix = `${import_node_path54.default.basename(comparableRepo)}-`;
20509
+ return import_node_path54.default.dirname(comparableWorktree) === import_node_path54.default.dirname(comparableRepo) && import_node_path54.default.basename(comparableWorktree).startsWith(legacyPrefix);
20125
20510
  }
20126
20511
  function previewActions(devpodStatus, routeCount, includeRecord) {
20127
20512
  const actions = [
@@ -20373,11 +20758,11 @@ function applyWorkspaceGc(plan) {
20373
20758
  candidates
20374
20759
  };
20375
20760
  }
20376
- var import_node_path53;
20761
+ var import_node_path54;
20377
20762
  var init_workspace_gc = __esm({
20378
20763
  "src/core/workspace-gc.ts"() {
20379
20764
  "use strict";
20380
- import_node_path53 = __toESM(require("path"));
20765
+ import_node_path54 = __toESM(require("path"));
20381
20766
  init_devpod_mutation();
20382
20767
  init_devpod_workspaces();
20383
20768
  init_host_routes();
@@ -20447,11 +20832,11 @@ function inspectPostgresCredentials(repoPath, config) {
20447
20832
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
20448
20833
  continue;
20449
20834
  }
20450
- if (!import_node_fs43.default.existsSync(absolutePath)) {
20835
+ if (!import_node_fs44.default.existsSync(absolutePath)) {
20451
20836
  continue;
20452
20837
  }
20453
20838
  try {
20454
- const raw = import_node_fs43.default.readFileSync(absolutePath, "utf-8");
20839
+ const raw = import_node_fs44.default.readFileSync(absolutePath, "utf-8");
20455
20840
  const parsed = import_yaml6.default.parse(raw);
20456
20841
  const root = asRecord2(parsed);
20457
20842
  const services = asRecord2(root?.services);
@@ -20731,7 +21116,7 @@ async function buildDoctorReport(options = {}) {
20731
21116
  const config = runtimeConfig.config;
20732
21117
  loadedConfig = config;
20733
21118
  loadedWorkspace = runtimeConfig.workspace;
20734
- const cliVersion = true ? "0.0.73" : "0.0.0-dev";
21119
+ const cliVersion = true ? "0.0.75" : "0.0.0-dev";
20735
21120
  const configVersion = config.devrouter?.version;
20736
21121
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
20737
21122
  addCheck(checks, {
@@ -20761,9 +21146,9 @@ async function buildDoctorReport(options = {}) {
20761
21146
  (app) => app.docker.composeFiles.map((filePath) => ({
20762
21147
  app: app.name,
20763
21148
  filePath,
20764
- absolutePath: import_node_path54.default.resolve(repo.path, filePath)
21149
+ absolutePath: import_node_path55.default.resolve(repo.path, filePath)
20765
21150
  }))
20766
- ).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
21151
+ ).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
20767
21152
  addCheck(checks, {
20768
21153
  id: "repo.compose-files",
20769
21154
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -20806,8 +21191,8 @@ async function buildDoctorReport(options = {}) {
20806
21191
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
20807
21192
  app: app.name,
20808
21193
  cwd: app.hostRun.cwd,
20809
- absolutePath: import_node_path54.default.resolve(repo.path, app.hostRun.cwd)
20810
- })).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
21194
+ absolutePath: import_node_path55.default.resolve(repo.path, app.hostRun.cwd)
21195
+ })).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
20811
21196
  addCheck(checks, {
20812
21197
  id: "repo.host-cwd",
20813
21198
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -20951,12 +21336,12 @@ async function buildDoctorReport(options = {}) {
20951
21336
  nextSteps
20952
21337
  };
20953
21338
  }
20954
- var import_node_fs43, import_node_path54, import_yaml6, POSTGRES_DEFAULTS;
21339
+ var import_node_fs44, import_node_path55, import_yaml6, POSTGRES_DEFAULTS;
20955
21340
  var init_doctor = __esm({
20956
21341
  "src/core/doctor.ts"() {
20957
21342
  "use strict";
20958
- import_node_fs43 = __toESM(require("fs"));
20959
- import_node_path54 = __toESM(require("path"));
21343
+ import_node_fs44 = __toESM(require("fs"));
21344
+ import_node_path55 = __toESM(require("path"));
20960
21345
  import_yaml6 = __toESM(require("yaml"));
20961
21346
  init_docker();
20962
21347
  init_host_routes();
@@ -21454,6 +21839,11 @@ async function runStopCommand(options) {
21454
21839
  return;
21455
21840
  }
21456
21841
  const label2 = result.kind === "primary" ? "Primary checkout" : `Workspace '${result.workspace}'`;
21842
+ if (result.runtimeAbsent) {
21843
+ process.stdout.write(`${label2} has no runtime or routes; stop is complete.
21844
+ `);
21845
+ return;
21846
+ }
21457
21847
  if (!result.stopped && !result.deleted && result.freedRoutes === 0) {
21458
21848
  process.stdout.write(`${label2} is already stopped; no routes needed removal.
21459
21849
  `);
@@ -21774,17 +22164,17 @@ function asRecord3(value) {
21774
22164
  return value;
21775
22165
  }
21776
22166
  function readJson(filePath) {
21777
- if (!import_node_fs44.default.existsSync(filePath)) {
22167
+ if (!import_node_fs45.default.existsSync(filePath)) {
21778
22168
  return void 0;
21779
22169
  }
21780
22170
  try {
21781
- return JSON.parse(import_node_fs44.default.readFileSync(filePath, "utf-8"));
22171
+ return JSON.parse(import_node_fs45.default.readFileSync(filePath, "utf-8"));
21782
22172
  } catch {
21783
22173
  return void 0;
21784
22174
  }
21785
22175
  }
21786
22176
  function relative(repoPath, filePath) {
21787
- return import_node_path55.default.relative(repoPath, filePath) || ".";
22177
+ return import_node_path56.default.relative(repoPath, filePath) || ".";
21788
22178
  }
21789
22179
  function redactEnvAssignments(value) {
21790
22180
  return value.replace(
@@ -21825,7 +22215,7 @@ function inspectPackageManager(repoPath, pkg) {
21825
22215
  ["bun.lock", "bun"]
21826
22216
  ];
21827
22217
  for (const [fileName, name] of lockfiles) {
21828
- if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
22218
+ if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
21829
22219
  return { name, source: fileName };
21830
22220
  }
21831
22221
  }
@@ -21840,9 +22230,9 @@ function inspectNode(repoPath, pkg) {
21840
22230
  if (typeof engines?.node === "string") {
21841
22231
  return { version: engines.node, source: "package.json:engines.node" };
21842
22232
  }
21843
- const nvmrc = import_node_path55.default.join(repoPath, ".nvmrc");
21844
- if (import_node_fs44.default.existsSync(nvmrc)) {
21845
- const version = import_node_fs44.default.readFileSync(nvmrc, "utf-8").trim();
22233
+ const nvmrc = import_node_path56.default.join(repoPath, ".nvmrc");
22234
+ if (import_node_fs45.default.existsSync(nvmrc)) {
22235
+ const version = import_node_fs45.default.readFileSync(nvmrc, "utf-8").trim();
21846
22236
  return { version, source: ".nvmrc" };
21847
22237
  }
21848
22238
  return void 0;
@@ -21903,7 +22293,7 @@ function configuredComposeFiles(repoPath) {
21903
22293
  const files = config.apps.filter(
21904
22294
  (app) => app.runtime === "docker"
21905
22295
  ).flatMap((app) => app.docker.composeFiles).filter(
21906
- (fileName) => !import_node_path55.default.isAbsolute(fileName) && !import_node_path55.default.normalize(fileName).startsWith("..")
22296
+ (fileName) => !import_node_path56.default.isAbsolute(fileName) && !import_node_path56.default.normalize(fileName).startsWith("..")
21907
22297
  );
21908
22298
  return Array.from(new Set(files));
21909
22299
  } catch {
@@ -21921,7 +22311,7 @@ function composeFiles(repoPath) {
21921
22311
  ...configuredComposeFiles(repoPath)
21922
22312
  ];
21923
22313
  return Array.from(new Set(candidates)).filter(
21924
- (fileName) => import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))
22314
+ (fileName) => import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))
21925
22315
  );
21926
22316
  }
21927
22317
  function stringArray2(value) {
@@ -21959,7 +22349,7 @@ function inspectServices(repoPath) {
21959
22349
  const services = [];
21960
22350
  for (const fileName of composeFiles(repoPath)) {
21961
22351
  try {
21962
- const parsed = import_yaml7.default.parse(import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8"));
22352
+ const parsed = import_yaml7.default.parse(import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8"));
21963
22353
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
21964
22354
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
21965
22355
  const service = asRecord3(value);
@@ -21994,8 +22384,8 @@ function inspectServices(repoPath) {
21994
22384
  return services;
21995
22385
  }
21996
22386
  function inspectEnvFiles(repoPath) {
21997
- const files = import_node_fs44.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
21998
- const content = import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8");
22387
+ const files = import_node_fs45.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
22388
+ const content = import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8");
21999
22389
  const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
22000
22390
  return { path: fileName, names };
22001
22391
  });
@@ -22009,18 +22399,18 @@ function inspectEnvFiles(repoPath) {
22009
22399
  };
22010
22400
  }
22011
22401
  function inspectDevcontainer(repoPath) {
22012
- const dir = import_node_path55.default.join(repoPath, ".devcontainer");
22013
- if (!import_node_fs44.default.existsSync(dir)) {
22402
+ const dir = import_node_path56.default.join(repoPath, ".devcontainer");
22403
+ if (!import_node_fs45.default.existsSync(dir)) {
22014
22404
  return { exists: false, files: [] };
22015
22405
  }
22016
22406
  return {
22017
22407
  exists: true,
22018
- files: import_node_fs44.default.readdirSync(dir).filter((fileName) => import_node_fs44.default.statSync(import_node_path55.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
22408
+ files: import_node_fs45.default.readdirSync(dir).filter((fileName) => import_node_fs45.default.statSync(import_node_path56.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
22019
22409
  };
22020
22410
  }
22021
22411
  function inspectDevrouter(repoPath) {
22022
22412
  const configPath = getRepoConfigPath(repoPath);
22023
- if (!import_node_fs44.default.existsSync(configPath)) {
22413
+ if (!import_node_fs45.default.existsSync(configPath)) {
22024
22414
  return {
22025
22415
  exists: false,
22026
22416
  configPath,
@@ -22064,15 +22454,15 @@ function inspectAgentGuidance(repoPath) {
22064
22454
  ["AGENTS.md", "agents"],
22065
22455
  ["CLAUDE.md", "claude"]
22066
22456
  ]) {
22067
- if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
22457
+ if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
22068
22458
  results.push({ path: fileName, kind });
22069
22459
  }
22070
22460
  }
22071
- const skillsDir = import_node_path55.default.join(repoPath, ".agents", "skills");
22072
- if (import_node_fs44.default.existsSync(skillsDir)) {
22073
- for (const name of import_node_fs44.default.readdirSync(skillsDir).sort()) {
22074
- const skillPath = import_node_path55.default.join(skillsDir, name, "SKILL.md");
22075
- if (import_node_fs44.default.existsSync(skillPath)) {
22461
+ const skillsDir = import_node_path56.default.join(repoPath, ".agents", "skills");
22462
+ if (import_node_fs45.default.existsSync(skillsDir)) {
22463
+ for (const name of import_node_fs45.default.readdirSync(skillsDir).sort()) {
22464
+ const skillPath = import_node_path56.default.join(skillsDir, name, "SKILL.md");
22465
+ if (import_node_fs45.default.existsSync(skillPath)) {
22076
22466
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
22077
22467
  }
22078
22468
  }
@@ -22115,7 +22505,7 @@ function buildIssues(report) {
22115
22505
  }
22116
22506
  function inspectRepo(options = {}) {
22117
22507
  const repoPath = resolveRepoPath(options.repo);
22118
- const pkg = readJson(import_node_path55.default.join(repoPath, "package.json"));
22508
+ const pkg = readJson(import_node_path56.default.join(repoPath, "package.json"));
22119
22509
  const scripts = inspectScripts(pkg);
22120
22510
  const reportWithoutIssues = {
22121
22511
  repoPath,
@@ -22134,12 +22524,12 @@ function inspectRepo(options = {}) {
22134
22524
  issues: buildIssues(reportWithoutIssues)
22135
22525
  };
22136
22526
  }
22137
- var import_node_fs44, import_node_path55, import_yaml7;
22527
+ var import_node_fs45, import_node_path56, import_yaml7;
22138
22528
  var init_repo_inspect = __esm({
22139
22529
  "src/core/repo-inspect.ts"() {
22140
22530
  "use strict";
22141
- import_node_fs44 = __toESM(require("fs"));
22142
- import_node_path55 = __toESM(require("path"));
22531
+ import_node_fs45 = __toESM(require("fs"));
22532
+ import_node_path56 = __toESM(require("path"));
22143
22533
  import_yaml7 = __toESM(require("yaml"));
22144
22534
  init_repo_config();
22145
22535
  }
@@ -22244,7 +22634,7 @@ function requiredFileChecks(repoPath) {
22244
22634
  ".devcontainer/docker-compose.yml",
22245
22635
  ".devrouter.yml"
22246
22636
  ];
22247
- const missing = required.filter((fileName) => !import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName)));
22637
+ const missing = required.filter((fileName) => !import_node_fs46.default.existsSync(import_node_path57.default.join(repoPath, fileName)));
22248
22638
  return {
22249
22639
  id: "repo.devcontainer.verify-files",
22250
22640
  level: missing.length === 0 ? "ok" : "error",
@@ -22469,12 +22859,12 @@ async function verifyDevcontainer(options = {}) {
22469
22859
  nextSteps: collectNextSteps3(checks)
22470
22860
  };
22471
22861
  }
22472
- var import_node_fs45, import_node_path56;
22862
+ var import_node_fs46, import_node_path57;
22473
22863
  var init_devcontainer_verify = __esm({
22474
22864
  "src/core/devcontainer-verify.ts"() {
22475
22865
  "use strict";
22476
- import_node_fs45 = __toESM(require("fs"));
22477
- import_node_path56 = __toESM(require("path"));
22866
+ import_node_fs46 = __toESM(require("fs"));
22867
+ import_node_path57 = __toESM(require("path"));
22478
22868
  init_capabilities();
22479
22869
  init_doctor();
22480
22870
  init_host_routes();
@@ -22771,7 +23161,7 @@ function packageManagerIssues(repo) {
22771
23161
  }
22772
23162
  function plannedFiles(repoPath, version) {
22773
23163
  const repo = inspectRepo({ repo: repoPath });
22774
- const projectName = sanitizeProjectName(import_node_path57.default.basename(repo.repoPath));
23164
+ const projectName = sanitizeProjectName(import_node_path58.default.basename(repo.repoPath));
22775
23165
  const nodeMajor = majorVersion(repo.node?.version, "24");
22776
23166
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
22777
23167
  const port = inferPort2(repo);
@@ -22816,8 +23206,8 @@ function plannedFiles(repoPath, version) {
22816
23206
  };
22817
23207
  }
22818
23208
  function classifyFile(repoPath, file) {
22819
- const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22820
- if (!import_node_fs46.default.existsSync(absolutePath)) {
23209
+ const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
23210
+ if (!import_node_fs47.default.existsSync(absolutePath)) {
22821
23211
  return {
22822
23212
  path: file.relativePath,
22823
23213
  action: "create",
@@ -22825,7 +23215,7 @@ function classifyFile(repoPath, file) {
22825
23215
  bytes: Buffer.byteLength(file.content)
22826
23216
  };
22827
23217
  }
22828
- const current = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
23218
+ const current = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
22829
23219
  if (!current.includes(MANAGED_MARKER2)) {
22830
23220
  return {
22831
23221
  path: file.relativePath,
@@ -22882,11 +23272,11 @@ function buildPlan(repoPath, dryRun, version) {
22882
23272
  };
22883
23273
  }
22884
23274
  function writeFile(repoPath, file) {
22885
- const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22886
- import_node_fs46.default.mkdirSync(import_node_path57.default.dirname(absolutePath), { recursive: true });
22887
- import_node_fs46.default.writeFileSync(absolutePath, file.content, "utf-8");
23275
+ const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
23276
+ import_node_fs47.default.mkdirSync(import_node_path58.default.dirname(absolutePath), { recursive: true });
23277
+ import_node_fs47.default.writeFileSync(absolutePath, file.content, "utf-8");
22888
23278
  if (file.executable) {
22889
- import_node_fs46.default.chmodSync(absolutePath, 493);
23279
+ import_node_fs47.default.chmodSync(absolutePath, 493);
22890
23280
  }
22891
23281
  }
22892
23282
  function writeDevcontainer(options = {}) {
@@ -22924,12 +23314,12 @@ function writeDevcontainer(options = {}) {
22924
23314
  nextSteps: postWriteNextSteps(repoPath)
22925
23315
  };
22926
23316
  }
22927
- var import_node_fs46, import_node_path57, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
23317
+ var import_node_fs47, import_node_path58, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22928
23318
  var init_devcontainer_write = __esm({
22929
23319
  "src/core/devcontainer-write.ts"() {
22930
23320
  "use strict";
22931
- import_node_fs46 = __toESM(require("fs"));
22932
- import_node_path57 = __toESM(require("path"));
23321
+ import_node_fs47 = __toESM(require("fs"));
23322
+ import_node_path58 = __toESM(require("path"));
22933
23323
  init_repo_config();
22934
23324
  init_repo_inspect();
22935
23325
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -23263,13 +23653,13 @@ function buildProfilePlanReport(options) {
23263
23653
  return { ...report, contractPath, bindings };
23264
23654
  }
23265
23655
  function loadContract(repoPath, contractPath) {
23266
- if (import_node_path58.default.isAbsolute(contractPath)) {
23656
+ if (import_node_path59.default.isAbsolute(contractPath)) {
23267
23657
  fail3("profile plan contract path must be relative to the repository.");
23268
23658
  }
23269
23659
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
23270
23660
  let metadata;
23271
23661
  try {
23272
- metadata = import_node_fs47.default.lstatSync(absolutePath);
23662
+ metadata = import_node_fs48.default.lstatSync(absolutePath);
23273
23663
  } catch (error) {
23274
23664
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
23275
23665
  }
@@ -23279,16 +23669,16 @@ function loadContract(repoPath, contractPath) {
23279
23669
  if (metadata.size > MAX_CONTRACT_BYTES) {
23280
23670
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
23281
23671
  }
23282
- const realRepo = import_node_fs47.default.realpathSync(repoPath);
23283
- const realContract = import_node_fs47.default.realpathSync(absolutePath);
23672
+ const realRepo = import_node_fs48.default.realpathSync(repoPath);
23673
+ const realContract = import_node_fs48.default.realpathSync(absolutePath);
23284
23674
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
23285
- const source = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
23675
+ const source = import_node_fs48.default.readFileSync(absolutePath, "utf-8");
23286
23676
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
23287
23677
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
23288
23678
  }
23289
23679
  return {
23290
23680
  contract: parseProfilePlanContract(source),
23291
- contractPath: import_node_path58.default.relative(repoPath, absolutePath).split(import_node_path58.default.sep).join("/")
23681
+ contractPath: import_node_path59.default.relative(repoPath, absolutePath).split(import_node_path59.default.sep).join("/")
23292
23682
  };
23293
23683
  }
23294
23684
  function resolveProfilePlan(options) {
@@ -23302,12 +23692,12 @@ function resolveProfilePlan(options) {
23302
23692
  contractPath: loaded.contractPath
23303
23693
  });
23304
23694
  }
23305
- var import_node_fs47, import_node_path58, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
23695
+ var import_node_fs48, import_node_path59, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
23306
23696
  var init_profile_plan = __esm({
23307
23697
  "src/core/profile-plan.ts"() {
23308
23698
  "use strict";
23309
- import_node_fs47 = __toESM(require("fs"));
23310
- import_node_path58 = __toESM(require("path"));
23699
+ import_node_fs48 = __toESM(require("fs"));
23700
+ import_node_path59 = __toESM(require("path"));
23311
23701
  import_yaml8 = require("yaml");
23312
23702
  init_paths();
23313
23703
  init_profile_resolution();
@@ -23339,7 +23729,7 @@ async function runProfileResolveCommand(options) {
23339
23729
  async function runProfilePlanCommand(options) {
23340
23730
  const report = resolveProfilePlan(options);
23341
23731
  if (options.output) {
23342
- writeFileAtomically(import_node_path59.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
23732
+ writeFileAtomically(import_node_path60.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
23343
23733
  `);
23344
23734
  }
23345
23735
  if (options.json) {
@@ -23375,14 +23765,14 @@ function printProfilePlanSummary(report, output2) {
23375
23765
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
23376
23766
  `);
23377
23767
  }
23378
- if (output2) process.stdout.write(`Output: ${import_node_path59.default.resolve(output2)}
23768
+ if (output2) process.stdout.write(`Output: ${import_node_path60.default.resolve(output2)}
23379
23769
  `);
23380
23770
  }
23381
- var import_node_path59;
23771
+ var import_node_path60;
23382
23772
  var init_profile = __esm({
23383
23773
  "src/commands/profile.ts"() {
23384
23774
  "use strict";
23385
- import_node_path59 = __toESM(require("path"));
23775
+ import_node_path60 = __toESM(require("path"));
23386
23776
  init_atomic_file();
23387
23777
  init_output();
23388
23778
  init_profile_plan();
@@ -23672,7 +24062,7 @@ function sanitizeRouterId(value) {
23672
24062
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
23673
24063
  }
23674
24064
  function repoHash(repoPath) {
23675
- return (0, import_node_crypto26.createHash)("sha1").update(import_node_path60.default.resolve(repoPath)).digest("hex").slice(0, 12);
24065
+ return (0, import_node_crypto26.createHash)("sha1").update(import_node_path61.default.resolve(repoPath)).digest("hex").slice(0, 12);
23676
24066
  }
23677
24067
  function asDockerApp(app) {
23678
24068
  return app.runtime === "docker";
@@ -23751,11 +24141,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
23751
24141
  if (dockerApps.length === 0) {
23752
24142
  throw new Error("No docker apps selected to prepare compose overlay.");
23753
24143
  }
23754
- const cachePath = import_node_path60.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
23755
- import_node_fs48.default.mkdirSync(cachePath, { recursive: true });
23756
- const overlayPath = import_node_path60.default.join(cachePath, "compose.devrouter.yml");
24144
+ const cachePath = import_node_path61.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
24145
+ import_node_fs49.default.mkdirSync(cachePath, { recursive: true });
24146
+ const overlayPath = import_node_path61.default.join(cachePath, "compose.devrouter.yml");
23757
24147
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
23758
- import_node_fs48.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
24148
+ import_node_fs49.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
23759
24149
  return {
23760
24150
  overlayPath,
23761
24151
  composeFiles: ensureComposeFiles(dockerApps),
@@ -23875,14 +24265,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23875
24265
  const port = Number(match[1]);
23876
24266
  return Number.isInteger(port) && port > 0 ? port : void 0;
23877
24267
  }
23878
- var import_node_child_process33, import_node_crypto26, import_node_fs48, import_node_path60, import_yaml9;
24268
+ var import_node_child_process33, import_node_crypto26, import_node_fs49, import_node_path61, import_yaml9;
23879
24269
  var init_docker_run = __esm({
23880
24270
  "src/core/docker-run.ts"() {
23881
24271
  "use strict";
23882
24272
  import_node_child_process33 = require("child_process");
23883
24273
  import_node_crypto26 = require("crypto");
23884
- import_node_fs48 = __toESM(require("fs"));
23885
- import_node_path60 = __toESM(require("path"));
24274
+ import_node_fs49 = __toESM(require("fs"));
24275
+ import_node_path61 = __toESM(require("path"));
23886
24276
  import_yaml9 = __toESM(require("yaml"));
23887
24277
  init_docker_error_guidance();
23888
24278
  init_paths();
@@ -24704,13 +25094,13 @@ function measureWorktreeConsumption(worktreePath, options) {
24704
25094
  const startedAt = Date.now();
24705
25095
  let rootStat;
24706
25096
  try {
24707
- rootStat = import_node_fs49.default.lstatSync(worktreePath);
25097
+ rootStat = import_node_fs50.default.lstatSync(worktreePath);
24708
25098
  } catch (error) {
24709
25099
  return { status: "unknown", reason: describeError(error, worktreePath) };
24710
25100
  }
24711
25101
  let rootEntries;
24712
25102
  try {
24713
- rootEntries = import_node_fs49.default.readdirSync(worktreePath, { withFileTypes: true });
25103
+ rootEntries = import_node_fs50.default.readdirSync(worktreePath, { withFileTypes: true });
24714
25104
  } catch (error) {
24715
25105
  return { status: "unknown", reason: describeError(error, worktreePath) };
24716
25106
  }
@@ -24736,10 +25126,10 @@ function measureWorktreeConsumption(worktreePath, options) {
24736
25126
  timedOut = true;
24737
25127
  break;
24738
25128
  }
24739
- const entryPath = import_node_path61.default.join(dirPath, entry.name);
25129
+ const entryPath = import_node_path62.default.join(dirPath, entry.name);
24740
25130
  let entryStat;
24741
25131
  try {
24742
- entryStat = import_node_fs49.default.lstatSync(entryPath);
25132
+ entryStat = import_node_fs50.default.lstatSync(entryPath);
24743
25133
  } catch (error) {
24744
25134
  if (error?.code === "ENOENT") continue;
24745
25135
  unreadableReason = describeIncompleteWalk(error);
@@ -24749,7 +25139,7 @@ function measureWorktreeConsumption(worktreePath, options) {
24749
25139
  if (!entryStat.isDirectory()) continue;
24750
25140
  let childEntries;
24751
25141
  try {
24752
- childEntries = import_node_fs49.default.readdirSync(entryPath, { withFileTypes: true });
25142
+ childEntries = import_node_fs50.default.readdirSync(entryPath, { withFileTypes: true });
24753
25143
  } catch (error) {
24754
25144
  unreadableReason = describeIncompleteWalk(error);
24755
25145
  break;
@@ -24813,12 +25203,12 @@ function describeError(error, worktreePath) {
24813
25203
  }
24814
25204
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
24815
25205
  }
24816
- var import_node_fs49, import_node_path61, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
25206
+ var import_node_fs50, import_node_path62, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24817
25207
  var init_workspace_consumption = __esm({
24818
25208
  "src/core/workspace-consumption.ts"() {
24819
25209
  "use strict";
24820
- import_node_fs49 = __toESM(require("fs"));
24821
- import_node_path61 = __toESM(require("path"));
25210
+ import_node_fs50 = __toESM(require("fs"));
25211
+ import_node_path62 = __toESM(require("path"));
24822
25212
  init_devpod_environment();
24823
25213
  DEFAULT_DEADLINE_MS = 1e4;
24824
25214
  BLOCK_SIZE_BYTES = 512;
@@ -24919,7 +25309,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
24919
25309
  }
24920
25310
  function readGitSnapshot(worktree, commandRunner) {
24921
25311
  const comparablePath = comparableWorkspacePath(worktree.path);
24922
- if (worktree.prunable || !import_node_fs50.default.existsSync(comparablePath)) {
25312
+ if (worktree.prunable || !import_node_fs51.default.existsSync(comparablePath)) {
24923
25313
  return { worktree, checkout: "missing", head: null, committerDate: null };
24924
25314
  }
24925
25315
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -25444,7 +25834,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
25444
25834
  containers2 = measureContainersFn(worktreePaths);
25445
25835
  } catch (error) {
25446
25836
  const reason = `container measurement failed: ${describeCause(error)}`;
25447
- containers2 = new Map(worktreePaths.map((path62) => [path62, unknownContainers(reason)]));
25837
+ containers2 = new Map(worktreePaths.map((path63) => [path63, unknownContainers(reason)]));
25448
25838
  }
25449
25839
  }
25450
25840
  const rows2 = records.map((record5) => {
@@ -25494,12 +25884,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
25494
25884
  networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
25495
25885
  };
25496
25886
  }
25497
- var import_node_child_process35, import_node_fs50, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25887
+ var import_node_child_process35, import_node_fs51, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25498
25888
  var init_workspace_cleanup = __esm({
25499
25889
  "src/core/workspace-cleanup.ts"() {
25500
25890
  "use strict";
25501
25891
  import_node_child_process35 = require("child_process");
25502
- import_node_fs50 = __toESM(require("fs"));
25892
+ import_node_fs51 = __toESM(require("fs"));
25503
25893
  init_devpod_workspaces();
25504
25894
  init_host_routes();
25505
25895
  init_network_diagnostics();
@@ -25611,7 +26001,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
25611
26001
  );
25612
26002
  }
25613
26003
  function defaultWorktreePath(mainRepo, ws) {
25614
- return import_node_path62.default.join(mainRepo, "trees", ws);
26004
+ return import_node_path63.default.join(mainRepo, "trees", ws);
25615
26005
  }
25616
26006
  function assertDefaultWorktreeRootIgnored(mainRepo) {
25617
26007
  const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -25619,12 +26009,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
25619
26009
  });
25620
26010
  if (ignored.status !== 0) {
25621
26011
  throw new Error(
25622
- `Default worktree root '${import_node_path62.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path62.default.join(mainRepo, ".gitignore")}' or use --path.`
26012
+ `Default worktree root '${import_node_path63.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path63.default.join(mainRepo, ".gitignore")}' or use --path.`
25623
26013
  );
25624
26014
  }
25625
26015
  }
25626
26016
  function legacyDefaultWorktreePath(mainRepo, ws) {
25627
- return import_node_path62.default.join(import_node_path62.default.dirname(mainRepo), `${import_node_path62.default.basename(mainRepo)}-${ws}`);
26017
+ return import_node_path63.default.join(import_node_path63.default.dirname(mainRepo), `${import_node_path63.default.basename(mainRepo)}-${ws}`);
25628
26018
  }
25629
26019
  function teardownFallbackPath(mainRepo, workspace) {
25630
26020
  const candidates = [
@@ -25736,7 +26126,7 @@ function assertFullDownPreflight(mainRepo, target) {
25736
26126
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
25737
26127
  throw new Error("Refusing to remove the primary Git checkout.");
25738
26128
  }
25739
- if (!target.worktree || target.worktree.prunable || !import_node_fs51.default.existsSync(target.worktreePath)) return;
26129
+ if (!target.worktree || target.worktree.prunable || !import_node_fs52.default.existsSync(target.worktreePath)) return;
25740
26130
  if (target.worktree.locked) {
25741
26131
  throw new Error(
25742
26132
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
@@ -25776,9 +26166,9 @@ async function workspaceUp(branch, opts = {}) {
25776
26166
  (candidate) => defaultWorktreePath(mainRepo, candidate)
25777
26167
  );
25778
26168
  const generatedPath = candidatePaths.find(
25779
- (candidate) => !import_node_fs51.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
26169
+ (candidate) => !import_node_fs52.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
25780
26170
  );
25781
- const selectedPath = opts.path ? import_node_path62.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
26171
+ const selectedPath = opts.path ? import_node_path63.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
25782
26172
  if (!selectedPath) {
25783
26173
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
25784
26174
  }
@@ -25787,7 +26177,7 @@ async function workspaceUp(branch, opts = {}) {
25787
26177
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
25788
26178
  );
25789
26179
  }
25790
- if (import_node_fs51.default.existsSync(selectedPath)) {
26180
+ if (import_node_fs52.default.existsSync(selectedPath)) {
25791
26181
  const registered = worktrees.find(
25792
26182
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
25793
26183
  );
@@ -25890,7 +26280,7 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
25890
26280
  );
25891
26281
  }
25892
26282
  const retained = action2 === "stop" ? readManagedRuntimeState(resolved.worktreePath, resolved.workspace) : void 0;
25893
- let absent = false;
26283
+ let absent = mutation.status === "proven-absent";
25894
26284
  const remove = () => {
25895
26285
  if (retained?.stopBaseline) {
25896
26286
  absent = proveManagedStop(retained).status === "proven-absent";
@@ -25899,6 +26289,11 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
25899
26289
  ))
25900
26290
  throw new Error("Absent stop contains routes outside the retained desired set.");
25901
26291
  }
26292
+ if (absent && !retained) {
26293
+ if (listRoutesForWorktreePath(resolved.worktreePath).length)
26294
+ throw new Error("Initial managed stop observed remaining workspace routes.");
26295
+ return [];
26296
+ }
25902
26297
  claimLifecycleEffect();
25903
26298
  return removeWorkspaceRoutesForWorktree(resolved.workspace, resolved.worktreePath);
25904
26299
  };
@@ -25934,7 +26329,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25934
26329
  workspace: resolved.workspace,
25935
26330
  devpodId: stopped.devpodId,
25936
26331
  freedRoutes: stopped.freedRoutes,
25937
- providerChanged: stopped.stopped
26332
+ providerChanged: stopped.stopped && !stopped.runtimeAbsent,
26333
+ ...stopped.runtimeAbsent ? { runtimeAbsent: true } : {}
25938
26334
  };
25939
26335
  }
25940
26336
  const operation = async () => {
@@ -25944,7 +26340,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25944
26340
  }
25945
26341
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
25946
26342
  if (removeWorktree) {
25947
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath)) {
26343
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath)) {
25948
26344
  const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
25949
26345
  encoding: "utf-8"
25950
26346
  });
@@ -25963,7 +26359,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25963
26359
  }
25964
26360
  return result;
25965
26361
  };
25966
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
26362
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25967
26363
  }
25968
26364
  async function workspaceStop(target, opts = {}) {
25969
26365
  return runWorkspaceLifecycle("stop", target, opts);
@@ -25971,13 +26367,13 @@ async function workspaceStop(target, opts = {}) {
25971
26367
  async function workspaceDown(target, opts = {}) {
25972
26368
  return runWorkspaceLifecycle("down", target, opts);
25973
26369
  }
25974
- var import_node_child_process36, import_node_fs51, import_node_path62, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
26370
+ var import_node_child_process36, import_node_fs52, import_node_path63, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25975
26371
  var init_workspace_lifecycle = __esm({
25976
26372
  "src/core/workspace-lifecycle.ts"() {
25977
26373
  "use strict";
25978
26374
  import_node_child_process36 = require("child_process");
25979
- import_node_fs51 = __toESM(require("fs"));
25980
- import_node_path62 = __toESM(require("path"));
26375
+ import_node_fs52 = __toESM(require("fs"));
26376
+ import_node_path63 = __toESM(require("path"));
25981
26377
  init_devpod_mutation();
25982
26378
  init_devpod_workspaces();
25983
26379
  init_devsy_mutation();
@@ -26161,7 +26557,7 @@ var init_version = __esm({
26161
26557
 
26162
26558
  // src/cli.ts
26163
26559
  var import_commander = require("commander");
26164
- var CLI_VERSION = true ? "0.0.73" : "0.0.0-dev";
26560
+ var CLI_VERSION = true ? "0.0.75" : "0.0.0-dev";
26165
26561
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
26166
26562
  function withErrorHandling(action2) {
26167
26563
  return async (...args) => {
@@ -26534,10 +26930,10 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
26534
26930
  workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
26535
26931
  "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
26536
26932
  ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
26537
- withErrorHandling(async (path62, _options, command) => {
26933
+ withErrorHandling(async (path63, _options, command) => {
26538
26934
  const options = command.opts();
26539
26935
  const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
26540
- await runWorkspaceJournalSettleCommand2({ ...path62 ? { path: path62 } : {}, ...options });
26936
+ await runWorkspaceJournalSettleCommand2({ ...path63 ? { path: path63 } : {}, ...options });
26541
26937
  })
26542
26938
  );
26543
26939
  function parseVersionRequest(argv) {