@devrouter/cli 0.0.61 → 0.0.62

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.
@@ -936,6 +936,28 @@ function inspectManagedStopRunnerId(endpoint, containerId) {
936
936
  throw new Error("Provider container binding is unavailable.");
937
937
  return value;
938
938
  }
939
+ function inspectProviderRunnerContainers(endpoint, runnerId) {
940
+ if (!runnerId || runnerId.length > 4096 || /[\r\n\0]/.test(runnerId))
941
+ throw new Error("Provider runner identity is malformed.");
942
+ const ids = parseDockerLines(
943
+ runManagedStopDocker(
944
+ [
945
+ "ps",
946
+ "-a",
947
+ "--no-trunc",
948
+ "--filter",
949
+ `label=dev.containers.id=${runnerId}`,
950
+ "--format",
951
+ "{{.ID}}"
952
+ ],
953
+ endpoint
954
+ )
955
+ );
956
+ if (ids.length > 256) throw new Error("Provider runner population exceeds its bound.");
957
+ ids.forEach(assertFullContainerId);
958
+ assertUniqueContainerIds(ids);
959
+ return ids;
960
+ }
939
961
  function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
940
962
  const ids = parseDockerLines(
941
963
  runManagedStopDocker(
@@ -1046,6 +1068,10 @@ function listDevsyWorkspaces() {
1046
1068
  ...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
1047
1069
  ...typeof candidate.context === "string" ? { context: candidate.context } : {}
1048
1070
  };
1071
+ const provider = candidate.provider;
1072
+ if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
1073
+ if (typeof provider?.options?.DOCKER_PATH?.value === "string")
1074
+ workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
1049
1075
  if ("lastUsed" in candidate) {
1050
1076
  if (typeof candidate.lastUsed === "string") {
1051
1077
  workspace.lastUsed = candidate.lastUsed;
@@ -1627,29 +1653,101 @@ function selectDevpodWorkspace(workspaces, repoPath) {
1627
1653
  // src/core/devsy-exec.ts
1628
1654
  var import_node_child_process22 = require("child_process");
1629
1655
 
1630
- // src/core/execution-outcome.ts
1631
- var ExecutionOutcomeError = class extends Error {
1632
- outcome;
1633
- constructor(message, outcome) {
1634
- super(message);
1635
- this.name = "ExecutionOutcomeError";
1636
- this.outcome = outcome;
1656
+ // src/core/devsy-exec-proof.ts
1657
+ var import_node_fs6 = __toESM(require("fs"));
1658
+ var import_node_os2 = __toESM(require("os"));
1659
+ var import_node_path7 = __toESM(require("path"));
1660
+ var import_node_util = require("util");
1661
+ function proveLocalDockerSelection(workspace) {
1662
+ const name = workspace.providerName;
1663
+ const context = workspace.context;
1664
+ if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
1665
+ throw new Error("Retained exec provider configuration identity is unavailable.");
1666
+ const file = import_node_path7.default.join(
1667
+ process.env.DEVSY_HOME || import_node_path7.default.join(import_node_os2.default.homedir(), ".devsy"),
1668
+ "contexts",
1669
+ context,
1670
+ "providers",
1671
+ name,
1672
+ "provider.json"
1673
+ );
1674
+ try {
1675
+ if (import_node_fs6.default.statSync(file).size > 1024 * 1024) throw new Error();
1676
+ const config = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
1677
+ const driver = config.agent?.driver ?? "docker";
1678
+ const configuredPath = config.agent?.docker?.path ?? "";
1679
+ if (typeof configuredPath !== "string") throw new Error();
1680
+ const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
1681
+ if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
1682
+ } catch {
1683
+ throw new Error("Retained exec requires a provider configured for the local Docker command.");
1637
1684
  }
1638
- };
1639
-
1640
- // src/core/reliability-lifecycle.ts
1641
- var import_node_path27 = __toESM(require("path"));
1685
+ return name;
1686
+ }
1687
+ function captureDevsyExecProof(repoPath) {
1688
+ repoPath = comparableWorkspacePath(repoPath);
1689
+ const registry = listDevsyWorkspaces();
1690
+ const workspace = selectDevsyWorkspace(registry, repoPath);
1691
+ if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
1692
+ throw new Error("Retained exec requires one exact running Devsy registration.");
1693
+ const providerName = proveLocalDockerSelection(workspace);
1694
+ const endpoint = resolveManagedStopEndpoint();
1695
+ if (!supportsManagedStopBaseline(endpoint))
1696
+ throw new Error("Retained exec requires an exact local Docker endpoint.");
1697
+ const daemon = inspectManagedStopDaemon(endpoint);
1698
+ const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
1699
+ (container) => container.state.Running
1700
+ );
1701
+ if (candidates.length !== 1)
1702
+ throw new Error("Retained exec requires one exact running primary container.");
1703
+ const candidate = candidates[0];
1704
+ const project = candidate.labels["com.docker.compose.project"];
1705
+ if (!project) throw new Error("Retained exec primary has no Compose identity.");
1706
+ const pinned = workspaceAppContainers(
1707
+ inspectManagedStopContainers(project, endpoint),
1708
+ repoPath
1709
+ ).filter((container) => container.state.Running);
1710
+ if (pinned.length !== 1 || pinned[0].id !== candidate.id)
1711
+ throw new Error("Retained exec primary changed during inspection.");
1712
+ const mounts = pinned[0].mounts.filter(
1713
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
1714
+ );
1715
+ if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
1716
+ throw new Error("Retained exec requires one exact source mount.");
1717
+ const uidBytes = Buffer.byteLength(workspace.uid);
1718
+ const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
1719
+ const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
1720
+ if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
1721
+ throw new Error("Retained exec runner does not select the exact primary container.");
1722
+ if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
1723
+ throw new Error("Retained exec provider or Docker identity changed.");
1724
+ return {
1725
+ repoPath,
1726
+ id: workspace.id,
1727
+ uid: workspace.uid,
1728
+ context: workspace.context,
1729
+ providerName,
1730
+ endpoint,
1731
+ daemon,
1732
+ containerId: candidate.id,
1733
+ workspacePath: mounts[0].Destination
1734
+ };
1735
+ }
1736
+ function revalidateDevsyExecProof(repoPath, proof) {
1737
+ if (!(0, import_node_util.isDeepStrictEqual)(captureDevsyExecProof(repoPath), proof))
1738
+ throw new Error("Retained exec identity changed before command launch.");
1739
+ }
1642
1740
 
1643
1741
  // src/core/devsy-mutation.ts
1644
1742
  var import_node_child_process14 = require("child_process");
1645
- var import_node_fs15 = __toESM(require("fs"));
1646
- var import_node_path20 = __toESM(require("path"));
1743
+ var import_node_fs16 = __toESM(require("fs"));
1744
+ var import_node_path21 = __toESM(require("path"));
1647
1745
 
1648
1746
  // src/core/devsy-agent.ts
1649
1747
  var import_node_child_process9 = require("child_process");
1650
1748
  var import_node_crypto4 = require("crypto");
1651
- var import_node_fs6 = __toESM(require("fs"));
1652
- var import_node_path7 = __toESM(require("path"));
1749
+ var import_node_fs7 = __toESM(require("fs"));
1750
+ var import_node_path8 = __toESM(require("path"));
1653
1751
  var SUPPORTED_DEVSY_VERSION = "1.16.2";
1654
1752
  var DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
1655
1753
  var DEVSY_AGENT_ASSETS = [
@@ -1675,7 +1773,7 @@ var DevsyAgentReadinessError = class extends Error {
1675
1773
  this.name = "DevsyAgentReadinessError";
1676
1774
  }
1677
1775
  };
1678
- var DEVSY_AGENT_CACHE_LOCK = import_node_path7.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
1776
+ var DEVSY_AGENT_CACHE_LOCK = import_node_path8.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
1679
1777
  function parseVersion(output) {
1680
1778
  return output?.match(
1681
1779
  /\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
@@ -1699,14 +1797,14 @@ function nativeAssetName(platform, arch) {
1699
1797
  return void 0;
1700
1798
  }
1701
1799
  function managedBinaryPath(cacheRoot, asset) {
1702
- return import_node_path7.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
1800
+ return import_node_path8.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
1703
1801
  }
1704
1802
  function hashOpenFile(file) {
1705
1803
  const digest = (0, import_node_crypto4.createHash)("sha256");
1706
1804
  const buffer = Buffer.allocUnsafe(1024 * 1024);
1707
1805
  let position = 0;
1708
1806
  for (; ; ) {
1709
- const bytesRead = import_node_fs6.default.readSync(file, buffer, 0, buffer.length, position);
1807
+ const bytesRead = import_node_fs7.default.readSync(file, buffer, 0, buffer.length, position);
1710
1808
  if (bytesRead === 0) break;
1711
1809
  digest.update(buffer.subarray(0, bytesRead));
1712
1810
  position += bytesRead;
@@ -1716,8 +1814,8 @@ function hashOpenFile(file) {
1716
1814
  function inspectBinary(binaryPath, assets, missingState) {
1717
1815
  let file;
1718
1816
  try {
1719
- file = import_node_fs6.default.openSync(binaryPath, import_node_fs6.default.constants.O_RDONLY | import_node_fs6.default.constants.O_NOFOLLOW);
1720
- const stat = import_node_fs6.default.fstatSync(file);
1817
+ file = import_node_fs7.default.openSync(binaryPath, import_node_fs7.default.constants.O_RDONLY | import_node_fs7.default.constants.O_NOFOLLOW);
1818
+ const stat = import_node_fs7.default.fstatSync(file);
1721
1819
  if (!stat.isFile()) {
1722
1820
  return { state: "invalid", reason: "the selected source is not a regular file" };
1723
1821
  }
@@ -1732,7 +1830,7 @@ function inspectBinary(binaryPath, assets, missingState) {
1732
1830
  const code = error.code;
1733
1831
  return code === "ENOENT" ? { state: missingState, reason: "the selected source is missing" } : { state: "invalid", reason: "the selected source is not a readable regular file" };
1734
1832
  } finally {
1735
- if (file !== void 0) import_node_fs6.default.closeSync(file);
1833
+ if (file !== void 0) import_node_fs7.default.closeSync(file);
1736
1834
  }
1737
1835
  }
1738
1836
  function resolvedOptions(options) {
@@ -1741,7 +1839,7 @@ function resolvedOptions(options) {
1741
1839
  platform: options.platform ?? process.platform,
1742
1840
  arch: options.arch ?? process.arch,
1743
1841
  versionOutput: options.versionOutput ?? installedVersionOutput(),
1744
- cacheRoot: options.cacheRoot ?? import_node_path7.default.join(CACHE_DIR, "devsy", "agents"),
1842
+ cacheRoot: options.cacheRoot ?? import_node_path8.default.join(CACHE_DIR, "devsy", "agents"),
1745
1843
  lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
1746
1844
  assets: options.assets ?? DEVSY_AGENT_ASSETS,
1747
1845
  nativeAssetName: options.nativeAssetName
@@ -1802,19 +1900,19 @@ function requireReadyDevsyAgent(options = {}) {
1802
1900
  }
1803
1901
 
1804
1902
  // src/core/managed-devsy-stop.ts
1805
- var import_node_os3 = __toESM(require("os"));
1806
- var import_node_path19 = __toESM(require("path"));
1903
+ var import_node_os4 = __toESM(require("os"));
1904
+ var import_node_path20 = __toESM(require("path"));
1807
1905
 
1808
1906
  // src/core/devcontainer-profile.ts
1809
1907
  var import_node_child_process10 = require("child_process");
1810
1908
  var import_node_crypto5 = require("crypto");
1811
- var import_node_fs8 = __toESM(require("fs"));
1812
- var import_node_path10 = __toESM(require("path"));
1909
+ var import_node_fs9 = __toESM(require("fs"));
1910
+ var import_node_path11 = __toESM(require("path"));
1813
1911
  var import_yaml = __toESM(require("yaml"));
1814
1912
 
1815
1913
  // src/core/devcontainer-config.ts
1816
- var import_node_fs7 = __toESM(require("fs"));
1817
- var import_node_path8 = __toESM(require("path"));
1914
+ var import_node_fs8 = __toESM(require("fs"));
1915
+ var import_node_path9 = __toESM(require("path"));
1818
1916
  var import_jsonc_parser = require("jsonc-parser");
1819
1917
  function isObject(value) {
1820
1918
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1837,11 +1935,11 @@ function parseDevcontainerConfig(contents, sourcePath) {
1837
1935
  return parsed;
1838
1936
  }
1839
1937
  function readDevcontainerConfig(repoPath) {
1840
- const sourcePath = import_node_path8.default.join(repoPath, ".devcontainer", "devcontainer.json");
1841
- if (!import_node_fs7.default.existsSync(sourcePath) || !import_node_fs7.default.lstatSync(sourcePath).isFile()) {
1938
+ const sourcePath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
1939
+ if (!import_node_fs8.default.existsSync(sourcePath) || !import_node_fs8.default.lstatSync(sourcePath).isFile()) {
1842
1940
  throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
1843
1941
  }
1844
- const sourceContents = import_node_fs7.default.readFileSync(sourcePath, "utf-8");
1942
+ const sourceContents = import_node_fs8.default.readFileSync(sourcePath, "utf-8");
1845
1943
  return {
1846
1944
  sourcePath,
1847
1945
  sourceContents,
@@ -1859,11 +1957,11 @@ function assertManagedDevcontainerLifecycle(repoPath) {
1859
1957
  }
1860
1958
 
1861
1959
  // src/core/paths.ts
1862
- var import_node_path9 = __toESM(require("path"));
1960
+ var import_node_path10 = __toESM(require("path"));
1863
1961
  function assertPathWithinRepo(filePath, repoRoot, label) {
1864
- const resolvedRoot = import_node_path9.default.resolve(repoRoot);
1865
- const resolved = import_node_path9.default.resolve(repoRoot, filePath);
1866
- if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path9.default.sep)) {
1962
+ const resolvedRoot = import_node_path10.default.resolve(repoRoot);
1963
+ const resolved = import_node_path10.default.resolve(repoRoot, filePath);
1964
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path10.default.sep)) {
1867
1965
  throw new Error(`${label} path '${filePath}' escapes the repository root.`);
1868
1966
  }
1869
1967
  return resolved;
@@ -1917,14 +2015,14 @@ function resolveComposeFiles(source, repoPath, linked) {
1917
2015
  "managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
1918
2016
  );
1919
2017
  }
1920
- const directory = import_node_path10.default.join(repoPath, ".devcontainer");
2018
+ const directory = import_node_path11.default.join(repoPath, ".devcontainer");
1921
2019
  const files = references.map((reference) => {
1922
2020
  const resolved2 = assertPathWithinRepo(
1923
2021
  resolveComposeReference(reference, linked),
1924
2022
  directory,
1925
2023
  "dockerComposeFile"
1926
2024
  );
1927
- if (!import_node_fs8.default.existsSync(resolved2) || !import_node_fs8.default.lstatSync(resolved2).isFile()) {
2025
+ if (!import_node_fs9.default.existsSync(resolved2) || !import_node_fs9.default.lstatSync(resolved2).isFile()) {
1928
2026
  throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
1929
2027
  }
1930
2028
  return resolved2;
@@ -1932,7 +2030,7 @@ function resolveComposeFiles(source, repoPath, linked) {
1932
2030
  for (const file of files) {
1933
2031
  let parsed;
1934
2032
  try {
1935
- parsed = import_yaml.default.parse(import_node_fs8.default.readFileSync(file, "utf-8"));
2033
+ parsed = import_yaml.default.parse(import_node_fs9.default.readFileSync(file, "utf-8"));
1936
2034
  } catch (error) {
1937
2035
  throw new Error(
1938
2036
  `Could not parse managed Dev Container compose file '${file}': ${String(error)}`
@@ -1981,12 +2079,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
1981
2079
  }
1982
2080
  }
1983
2081
  function assertGeneratedPathOwnership(generatedPath) {
1984
- if (!import_node_fs8.default.existsSync(generatedPath)) return;
1985
- const stat = import_node_fs8.default.lstatSync(generatedPath);
2082
+ if (!import_node_fs9.default.existsSync(generatedPath)) return;
2083
+ const stat = import_node_fs9.default.lstatSync(generatedPath);
1986
2084
  if (!stat.isFile() || stat.isSymbolicLink()) {
1987
2085
  throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
1988
2086
  }
1989
- const firstLine = import_node_fs8.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
2087
+ const firstLine = import_node_fs9.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
1990
2088
  if (firstLine !== MANAGED_DEVCONTAINER_MARKER) {
1991
2089
  throw new Error(
1992
2090
  `Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
@@ -2062,7 +2160,7 @@ function inspectManagedDevcontainerConfig(options) {
2062
2160
  const contents = `${MANAGED_DEVCONTAINER_MARKER}
2063
2161
  ${JSON.stringify(effective, null, 2)}
2064
2162
  `;
2065
- const generatedPath = import_node_path10.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
2163
+ const generatedPath = import_node_path11.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
2066
2164
  assertIgnoredGeneratedPath(options.repoPath, generatedPath);
2067
2165
  assertGeneratedPathOwnership(generatedPath);
2068
2166
  return {
@@ -2089,13 +2187,13 @@ function writeManagedDevcontainerConfig(plan) {
2089
2187
  function inspectManagedDevcontainerGeneratedConfig(plan) {
2090
2188
  let stat;
2091
2189
  try {
2092
- stat = import_node_fs8.default.lstatSync(plan.generatedPath);
2190
+ stat = import_node_fs9.default.lstatSync(plan.generatedPath);
2093
2191
  } catch (error) {
2094
2192
  if (error.code === "ENOENT") return { status: "missing" };
2095
2193
  throw error;
2096
2194
  }
2097
2195
  if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
2098
- const contents = import_node_fs8.default.readFileSync(plan.generatedPath, "utf-8");
2196
+ const contents = import_node_fs9.default.readFileSync(plan.generatedPath, "utf-8");
2099
2197
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
2100
2198
  `)) {
2101
2199
  return { status: "foreign" };
@@ -2107,17 +2205,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
2107
2205
  };
2108
2206
  }
2109
2207
  function removeManagedDevcontainerConfig(plan) {
2110
- if (!import_node_fs8.default.existsSync(plan.generatedPath)) return;
2111
- const stat = import_node_fs8.default.lstatSync(plan.generatedPath);
2208
+ if (!import_node_fs9.default.existsSync(plan.generatedPath)) return;
2209
+ const stat = import_node_fs9.default.lstatSync(plan.generatedPath);
2112
2210
  if (!stat.isFile()) {
2113
2211
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
2114
2212
  }
2115
- const contents = import_node_fs8.default.readFileSync(plan.generatedPath, "utf-8");
2213
+ const contents = import_node_fs9.default.readFileSync(plan.generatedPath, "utf-8");
2116
2214
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
2117
2215
  `)) {
2118
2216
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
2119
2217
  }
2120
- import_node_fs8.default.unlinkSync(plan.generatedPath);
2218
+ import_node_fs9.default.unlinkSync(plan.generatedPath);
2121
2219
  }
2122
2220
  function assertSafeContainerId(containerId) {
2123
2221
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
@@ -2141,8 +2239,8 @@ function managedComposeEnvironment(workspace) {
2141
2239
  }
2142
2240
  function assertReadableComposeFile(file) {
2143
2241
  try {
2144
- if (!import_node_fs8.default.statSync(file).isFile()) throw new Error("not a regular file");
2145
- import_node_fs8.default.accessSync(file, import_node_fs8.default.constants.R_OK);
2242
+ if (!import_node_fs9.default.statSync(file).isFile()) throw new Error("not a regular file");
2243
+ import_node_fs9.default.accessSync(file, import_node_fs9.default.constants.R_OK);
2146
2244
  } catch {
2147
2245
  throw new Error("Managed container Compose configuration contains an unreadable file.");
2148
2246
  }
@@ -2274,8 +2372,8 @@ function stopExactManagedService(containerId, service, options) {
2274
2372
  }
2275
2373
 
2276
2374
  // src/core/managed-compose-population.ts
2277
- var import_node_fs9 = __toESM(require("fs"));
2278
- var import_node_path11 = __toESM(require("path"));
2375
+ var import_node_fs10 = __toESM(require("fs"));
2376
+ var import_node_path12 = __toESM(require("path"));
2279
2377
  function proveManagedComposePopulation(options) {
2280
2378
  const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers } = options;
2281
2379
  const services = /* @__PURE__ */ new Set();
@@ -2289,13 +2387,13 @@ function proveManagedComposePopulation(options) {
2289
2387
  }
2290
2388
  services.add(service);
2291
2389
  const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
2292
- if (files.some((file) => !file || !import_node_path11.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path11.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
2390
+ if (files.some((file) => !file || !import_node_path12.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path12.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
2293
2391
  throw new Error("Managed stop Compose file identity changed.");
2294
2392
  }
2295
2393
  for (const file of files.slice(plan.composeFiles.length)) {
2296
- if (import_node_path11.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2297
- import_node_path11.default.basename(file)
2298
- ) || import_node_fs9.default.realpathSync(file) !== import_node_path11.default.join(import_node_fs9.default.realpathSync(devsyRoot), import_node_path11.default.relative(devsyRoot, file))) {
2394
+ if (import_node_path12.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2395
+ import_node_path12.default.basename(file)
2396
+ ) || import_node_fs10.default.realpathSync(file) !== import_node_path12.default.join(import_node_fs10.default.realpathSync(devsyRoot), import_node_path12.default.relative(devsyRoot, file))) {
2299
2397
  throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
2300
2398
  }
2301
2399
  }
@@ -2316,11 +2414,11 @@ function proveManagedComposePopulation(options) {
2316
2414
 
2317
2415
  // src/core/managed-runtime-state.ts
2318
2416
  var import_node_crypto6 = require("crypto");
2319
- var import_node_fs10 = __toESM(require("fs"));
2320
- var import_node_path13 = __toESM(require("path"));
2417
+ var import_node_fs11 = __toESM(require("fs"));
2418
+ var import_node_path14 = __toESM(require("path"));
2321
2419
 
2322
2420
  // src/core/managed-stop-baseline.ts
2323
- var import_node_path12 = __toESM(require("path"));
2421
+ var import_node_path13 = __toESM(require("path"));
2324
2422
  function record(value, keys2) {
2325
2423
  if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
2326
2424
  throw new Error("Invalid managed stop baseline fields.");
@@ -2335,7 +2433,7 @@ function text(value, empty = false) {
2335
2433
  }
2336
2434
  function absolute(value) {
2337
2435
  const result = text(value);
2338
- if (!import_node_path12.default.isAbsolute(result) || import_node_path12.default.normalize(result) !== result) {
2436
+ if (!import_node_path13.default.isAbsolute(result) || import_node_path13.default.normalize(result) !== result) {
2339
2437
  throw new Error("Invalid managed stop baseline path.");
2340
2438
  }
2341
2439
  return result;
@@ -2413,8 +2511,8 @@ function validateManagedStopBaseline(value, state) {
2413
2511
  const c = record(entry, ["id", "service", "configFiles", "mounts"]);
2414
2512
  const configFiles = list(c.configFiles, absolute);
2415
2513
  if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
2416
- (file) => import_node_path12.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2417
- import_node_path12.default.basename(file)
2514
+ (file) => import_node_path13.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
2515
+ import_node_path13.default.basename(file)
2418
2516
  )
2419
2517
  )) {
2420
2518
  throw new Error("Invalid stop baseline Compose provenance.");
@@ -2453,7 +2551,7 @@ function stateKey(repoPath, workspace) {
2453
2551
  return (0, import_node_crypto6.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
2454
2552
  }
2455
2553
  function managedRuntimeStatePath(repoPath, workspace) {
2456
- return import_node_path13.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
2554
+ return import_node_path14.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
2457
2555
  }
2458
2556
  function isStringArray(value) {
2459
2557
  return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
@@ -2518,9 +2616,9 @@ function validateState(value, repoPath, workspace) {
2518
2616
  }
2519
2617
  return result;
2520
2618
  }
2521
- function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs10.default.readFileSync(file, "utf-8")) {
2619
+ function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs11.default.readFileSync(file, "utf-8")) {
2522
2620
  const statePath = managedRuntimeStatePath(repoPath, workspace);
2523
- if (!import_node_fs10.default.existsSync(statePath)) return void 0;
2621
+ if (!import_node_fs11.default.existsSync(statePath)) return void 0;
2524
2622
  let parsed;
2525
2623
  try {
2526
2624
  parsed = JSON.parse(read(statePath));
@@ -2547,9 +2645,9 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
2547
2645
  }
2548
2646
 
2549
2647
  // src/core/managed-stop-recovery.ts
2550
- var import_node_os2 = __toESM(require("os"));
2551
- var import_node_path15 = __toESM(require("path"));
2552
- var import_node_util = require("util");
2648
+ var import_node_os3 = __toESM(require("os"));
2649
+ var import_node_path16 = __toESM(require("path"));
2650
+ var import_node_util2 = require("util");
2553
2651
 
2554
2652
  // src/core/reliability-context.ts
2555
2653
  var claim;
@@ -2563,11 +2661,11 @@ function claimLifecycleEffect() {
2563
2661
 
2564
2662
  // src/core/workspace-ownership.ts
2565
2663
  var import_node_child_process11 = require("child_process");
2566
- var import_node_fs11 = __toESM(require("fs"));
2567
- var import_node_path14 = __toESM(require("path"));
2664
+ var import_node_fs12 = __toESM(require("fs"));
2665
+ var import_node_path15 = __toESM(require("path"));
2568
2666
  var READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
2569
2667
  var OWNERSHIP_VERSION = 1;
2570
- var OWNERSHIP_DIR = import_node_path14.default.join("devrouter", "workspaces");
2668
+ var OWNERSHIP_DIR = import_node_path15.default.join("devrouter", "workspaces");
2571
2669
  function commandError(command, repoPath, stderr) {
2572
2670
  return new Error(
2573
2671
  `${command} failed for '${repoPath}': ${stderr?.trim() || "not a Git repository"}`
@@ -2582,7 +2680,7 @@ function resolveGitCommonDir(repoPath) {
2582
2680
  if (result.status !== 0 || !output) {
2583
2681
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
2584
2682
  }
2585
- return comparableWorkspacePath(import_node_path14.default.isAbsolute(output) ? output : import_node_path14.default.resolve(repoPath, output));
2683
+ return comparableWorkspacePath(import_node_path15.default.isAbsolute(output) ? output : import_node_path15.default.resolve(repoPath, output));
2586
2684
  }
2587
2685
  function listGitWorktrees(repoPath) {
2588
2686
  const result = (0, import_node_child_process11.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
@@ -2622,7 +2720,7 @@ function listGitWorktrees(repoPath) {
2622
2720
  return worktrees;
2623
2721
  }
2624
2722
  function ownershipDirectory(repoPath) {
2625
- return import_node_path14.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
2723
+ return import_node_path15.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
2626
2724
  }
2627
2725
  function validateWorkspace(value, label) {
2628
2726
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -2650,7 +2748,7 @@ function validateRecord(value, expectedWorkspace) {
2650
2748
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
2651
2749
  );
2652
2750
  }
2653
- if (typeof candidate.worktreePath !== "string" || !import_node_path14.default.isAbsolute(candidate.worktreePath)) {
2751
+ if (typeof candidate.worktreePath !== "string" || !import_node_path15.default.isAbsolute(candidate.worktreePath)) {
2654
2752
  throw new Error("invalid workspace ownership worktreePath");
2655
2753
  }
2656
2754
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -2668,7 +2766,7 @@ function validateRecord(value, expectedWorkspace) {
2668
2766
  };
2669
2767
  }
2670
2768
  function recordPath(repoPath, workspace) {
2671
- return import_node_path14.default.join(
2769
+ return import_node_path15.default.join(
2672
2770
  ownershipDirectory(repoPath),
2673
2771
  `${validateWorkspace(workspace, "workspace")}.json`
2674
2772
  );
@@ -2676,7 +2774,7 @@ function recordPath(repoPath, workspace) {
2676
2774
  function readRecordFile(filePath, expectedWorkspace) {
2677
2775
  let parsed;
2678
2776
  try {
2679
- parsed = JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf-8"));
2777
+ parsed = JSON.parse(import_node_fs12.default.readFileSync(filePath, "utf-8"));
2680
2778
  } catch (error) {
2681
2779
  if (error instanceof SyntaxError) {
2682
2780
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -2701,7 +2799,7 @@ function listWorkspaceOwnership(repoPath) {
2701
2799
  function listWorkspaceOwnershipInDirectory(directory) {
2702
2800
  let entries;
2703
2801
  try {
2704
- entries = import_node_fs11.default.readdirSync(directory, { withFileTypes: true });
2802
+ entries = import_node_fs12.default.readdirSync(directory, { withFileTypes: true });
2705
2803
  } catch (error) {
2706
2804
  if (error.code === "ENOENT") return [];
2707
2805
  throw error;
@@ -2709,14 +2807,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
2709
2807
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
2710
2808
  const workspace = entry.name.slice(0, -".json".length);
2711
2809
  validateWorkspace(workspace, "filename");
2712
- return readRecordFile(import_node_path14.default.join(directory, entry.name), workspace);
2810
+ return readRecordFile(import_node_path15.default.join(directory, entry.name), workspace);
2713
2811
  });
2714
2812
  }
2715
2813
  function writeWorkspaceOwnershipInDirectory(directory, input) {
2716
2814
  const workspace = validateWorkspace(input.workspace, "workspace");
2717
2815
  const devpodId = validateWorkspace(input.devpodId, "devpodId");
2718
2816
  const worktreePath = comparableWorkspacePath(input.worktreePath);
2719
- const filePath = import_node_path14.default.join(directory, `${workspace}.json`);
2817
+ const filePath = import_node_path15.default.join(directory, `${workspace}.json`);
2720
2818
  const now = (/* @__PURE__ */ new Date()).toISOString();
2721
2819
  const records = listWorkspaceOwnershipInDirectory(directory);
2722
2820
  const existing = records.find((record5) => record5.workspace === workspace);
@@ -2752,9 +2850,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input) {
2752
2850
  return record4;
2753
2851
  }
2754
2852
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
2755
- const filePath = import_node_path14.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
2853
+ const filePath = import_node_path15.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
2756
2854
  try {
2757
- import_node_fs11.default.rmSync(filePath);
2855
+ import_node_fs12.default.rmSync(filePath);
2758
2856
  return true;
2759
2857
  } catch (error) {
2760
2858
  if (error.code === "ENOENT") return false;
@@ -2765,7 +2863,7 @@ function sameOwnershipRecord(left, right) {
2765
2863
  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;
2766
2864
  }
2767
2865
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
2768
- const filePath = import_node_path14.default.join(
2866
+ const filePath = import_node_path15.default.join(
2769
2867
  directory,
2770
2868
  `${validateWorkspace(expected.workspace, "workspace")}.json`
2771
2869
  );
@@ -2777,14 +2875,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
2777
2875
  throw error;
2778
2876
  }
2779
2877
  if (!sameOwnershipRecord(current, expected)) return "changed";
2780
- import_node_fs11.default.rmSync(filePath);
2878
+ import_node_fs12.default.rmSync(filePath);
2781
2879
  return "removed";
2782
2880
  }
2783
2881
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
2784
2882
  const directory = ownershipDirectory(repoPath);
2785
- import_node_fs11.default.mkdirSync(directory, { recursive: true });
2883
+ import_node_fs12.default.mkdirSync(directory, { recursive: true });
2786
2884
  return withFileLockSync(
2787
- import_node_path14.default.join(directory, ".lock"),
2885
+ import_node_path15.default.join(directory, ".lock"),
2788
2886
  {
2789
2887
  activity: "workspace ownership transaction",
2790
2888
  target: `'${repoPath}'`,
@@ -2817,7 +2915,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
2817
2915
  try {
2818
2916
  workspace = readPersistedWorkspace(worktree.path);
2819
2917
  } catch (error) {
2820
- if (!import_node_fs11.default.existsSync(worktree.path)) continue;
2918
+ if (!import_node_fs12.default.existsSync(worktree.path)) continue;
2821
2919
  throw error;
2822
2920
  }
2823
2921
  if (!workspace) continue;
@@ -2974,7 +3072,7 @@ function inspectWorkspaceOwnership(record4, worktrees, devpods) {
2974
3072
  if (worktree?.locked) {
2975
3073
  return { ownerStatus: "locked", devpodStatus, worktree };
2976
3074
  }
2977
- if ((!worktree || worktree.prunable) && import_node_fs11.default.existsSync(record4.worktreePath)) {
3075
+ if ((!worktree || worktree.prunable) && import_node_fs12.default.existsSync(record4.worktreePath)) {
2978
3076
  return { ownerStatus: "conflict", devpodStatus, worktree };
2979
3077
  }
2980
3078
  if (!worktree || worktree.prunable) {
@@ -3052,7 +3150,7 @@ function prove(state, baseline) {
3052
3150
  const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
3053
3151
  const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
3054
3152
  const observed = containers.map(identity).sort((a, b) => compareText(a.id, b.id));
3055
- if (!(0, import_node_util.isDeepStrictEqual)(expected, observed) || containers.some(
3153
+ if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers.some(
3056
3154
  (container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
3057
3155
  )) {
3058
3156
  throw new Error("Retained container population or immutable identity changed.");
@@ -3061,7 +3159,7 @@ function prove(state, baseline) {
3061
3159
  baseline.endpoint,
3062
3160
  baseline.composeDirectory
3063
3161
  ).sort();
3064
- if (!(0, import_node_util.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
3162
+ if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
3065
3163
  throw new Error("Workspace contains an unrecorded container population.");
3066
3164
  }
3067
3165
  const primary = baseline.containers.find(
@@ -3073,7 +3171,7 @@ function prove(state, baseline) {
3073
3171
  if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
3074
3172
  throw new Error("Provider-to-container binding changed.");
3075
3173
  }
3076
- if (!(0, import_node_util.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
3174
+ if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
3077
3175
  throw new Error("Stop authority changed during inspection.");
3078
3176
  }
3079
3177
  return containers;
@@ -3082,8 +3180,8 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
3082
3180
  const owner = registration(state);
3083
3181
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(owner.providerId))
3084
3182
  throw new Error("Invalid capture provider identity.");
3085
- const devsyRoot = import_node_path15.default.resolve(process.env.DEVSY_HOME || import_node_path15.default.join(import_node_os2.default.homedir(), ".devsy"));
3086
- const featureDirectory = import_node_path15.default.join(
3183
+ const devsyRoot = import_node_path16.default.resolve(process.env.DEVSY_HOME || import_node_path16.default.join(import_node_os3.default.homedir(), ".devsy"));
3184
+ const featureDirectory = import_node_path16.default.join(
3087
3185
  devsyRoot,
3088
3186
  "contexts",
3089
3187
  owner.context,
@@ -3133,11 +3231,11 @@ function captureManagedStopBaseline(state, plan, primaryId, expectedEndpoint) {
3133
3231
  }
3134
3232
  function proveRetainedManagedStop(state) {
3135
3233
  const baseline = validateManagedStopBaseline(state.stopBaseline, state);
3136
- if (!(0, import_node_util.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
3234
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
3137
3235
  throw new Error("Retained stop generation changed.");
3138
3236
  }
3139
3237
  const result = prove(state, baseline);
3140
- if (!(0, import_node_util.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
3238
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
3141
3239
  throw new Error("Retained stop generation changed during inspection.");
3142
3240
  }
3143
3241
  return result;
@@ -3168,8 +3266,8 @@ function stopFromManagedBaseline(state) {
3168
3266
  }
3169
3267
 
3170
3268
  // src/core/repo-config.ts
3171
- var import_node_fs13 = __toESM(require("fs"));
3172
- var import_node_path17 = __toESM(require("path"));
3269
+ var import_node_fs14 = __toESM(require("fs"));
3270
+ var import_node_path18 = __toESM(require("path"));
3173
3271
  var import_yaml3 = __toESM(require("yaml"));
3174
3272
 
3175
3273
  // src/core/capabilities.ts
@@ -3195,9 +3293,9 @@ function formatSupportedTcpProtocols() {
3195
3293
  }
3196
3294
 
3197
3295
  // src/core/host-routes.ts
3198
- var import_node_fs12 = __toESM(require("fs"));
3199
- var import_node_path16 = __toESM(require("path"));
3200
- var import_node_util2 = require("util");
3296
+ var import_node_fs13 = __toESM(require("fs"));
3297
+ var import_node_path17 = __toESM(require("path"));
3298
+ var import_node_util3 = require("util");
3201
3299
  var import_yaml2 = __toESM(require("yaml"));
3202
3300
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0"]);
3203
3301
  var UPSTREAM_RE = /^([a-zA-Z0-9._-]+):(\d{1,5})$/;
@@ -3380,7 +3478,7 @@ function writeRouteGeneration(routes, tlsEnabled) {
3380
3478
  writeFileAtomically(HOST_ROUTES_STATE_FILE, renderCompatibilityState(routes));
3381
3479
  writeFileAtomically(TRAEFIK_HOST_ROUTES_FILE, renderCanonicalState(routes, tlsEnabled));
3382
3480
  }
3383
- function readCompatibilityState(read = (file) => import_node_fs12.default.readFileSync(file, "utf-8")) {
3481
+ function readCompatibilityState(read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
3384
3482
  let raw;
3385
3483
  try {
3386
3484
  raw = read(HOST_ROUTES_STATE_FILE);
@@ -3437,14 +3535,14 @@ function parseCanonicalState(raw) {
3437
3535
  throw new Error(`Canonical host-route document is invalid: ${error.message}`);
3438
3536
  }
3439
3537
  const expectedDocument = buildHostRoutesDocument(routes, metadata.tlsEnabled);
3440
- if (!(0, import_node_util2.isDeepStrictEqual)(document, expectedDocument)) {
3538
+ if (!(0, import_node_util3.isDeepStrictEqual)(document, expectedDocument)) {
3441
3539
  throw new Error("Canonical host-route document does not match its metadata.");
3442
3540
  }
3443
3541
  return { kind: "canonical", metadata };
3444
3542
  }
3445
3543
  function ensureHostRouteStorage() {
3446
- import_node_fs12.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
3447
- import_node_fs12.default.mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });
3544
+ import_node_fs13.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
3545
+ import_node_fs13.default.mkdirSync(TRAEFIK_DYNAMIC_DIR, { recursive: true });
3448
3546
  }
3449
3547
  function writeState(routes) {
3450
3548
  ensureHostRouteStorage();
@@ -3463,8 +3561,8 @@ function refreshHostRoutesDynamicFile() {
3463
3561
  }
3464
3562
  function readHostRouteStateLocked() {
3465
3563
  ensureHostRouteStorage();
3466
- if (!import_node_fs12.default.existsSync(TRAEFIK_HOST_ROUTES_FILE)) {
3467
- if (import_node_fs12.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3564
+ if (!import_node_fs13.default.existsSync(TRAEFIK_HOST_ROUTES_FILE)) {
3565
+ if (import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3468
3566
  const routes = readCompatibilityState();
3469
3567
  writeRouteGeneration(routes, isTLSEnabled());
3470
3568
  return routes;
@@ -3472,10 +3570,10 @@ function readHostRouteStateLocked() {
3472
3570
  writeRouteGeneration([], isTLSEnabled());
3473
3571
  return [];
3474
3572
  }
3475
- const raw = import_node_fs12.default.readFileSync(TRAEFIK_HOST_ROUTES_FILE, "utf-8");
3573
+ const raw = import_node_fs13.default.readFileSync(TRAEFIK_HOST_ROUTES_FILE, "utf-8");
3476
3574
  const canonical = parseCanonicalState(raw);
3477
3575
  if (canonical.kind === "legacy") {
3478
- if (!import_node_fs12.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3576
+ if (!import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3479
3577
  throw new Error(
3480
3578
  "Headerless host-route document requires a valid compatibility state file for migration."
3481
3579
  );
@@ -3485,9 +3583,9 @@ function readHostRouteStateLocked() {
3485
3583
  return routes;
3486
3584
  }
3487
3585
  let mirrorMatches = false;
3488
- if (import_node_fs12.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3586
+ if (import_node_fs13.default.existsSync(HOST_ROUTES_STATE_FILE)) {
3489
3587
  try {
3490
- mirrorMatches = (0, import_node_util2.isDeepStrictEqual)(readCompatibilityState(), canonical.metadata.routes);
3588
+ mirrorMatches = (0, import_node_util3.isDeepStrictEqual)(readCompatibilityState(), canonical.metadata.routes);
3491
3589
  } catch {
3492
3590
  mirrorMatches = false;
3493
3591
  }
@@ -4505,15 +4603,15 @@ function applyProfile(config, profile) {
4505
4603
  return next;
4506
4604
  }
4507
4605
  function resolveRepoPath(repoPath) {
4508
- return import_node_path17.default.resolve(repoPath ?? process.cwd());
4606
+ return import_node_path18.default.resolve(repoPath ?? process.cwd());
4509
4607
  }
4510
4608
  function getRepoConfigPath(repoPath) {
4511
- return import_node_path17.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
4609
+ return import_node_path18.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
4512
4610
  }
4513
- function loadRepoConfig(repoPath, read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
4611
+ function loadRepoConfig(repoPath, read = (file) => import_node_fs14.default.readFileSync(file, "utf-8")) {
4514
4612
  const resolvedRepoPath = resolveRepoPath(repoPath);
4515
4613
  const configPath = getRepoConfigPath(resolvedRepoPath);
4516
- if (!import_node_fs13.default.existsSync(configPath)) {
4614
+ if (!import_node_fs14.default.existsSync(configPath)) {
4517
4615
  throw new Error(
4518
4616
  `Missing ${CONFIG_FILE_NAME} in ${resolvedRepoPath}. Run 'dev repo init --repo ${resolvedRepoPath}' first.`
4519
4617
  );
@@ -4523,7 +4621,7 @@ function loadRepoConfig(repoPath, read = (file) => import_node_fs13.default.read
4523
4621
  const config = parseConfig(parsed ?? {}, configPath);
4524
4622
  const requiredVersion = config.devrouter?.version;
4525
4623
  if (requiredVersion && !hasWarnedVersionMismatch) {
4526
- const cliVersion = true ? "0.0.61" : "0.0.0-dev";
4624
+ const cliVersion = true ? "0.0.62" : "0.0.0-dev";
4527
4625
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4528
4626
  hasWarnedVersionMismatch = true;
4529
4627
  process.stderr.write(
@@ -4543,7 +4641,7 @@ function namespaceHost(host, workspace) {
4543
4641
  return `${base}.${workspace}${suffix}`;
4544
4642
  }
4545
4643
  function applyWorkspace(config, workspace, repoPath) {
4546
- const defaultToken = wsFromBranch(config.project?.name ?? import_node_path17.default.basename(import_node_path17.default.resolve(repoPath))) ?? "app";
4644
+ const defaultToken = wsFromBranch(config.project?.name ?? import_node_path18.default.basename(import_node_path18.default.resolve(repoPath))) ?? "app";
4547
4645
  const substitutionToken = workspace ?? defaultToken;
4548
4646
  const next = structuredClone(config);
4549
4647
  for (const app of next.apps) {
@@ -4704,10 +4802,10 @@ async function ensureNetwork(name) {
4704
4802
  // src/core/tls.ts
4705
4803
  var import_node_child_process13 = require("child_process");
4706
4804
  var import_node_crypto7 = require("crypto");
4707
- var import_node_fs14 = __toESM(require("fs"));
4708
- var import_node_path18 = __toESM(require("path"));
4805
+ var import_node_fs15 = __toESM(require("fs"));
4806
+ var import_node_path19 = __toESM(require("path"));
4709
4807
  var DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
4710
- var TLS_CERTIFICATE_LOCK_FILE = import_node_path18.default.join(DEVROUTER_HOME, "tls-certificate.lock");
4808
+ var TLS_CERTIFICATE_LOCK_FILE = import_node_path19.default.join(DEVROUTER_HOME, "tls-certificate.lock");
4711
4809
  var TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
4712
4810
  var TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
4713
4811
  function runOrThrow(command, args) {
@@ -4739,8 +4837,8 @@ function tlsSetupCommand(repoPath) {
4739
4837
  }
4740
4838
  function getMkcertRootCAPath(options = {}) {
4741
4839
  ensureMkcert();
4742
- const rootCAPath = import_node_path18.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
4743
- if (!import_node_fs14.default.existsSync(rootCAPath)) {
4840
+ const rootCAPath = import_node_path19.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
4841
+ if (!import_node_fs15.default.existsSync(rootCAPath)) {
4744
4842
  throw new Error(
4745
4843
  `mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
4746
4844
  );
@@ -4841,11 +4939,11 @@ function compactTLSCertificateHosts(hosts) {
4841
4939
  );
4842
4940
  }
4843
4941
  function readCurrentCertificateHosts(options = {}) {
4844
- if (!import_node_fs14.default.existsSync(CERT_FILE)) {
4942
+ if (!import_node_fs15.default.existsSync(CERT_FILE)) {
4845
4943
  return [];
4846
4944
  }
4847
4945
  try {
4848
- const pem = import_node_fs14.default.readFileSync(CERT_FILE, "utf-8");
4946
+ const pem = import_node_fs15.default.readFileSync(CERT_FILE, "utf-8");
4849
4947
  return parseCertificateDnsHosts(pem);
4850
4948
  } catch (error) {
4851
4949
  if (options.replaceMalformed) {
@@ -5084,8 +5182,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
5084
5182
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
5085
5183
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
5086
5184
  }
5087
- const devsyRoot = import_node_path19.default.resolve(process.env.DEVSY_HOME || import_node_path19.default.join(import_node_os3.default.homedir(), ".devsy"));
5088
- const featureDirectory = import_node_path19.default.join(
5185
+ const devsyRoot = import_node_path20.default.resolve(process.env.DEVSY_HOME || import_node_path20.default.join(import_node_os4.default.homedir(), ".devsy"));
5186
+ const featureDirectory = import_node_path20.default.join(
5089
5187
  devsyRoot,
5090
5188
  "contexts",
5091
5189
  context,
@@ -5191,7 +5289,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
5191
5289
  }
5192
5290
 
5193
5291
  // src/core/devsy-mutation.ts
5194
- var DEVSY_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
5292
+ var DEVSY_MUTATION_LOCK_FILE = import_node_path21.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
5195
5293
  var DEVSY_MUTATION_WAIT_MS = 18e5;
5196
5294
  var DevsyStartPostconditionError = class extends Error {
5197
5295
  };
@@ -5206,7 +5304,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
5206
5304
  }
5207
5305
  }
5208
5306
  function withMutationLock(activity, target, operation) {
5209
- import_node_fs15.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
5307
+ import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
5210
5308
  return withFileLockSync(
5211
5309
  DEVSY_MUTATION_LOCK_FILE,
5212
5310
  {
@@ -5220,7 +5318,7 @@ function withMutationLock(activity, target, operation) {
5220
5318
  );
5221
5319
  }
5222
5320
  function withMutationLockAsync(activity, target, operation) {
5223
- import_node_fs15.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
5321
+ import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
5224
5322
  return withFileLock(
5225
5323
  DEVSY_MUTATION_LOCK_FILE,
5226
5324
  {
@@ -5424,6 +5522,19 @@ async function startDevsyWorkspace(options) {
5424
5522
  });
5425
5523
  }
5426
5524
 
5525
+ // src/core/execution-outcome.ts
5526
+ var ExecutionOutcomeError = class extends Error {
5527
+ outcome;
5528
+ constructor(message, outcome) {
5529
+ super(message);
5530
+ this.name = "ExecutionOutcomeError";
5531
+ this.outcome = outcome;
5532
+ }
5533
+ };
5534
+
5535
+ // src/core/reliability-lifecycle.ts
5536
+ var import_node_path28 = __toESM(require("path"));
5537
+
5427
5538
  // src/core/reliability-contract.ts
5428
5539
  var RELIABILITY_CONTRACT_VERSION = 2;
5429
5540
  var RELIABILITY_MAX_ITEMS = 128;
@@ -5553,7 +5664,7 @@ function assertReliabilityEvent(value) {
5553
5664
  valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
5554
5665
  break;
5555
5666
  case "operation-request":
5556
- valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
5667
+ valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean" && (value.recoverInterruptedEnsure === void 0 || typeof value.recoverInterruptedEnsure === "boolean");
5557
5668
  break;
5558
5669
  case "drained":
5559
5670
  valid = isReliabilityId(value.operationId);
@@ -5736,6 +5847,10 @@ function handleRequest(state, event) {
5736
5847
  });
5737
5848
  return transition(state, "accepted");
5738
5849
  }
5850
+ function canExecAfterInterruptedEnsure(state) {
5851
+ const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
5852
+ return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
5853
+ }
5739
5854
  function handleOperationRequest(state, event) {
5740
5855
  if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
5741
5856
  const previous = state.operationHistory.find((entry) => entry.key === event.key);
@@ -5746,9 +5861,11 @@ function handleOperationRequest(state, event) {
5746
5861
  );
5747
5862
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
5748
5863
  return unchanged(state, "conflict");
5864
+ if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
5865
+ return unchanged(state, "blocked");
5749
5866
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
5750
5867
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
5751
- if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
5868
+ if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
5752
5869
  return unchanged(state, "blocked");
5753
5870
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
5754
5871
  return unchanged(state, "blocked");
@@ -5759,8 +5876,9 @@ function handleOperationRequest(state, event) {
5759
5876
  if (rollover) {
5760
5877
  if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
5761
5878
  return unchanged(state, "blocked");
5879
+ const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
5762
5880
  retired = state.operationHistory.findIndex(
5763
- (entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
5881
+ (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
5764
5882
  );
5765
5883
  if (retired === -1) return unchanged(state, "blocked");
5766
5884
  }
@@ -6129,17 +6247,17 @@ function stepReliability(state, event, nowMs) {
6129
6247
 
6130
6248
  // src/core/reliability-operation-store.ts
6131
6249
  var import_node_crypto8 = require("crypto");
6132
- var import_node_fs16 = __toESM(require("fs"));
6133
- var import_node_path21 = __toESM(require("path"));
6250
+ var import_node_fs17 = __toESM(require("fs"));
6251
+ var import_node_path22 = __toESM(require("path"));
6134
6252
  var MAX_RECORD_BYTES = 1048576;
6135
6253
  function identityKey(identity2) {
6136
- if (!import_node_path21.default.isAbsolute(identity2.repoPath) || import_node_path21.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
6254
+ if (!import_node_path22.default.isAbsolute(identity2.repoPath) || import_node_path22.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
6137
6255
  throw new Error("Invalid reliability workspace identity.");
6138
6256
  }
6139
6257
  return (0, import_node_crypto8.createHash)("sha256").update(identity2.repoPath).digest("hex");
6140
6258
  }
6141
6259
  function reliabilityOperationPath(identity2) {
6142
- return import_node_path21.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
6260
+ return import_node_path22.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
6143
6261
  }
6144
6262
  function keys(value, expected) {
6145
6263
  if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
@@ -6218,21 +6336,21 @@ function readReliabilityOperation(identity2) {
6218
6336
  const file = reliabilityOperationPath(identity2);
6219
6337
  let descriptor;
6220
6338
  try {
6221
- descriptor = import_node_fs16.default.openSync(
6339
+ descriptor = import_node_fs17.default.openSync(
6222
6340
  file,
6223
- import_node_fs16.default.constants.O_RDONLY | import_node_fs16.default.constants.O_NOFOLLOW | import_node_fs16.default.constants.O_NONBLOCK
6341
+ import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NOFOLLOW | import_node_fs17.default.constants.O_NONBLOCK
6224
6342
  );
6225
6343
  } catch (error) {
6226
6344
  if (error.code === "ENOENT") return void 0;
6227
6345
  throw error;
6228
6346
  }
6229
6347
  try {
6230
- const stat = import_node_fs16.default.fstatSync(descriptor);
6348
+ const stat = import_node_fs17.default.fstatSync(descriptor);
6231
6349
  if (!stat.isFile() || stat.size > MAX_RECORD_BYTES || (stat.mode & 63) !== 0) {
6232
6350
  throw new Error("Reliability record is not a bounded private file.");
6233
6351
  }
6234
6352
  const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
6235
- const count = import_node_fs16.default.readSync(descriptor, bytes, 0, bytes.length, 0);
6353
+ const count = import_node_fs17.default.readSync(descriptor, bytes, 0, bytes.length, 0);
6236
6354
  if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
6237
6355
  const record4 = JSON.parse(
6238
6356
  bytes.subarray(0, count).toString("utf8")
@@ -6240,7 +6358,7 @@ function readReliabilityOperation(identity2) {
6240
6358
  validate(record4, identity2);
6241
6359
  return record4;
6242
6360
  } finally {
6243
- import_node_fs16.default.closeSync(descriptor);
6361
+ import_node_fs17.default.closeSync(descriptor);
6244
6362
  }
6245
6363
  }
6246
6364
  function persist(record4) {
@@ -6255,23 +6373,23 @@ function persist(record4) {
6255
6373
  } catch (error) {
6256
6374
  const reread = readReliabilityOperation(record4.identity);
6257
6375
  if (!reread || JSON.stringify(reread) !== JSON.stringify(record4)) throw error;
6258
- const descriptor = import_node_fs16.default.openSync(file, import_node_fs16.default.constants.O_RDONLY | import_node_fs16.default.constants.O_NOFOLLOW);
6376
+ const descriptor = import_node_fs17.default.openSync(file, import_node_fs17.default.constants.O_RDONLY | import_node_fs17.default.constants.O_NOFOLLOW);
6259
6377
  try {
6260
- import_node_fs16.default.fsyncSync(descriptor);
6378
+ import_node_fs17.default.fsyncSync(descriptor);
6261
6379
  } finally {
6262
- import_node_fs16.default.closeSync(descriptor);
6380
+ import_node_fs17.default.closeSync(descriptor);
6263
6381
  }
6264
- const directory = import_node_fs16.default.openSync(import_node_path21.default.dirname(file), "r");
6382
+ const directory = import_node_fs17.default.openSync(import_node_path22.default.dirname(file), "r");
6265
6383
  try {
6266
- import_node_fs16.default.fsyncSync(directory);
6384
+ import_node_fs17.default.fsyncSync(directory);
6267
6385
  } finally {
6268
- import_node_fs16.default.closeSync(directory);
6386
+ import_node_fs17.default.closeSync(directory);
6269
6387
  }
6270
6388
  }
6271
6389
  }
6272
6390
  function updateReliabilityOperation(identity2, operation) {
6273
6391
  const file = reliabilityOperationPath(identity2);
6274
- import_node_fs16.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true, mode: 448 });
6392
+ import_node_fs17.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true, mode: 448 });
6275
6393
  return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
6276
6394
  const record4 = readReliabilityOperation(identity2) ?? {
6277
6395
  version: 1,
@@ -6298,8 +6416,8 @@ function updateReliabilityOperation(identity2, operation) {
6298
6416
  // src/core/reliability-worker.ts
6299
6417
  var import_node_child_process15 = require("child_process");
6300
6418
  var import_node_crypto9 = require("crypto");
6301
- var import_node_fs17 = __toESM(require("fs"));
6302
- var import_node_path22 = __toESM(require("path"));
6419
+ var import_node_fs18 = __toESM(require("fs"));
6420
+ var import_node_path23 = __toESM(require("path"));
6303
6421
  function workerGroupAbsent(pid) {
6304
6422
  try {
6305
6423
  process.kill(-pid, 0);
@@ -6311,19 +6429,19 @@ function workerGroupAbsent(pid) {
6311
6429
 
6312
6430
  // src/core/workspace-ensure.ts
6313
6431
  var import_node_child_process21 = require("child_process");
6314
- var import_node_fs22 = __toESM(require("fs"));
6315
- var import_node_path26 = __toESM(require("path"));
6432
+ var import_node_fs23 = __toESM(require("fs"));
6433
+ var import_node_path27 = __toESM(require("path"));
6316
6434
 
6317
6435
  // src/core/devpod-mutation.ts
6318
6436
  var import_node_child_process16 = require("child_process");
6319
- var import_node_fs18 = __toESM(require("fs"));
6320
- var import_node_path23 = __toESM(require("path"));
6321
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path23.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
6437
+ var import_node_fs19 = __toESM(require("fs"));
6438
+ var import_node_path24 = __toESM(require("path"));
6439
+ var DEVPOD_MUTATION_LOCK_FILE = import_node_path24.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
6322
6440
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
6323
6441
  var DevpodStartPostconditionError = class extends Error {
6324
6442
  };
6325
6443
  function withMutationLock2(activity, target, operation) {
6326
- import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6444
+ import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6327
6445
  return withFileLockSync(
6328
6446
  DEVPOD_MUTATION_LOCK_FILE,
6329
6447
  {
@@ -6642,7 +6760,7 @@ function transportFailure() {
6642
6760
 
6643
6761
  // src/core/managed-host-preparation.ts
6644
6762
  var import_node_child_process18 = require("child_process");
6645
- var import_node_fs19 = __toESM(require("fs"));
6763
+ var import_node_fs20 = __toESM(require("fs"));
6646
6764
  var PREPARATION_TIMEOUT_MS = 6e4;
6647
6765
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
6648
6766
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -6685,7 +6803,7 @@ function runPreparationCommand(argv, cwd) {
6685
6803
  }
6686
6804
  function readConfigBytes(configPath) {
6687
6805
  try {
6688
- return import_node_fs19.default.readFileSync(configPath);
6806
+ return import_node_fs20.default.readFileSync(configPath);
6689
6807
  } catch {
6690
6808
  return void 0;
6691
6809
  }
@@ -6721,8 +6839,8 @@ async function runManagedHostPreparation(repoPath, config) {
6721
6839
  // src/core/managed-post-start.ts
6722
6840
  var import_node_child_process19 = require("child_process");
6723
6841
  var import_node_crypto10 = require("crypto");
6724
- var import_node_fs20 = __toESM(require("fs"));
6725
- var import_node_path24 = __toESM(require("path"));
6842
+ var import_node_fs21 = __toESM(require("fs"));
6843
+ var import_node_path25 = __toESM(require("path"));
6726
6844
  var MANAGED_MARKER = "devrouter:managed devcontainer";
6727
6845
  var MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
6728
6846
  var RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
@@ -6735,7 +6853,7 @@ function renderDeliveryScript(targetPath) {
6735
6853
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
6736
6854
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
6737
6855
  }
6738
- const temporaryPrefix = import_node_path24.default.posix.basename(targetPath);
6856
+ const temporaryPrefix = import_node_path25.default.posix.basename(targetPath);
6739
6857
  return `set -eu
6740
6858
  umask 077
6741
6859
  runtime_root=/tmp/devrouter
@@ -6770,8 +6888,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
6770
6888
  }
6771
6889
  }
6772
6890
  function readRegularFileBytes(filePath) {
6773
- if (!import_node_fs20.default.existsSync(filePath) || !import_node_fs20.default.lstatSync(filePath).isFile()) return void 0;
6774
- return import_node_fs20.default.readFileSync(filePath);
6891
+ if (!import_node_fs21.default.existsSync(filePath) || !import_node_fs21.default.lstatSync(filePath).isFile()) return void 0;
6892
+ return import_node_fs21.default.readFileSync(filePath);
6775
6893
  }
6776
6894
  function readRegularFile(filePath) {
6777
6895
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -6781,21 +6899,21 @@ function adapterFingerprint(adapter) {
6781
6899
  }
6782
6900
  function resolveProcessHelperPath() {
6783
6901
  const candidates = [
6784
- import_node_path24.default.resolve(__dirname, "..", "bin", "devrouter-process"),
6785
- import_node_path24.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
6902
+ import_node_path25.default.resolve(__dirname, "..", "bin", "devrouter-process"),
6903
+ import_node_path25.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
6786
6904
  ];
6787
- const helperPath = candidates.find((candidate) => import_node_fs20.default.existsSync(candidate));
6905
+ const helperPath = candidates.find((candidate) => import_node_fs21.default.existsSync(candidate));
6788
6906
  if (!helperPath) {
6789
6907
  throw new Error("Could not locate the packaged devrouter-process helper.");
6790
6908
  }
6791
6909
  return helperPath;
6792
6910
  }
6793
6911
  function resolveManagedPostStartPlan(repoPath) {
6794
- const adapterPath = import_node_path24.default.join(repoPath, MANAGED_ADAPTER_PATH);
6912
+ const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
6795
6913
  const adapterBytes = readRegularFileBytes(adapterPath);
6796
6914
  const adapterText = adapterBytes?.toString("utf-8");
6797
- const dockerfilePath = import_node_path24.default.join(repoPath, ".devcontainer", "Dockerfile");
6798
- const devcontainerPath = import_node_path24.default.join(repoPath, ".devcontainer", "devcontainer.json");
6915
+ const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
6916
+ const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
6799
6917
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
6800
6918
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
6801
6919
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -6836,7 +6954,7 @@ function resolveManagedPostStartPlan(repoPath) {
6836
6954
  }
6837
6955
  function runManagedPostStart(options) {
6838
6956
  if (options.plan.kind !== "runtime") return;
6839
- const helper = import_node_fs20.default.readFileSync(resolveProcessHelperPath());
6957
+ const helper = import_node_fs21.default.readFileSync(resolveProcessHelperPath());
6840
6958
  deliverRuntimeFile(
6841
6959
  options.container.id,
6842
6960
  RUNTIME_HELPER_PATH,
@@ -7353,10 +7471,10 @@ function collectManagedRuntimeStatus(options) {
7353
7471
 
7354
7472
  // src/core/traefik-route-health.ts
7355
7473
  var import_node_child_process20 = require("child_process");
7356
- var import_node_fs21 = __toESM(require("fs"));
7357
- var import_node_path25 = __toESM(require("path"));
7474
+ var import_node_fs22 = __toESM(require("fs"));
7475
+ var import_node_path26 = __toESM(require("path"));
7358
7476
  var ROUTER_API_BASE = "http://127.0.0.1:8080/api";
7359
- var ROUTER_RELOAD_LOCK_FILE = import_node_path25.default.join(DEVROUTER_HOME, "router-reload.lock");
7477
+ var ROUTER_RELOAD_LOCK_FILE = import_node_path26.default.join(DEVROUTER_HOME, "router-reload.lock");
7360
7478
  var ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
7361
7479
  var DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
7362
7480
  var DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -7620,7 +7738,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
7620
7738
  throw new Error(
7621
7739
  "Traefik routes did not reload; shared router restart is disabled for this operation."
7622
7740
  );
7623
- import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7741
+ import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7624
7742
  return withFileLock(
7625
7743
  ROUTER_RELOAD_LOCK_FILE,
7626
7744
  {
@@ -7672,11 +7790,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
7672
7790
  var POLL_INTERVAL_MS = 1e3;
7673
7791
  function assertOverlay(container, repoPath) {
7674
7792
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
7675
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))) {
7793
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path27.default.join(repoPath, ".devcontainer"))) {
7676
7794
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
7677
7795
  }
7678
7796
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
7679
- const expectedOverlay = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
7797
+ const expectedOverlay = import_node_path27.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
7680
7798
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
7681
7799
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
7682
7800
  }
@@ -7871,7 +7989,7 @@ function hasExactManagedComposeProject(repoPath, state) {
7871
7989
  }).some((container) => {
7872
7990
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
7873
7991
  return Boolean(
7874
- workingDir && sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))
7992
+ workingDir && sameWorkspacePath(workingDir, import_node_path27.default.join(repoPath, ".devcontainer"))
7875
7993
  );
7876
7994
  });
7877
7995
  } catch {
@@ -8255,7 +8373,7 @@ function resolvePrimaryTarget(repoPath) {
8255
8373
  }
8256
8374
  function isPrimaryCheckout(repoPath) {
8257
8375
  try {
8258
- return import_node_fs22.default.statSync(import_node_path26.default.join(repoPath, ".git")).isDirectory();
8376
+ return import_node_fs23.default.statSync(import_node_path27.default.join(repoPath, ".git")).isDirectory();
8259
8377
  } catch {
8260
8378
  return false;
8261
8379
  }
@@ -8316,8 +8434,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
8316
8434
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
8317
8435
  let devpodId = target.devpodId;
8318
8436
  if (target.kind === "linked") {
8319
- const overlayPath = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
8320
- if (!import_node_fs22.default.existsSync(overlayPath)) {
8437
+ const overlayPath = import_node_path27.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
8438
+ if (!import_node_fs23.default.existsSync(overlayPath)) {
8321
8439
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
8322
8440
  }
8323
8441
  }
@@ -8387,7 +8505,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
8387
8505
  }
8388
8506
  const apps = proxyAppsFromConfig(runtime.config);
8389
8507
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
8390
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path26.default.basename(repoPath)) ?? "app";
8508
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path27.default.basename(repoPath)) ?? "app";
8391
8509
  for (const [index, app] of apps.entries()) {
8392
8510
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
8393
8511
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -9157,9 +9275,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9157
9275
  }
9158
9276
  const repairLocked = () => {
9159
9277
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
9160
- import_node_fs22.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9278
+ import_node_fs23.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9161
9279
  return withFileLock(
9162
- import_node_path26.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
9280
+ import_node_path27.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
9163
9281
  {
9164
9282
  activity: "Managed runtime repair",
9165
9283
  target: repoPath,
@@ -9354,7 +9472,7 @@ async function executeLifecycleWorker(request, operation) {
9354
9472
  const owned = containers.filter(
9355
9473
  (container) => sameWorkspacePath(
9356
9474
  container.labels["com.docker.compose.project.working_dir"] ?? "",
9357
- import_node_path27.default.join(request.repoPath, ".devcontainer")
9475
+ import_node_path28.default.join(request.repoPath, ".devcontainer")
9358
9476
  )
9359
9477
  );
9360
9478
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -9440,7 +9558,7 @@ function proveLifecycleStopped() {
9440
9558
  const remaining = inspectWorkspaceContainers().filter(
9441
9559
  (container) => sameWorkspacePath(
9442
9560
  container.labels["com.docker.compose.project.working_dir"] ?? "",
9443
- import_node_path27.default.join(request.repoPath, ".devcontainer")
9561
+ import_node_path28.default.join(request.repoPath, ".devcontainer")
9444
9562
  )
9445
9563
  );
9446
9564
  if (remaining.some(
@@ -9476,7 +9594,7 @@ function ensureGuidance(repoPath) {
9476
9594
  function errorMessage(error) {
9477
9595
  return error instanceof Error ? error.message : String(error);
9478
9596
  }
9479
- async function devsyExecOutcome(repoPath, command) {
9597
+ async function devsyExecOutcome(repoPath, command, retainedProof) {
9480
9598
  if (command.length === 0) {
9481
9599
  throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
9482
9600
  }
@@ -9487,12 +9605,25 @@ async function devsyExecOutcome(repoPath, command) {
9487
9605
  `No exact Devsy workspace exists for '${repoPath}'. ${ensureGuidance(repoPath)}`
9488
9606
  );
9489
9607
  }
9490
- const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
9608
+ const args = [
9609
+ "workspace",
9610
+ "exec",
9611
+ "--result-format",
9612
+ "plain",
9613
+ ...retainedProof ? ["--context", retainedProof.context] : [],
9614
+ retainedProof?.id ?? workspace.id,
9615
+ "--",
9616
+ ...command
9617
+ ];
9491
9618
  return new Promise((resolve, reject) => {
9492
9619
  let child;
9493
9620
  try {
9494
- claimLifecycleEffect();
9495
- child = (0, import_node_child_process22.spawn)("devsy", args, { stdio: "inherit" });
9621
+ const launch = () => {
9622
+ if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
9623
+ claimLifecycleEffect();
9624
+ return (0, import_node_child_process22.spawn)("devsy", args, { stdio: "inherit" });
9625
+ };
9626
+ child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
9496
9627
  } catch (error) {
9497
9628
  reject(
9498
9629
  new ExecutionOutcomeError(`devsy exec failed: ${errorMessage(error)}`, {
@@ -9775,11 +9906,11 @@ async function devpodExecOutcome(repoPath, command) {
9775
9906
 
9776
9907
  // src/core/workspace-lifecycle.ts
9777
9908
  var import_node_child_process24 = require("child_process");
9778
- var import_node_fs24 = __toESM(require("fs"));
9779
- var import_node_path28 = __toESM(require("path"));
9909
+ var import_node_fs25 = __toESM(require("fs"));
9910
+ var import_node_path29 = __toESM(require("path"));
9780
9911
 
9781
9912
  // src/core/route-state.ts
9782
- var import_node_fs23 = __toESM(require("fs"));
9913
+ var import_node_fs24 = __toESM(require("fs"));
9783
9914
  function removeWorkspaceRoutesForWorktree(workspace, worktreePath) {
9784
9915
  return removeHostRoutesWhere(
9785
9916
  (route) => route.workspace === workspace && sameWorkspacePath(route.repoPath, worktreePath)
@@ -9953,7 +10084,11 @@ process.once("message", async (message) => {
9953
10084
  return stopped;
9954
10085
  }
9955
10086
  if (request.kind === "exec") {
9956
- const outcome = await devpodExecOutcome(request.repoPath, request.command ?? []);
10087
+ const outcome = request.retainedExecProof ? await devsyExecOutcome(
10088
+ request.repoPath,
10089
+ request.command ?? [],
10090
+ request.retainedExecProof
10091
+ ) : await devpodExecOutcome(request.repoPath, request.command ?? []);
9957
10092
  recordLifecycleOutcome(outcome);
9958
10093
  return outcome;
9959
10094
  }