@devrouter/cli 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,8 +23,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/core/devpod-exec.ts
26
- var import_node_child_process33 = require("child_process");
27
- var import_node_crypto25 = require("crypto");
26
+ var import_node_child_process34 = require("child_process");
27
+ var import_node_crypto26 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
30
30
  var import_node_child_process3 = require("child_process");
@@ -497,8 +497,8 @@ function wsFromBranch(branch) {
497
497
  function workspaceIdentityFallback(source, attempt) {
498
498
  const readablePrefix = (wsFromBranch(source) ?? "workspace").slice(0, WORKSPACE_IDENTITY_PREFIX_LENGTH).replace(/-+$/, "");
499
499
  const prefix = readablePrefix || "workspace";
500
- const hash = (0, import_node_crypto3.createHash)("sha256").update(WORKSPACE_IDENTITY_HASH_DOMAIN, "utf-8").update("\0", "utf-8").update(source, "utf-8").update("\0", "utf-8").update(String(attempt), "utf-8").digest("hex").slice(0, WORKSPACE_IDENTITY_HASH_LENGTH);
501
- return `${prefix}-${hash}`;
500
+ const hash2 = (0, import_node_crypto3.createHash)("sha256").update(WORKSPACE_IDENTITY_HASH_DOMAIN, "utf-8").update("\0", "utf-8").update(source, "utf-8").update("\0", "utf-8").update(String(attempt), "utf-8").digest("hex").slice(0, WORKSPACE_IDENTITY_HASH_LENGTH);
501
+ return `${prefix}-${hash2}`;
502
502
  }
503
503
  function workspaceIdentityCandidates(source) {
504
504
  const candidates = [];
@@ -1982,7 +1982,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
1982
1982
  }
1983
1983
 
1984
1984
  // src/core/devsy-exec.ts
1985
- var import_node_child_process32 = require("child_process");
1985
+ var import_node_child_process33 = require("child_process");
1986
1986
 
1987
1987
  // src/core/devsy-exec-proof.ts
1988
1988
  var import_node_fs7 = __toESM(require("fs"));
@@ -2815,7 +2815,7 @@ function assertManagedContainerConfigUnchanged(options) {
2815
2815
  if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
2816
2816
  throw new Error("Compose configuration rendering failed.");
2817
2817
  }
2818
- const hash = (input2) => (0, import_node_child_process10.spawnSync)(
2818
+ const hash2 = (input2) => (0, import_node_child_process10.spawnSync)(
2819
2819
  "docker",
2820
2820
  [...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
2821
2821
  {
@@ -2829,7 +2829,7 @@ function assertManagedContainerConfigUnchanged(options) {
2829
2829
  }
2830
2830
  );
2831
2831
  const input = rendered.stdout.replaceAll("$$", "$");
2832
- const result2 = hash(input);
2832
+ const result2 = hash2(input);
2833
2833
  const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
2834
2834
  if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
2835
2835
  return result2;
@@ -2844,7 +2844,7 @@ function assertManagedContainerConfigUnchanged(options) {
2844
2844
  serviceConfig,
2845
2845
  env
2846
2846
  });
2847
- const resolved = hash(JSON.stringify(model));
2847
+ const resolved = hash2(JSON.stringify(model));
2848
2848
  revalidate();
2849
2849
  return resolved;
2850
2850
  } catch {
@@ -5589,7 +5589,7 @@ function retain(session, previous) {
5589
5589
 
5590
5590
  // src/core/reliability-operation-store.ts
5591
5591
  function installedCliVersion() {
5592
- return true ? "0.6.1" : "0.0.0-dev";
5592
+ return true ? "0.6.2" : "0.0.0-dev";
5593
5593
  }
5594
5594
  function compareCliVersions(left, right) {
5595
5595
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -5737,6 +5737,7 @@ function validate2(record5, identity2) {
5737
5737
  "state",
5738
5738
  "worker",
5739
5739
  "stopWorkStarted",
5740
+ "freshBootstrap",
5740
5741
  "effectSequence",
5741
5742
  "outcome",
5742
5743
  "result",
@@ -5754,6 +5755,47 @@ function validate2(record5, identity2) {
5754
5755
  ]);
5755
5756
  validateConsumerProtection(record5);
5756
5757
  validateIdleProtection(record5);
5758
+ if (record5.freshBootstrap !== void 0) {
5759
+ const attempt = record5.freshBootstrap;
5760
+ keys(attempt, [
5761
+ "version",
5762
+ "operationId",
5763
+ "workerId",
5764
+ "runtimeGeneration",
5765
+ "sourceSha256",
5766
+ "providerId",
5767
+ "providerContext",
5768
+ "definitionSha256",
5769
+ "endpoint",
5770
+ "daemonId",
5771
+ "previousVolumeNameDigests",
5772
+ "stage",
5773
+ "resources"
5774
+ ]);
5775
+ const digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
5776
+ const exact = (value) => typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(value);
5777
+ if (attempt.version !== 1 || !isReliabilityId(attempt.operationId) || !isReliabilityId(attempt.workerId) || !isReliabilityCounter(attempt.runtimeGeneration) || !digest(attempt.sourceSha256) || !digest(attempt.definitionSha256) || !exact(attempt.providerId) || !exact(attempt.providerContext) || typeof attempt.endpoint !== "string" || !attempt.endpoint.startsWith("unix:///") || attempt.endpoint.includes("\0") || typeof attempt.daemonId !== "string" || attempt.daemonId.length === 0 || attempt.daemonId.length > 256 || !["claimed", "provisioned", "initializing", "initialized", "ready", "failed"].includes(
5778
+ attempt.stage
5779
+ ) || !Array.isArray(attempt.previousVolumeNameDigests) || attempt.previousVolumeNameDigests.length > 4096 || !attempt.previousVolumeNameDigests.every(digest) || new Set(attempt.previousVolumeNameDigests).size !== attempt.previousVolumeNameDigests.length)
5780
+ throw new Error("Invalid durable fresh-bootstrap attempt.");
5781
+ if (attempt.resources !== void 0) {
5782
+ const resources = attempt.resources;
5783
+ keys(resources, ["primaryId", "project", "dataVolumes"]);
5784
+ if (!digest(resources.primaryId) || !exact(resources.project) || !Array.isArray(resources.dataVolumes) || resources.dataVolumes.length === 0 || resources.dataVolumes.length > 64)
5785
+ throw new Error("Invalid fresh-bootstrap resource binding.");
5786
+ const names = /* @__PURE__ */ new Set();
5787
+ const bindings = /* @__PURE__ */ new Set();
5788
+ for (const volume of resources.dataVolumes) {
5789
+ keys(volume, ["key", "name", "createdAt", "mountpoint"]);
5790
+ if (!exact(volume.key) || typeof volume.name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(volume.name) || names.has(volume.name) || bindings.has(volume.key) || typeof volume.createdAt !== "string" || !Number.isFinite(Date.parse(volume.createdAt)) || typeof volume.mountpoint !== "string" || !import_node_path21.default.isAbsolute(volume.mountpoint) || volume.mountpoint.includes("\0"))
5791
+ throw new Error("Invalid fresh-bootstrap data-volume binding.");
5792
+ names.add(volume.name);
5793
+ bindings.add(volume.key);
5794
+ }
5795
+ } else if (["provisioned", "initializing", "initialized", "ready"].includes(attempt.stage)) {
5796
+ throw new Error("Fresh-bootstrap phase requires proven resource bindings.");
5797
+ }
5798
+ }
5757
5799
  if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
5758
5800
  throw new Error(
5759
5801
  `Reliability record was last written by devrouter ${record5.writtenByVersion}, which is newer than the installed ${installedCliVersion()}; upgrade devrouter (devrouter upgrade) before running lifecycle commands.`
@@ -6233,10 +6275,30 @@ function updateReliabilityOperation(identity2, operation) {
6233
6275
  const previousEnrollment = record5.enrollment && { ...record5.enrollment };
6234
6276
  const previousProtection = structuredClone(record5.consumerProtection);
6235
6277
  const previousIdleProtection = structuredClone(record5.idleProtection);
6278
+ const previousBootstrap = structuredClone(record5.freshBootstrap);
6236
6279
  const result = operation(record5);
6237
6280
  if (result && typeof result.then === "function") {
6238
6281
  throw new Error("Reliability record updates must be synchronous.");
6239
6282
  }
6283
+ if (previousBootstrap && !(0, import_node_util4.isDeepStrictEqual)(previousBootstrap, record5.freshBootstrap)) {
6284
+ const current = record5.freshBootstrap;
6285
+ const {
6286
+ stage: previousStage,
6287
+ resources: previousResources,
6288
+ ...previousIdentity
6289
+ } = previousBootstrap;
6290
+ const { stage, resources, ...currentIdentity } = current ?? {};
6291
+ const next = {
6292
+ claimed: ["provisioned", "failed"],
6293
+ provisioned: ["initializing", "failed"],
6294
+ initializing: ["initialized", "failed"],
6295
+ initialized: ["ready"],
6296
+ ready: [],
6297
+ failed: []
6298
+ };
6299
+ if (!current || !(0, import_node_util4.isDeepStrictEqual)(previousIdentity, currentIdentity) || !next[previousStage].includes(stage ?? "") || previousResources && !(0, import_node_util4.isDeepStrictEqual)(previousResources, resources))
6300
+ throw new Error("Durable fresh-bootstrap attempt cannot be cleared, replaced or rearmed.");
6301
+ }
6240
6302
  if (record5.version < previousVersion || previousEnrollment && !(0, import_node_util4.isDeepStrictEqual)(record5.enrollment, previousEnrollment))
6241
6303
  throw new Error("Durable capacity enrollment cannot be downgraded or replaced.");
6242
6304
  if (!(0, import_node_util4.isDeepStrictEqual)(record5.consumerProtection, previousProtection))
@@ -6702,7 +6764,7 @@ function parseApp(value, index) {
6702
6764
  if (objectValue.readiness !== void 0) {
6703
6765
  throw new Error(`${pathLabel}.readiness is only supported for HTTP proxy apps.`);
6704
6766
  }
6705
- const docker = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
6767
+ const docker2 = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
6706
6768
  if (protocol === "http") {
6707
6769
  return {
6708
6770
  name,
@@ -6710,7 +6772,7 @@ function parseApp(value, index) {
6710
6772
  protocol: "http",
6711
6773
  runtime: "docker",
6712
6774
  dependencies,
6713
- docker
6775
+ docker: docker2
6714
6776
  };
6715
6777
  }
6716
6778
  if (protocol === "tcp") {
@@ -6727,7 +6789,7 @@ function parseApp(value, index) {
6727
6789
  tcpProtocol,
6728
6790
  runtime: "docker",
6729
6791
  dependencies,
6730
- docker
6792
+ docker: docker2
6731
6793
  };
6732
6794
  }
6733
6795
  }
@@ -6913,7 +6975,7 @@ function parseManagedRuntime(value, configPath) {
6913
6975
  );
6914
6976
  ensureAllowedKeys(
6915
6977
  devcontainer,
6916
- ["baseServices", "profileServices", "prepareCommand", "hostPorts"],
6978
+ ["baseServices", "profileServices", "prepareCommand", "hostPorts", "freshBootstrap"],
6917
6979
  `${configPath}.managedRuntime.devcontainer`
6918
6980
  );
6919
6981
  const baseServices = parseRequiredUniqueStringArray(
@@ -6928,6 +6990,23 @@ function parseManagedRuntime(value, configPath) {
6928
6990
  devcontainer.prepareCommand,
6929
6991
  `${configPath}.managedRuntime.devcontainer.prepareCommand`
6930
6992
  );
6993
+ let freshBootstrap;
6994
+ if (devcontainer.freshBootstrap !== void 0) {
6995
+ const label = `${configPath}.managedRuntime.devcontainer.freshBootstrap`;
6996
+ const raw = ensureObject(devcontainer.freshBootstrap, label);
6997
+ ensureAllowedKeys(raw, ["command", "dataVolumes", "sharedCaches"], label);
6998
+ const command = parsePrepareCommand(raw.command, `${label}.command`);
6999
+ if (!command) throw new Error(`${label}.command is required.`);
7000
+ const dataVolumes = parseRequiredUniqueStringArray(raw.dataVolumes, `${label}.dataVolumes`);
7001
+ const sharedCaches = raw.sharedCaches === void 0 ? [] : parseRequiredUniqueStringArray(raw.sharedCaches, `${label}.sharedCaches`);
7002
+ if (dataVolumes.length === 0 || dataVolumes.length + sharedCaches.length > 64)
7003
+ throw new Error(`${label} requires data volumes and at most 64 volume bindings.`);
7004
+ if ([...dataVolumes, ...sharedCaches].some(
7005
+ (key) => !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(key)
7006
+ ) || dataVolumes.some((key) => sharedCaches.includes(key)))
7007
+ throw new Error(`${label} requires distinct exact data-volume and shared-cache keys.`);
7008
+ freshBootstrap = { command, dataVolumes, sharedCaches };
7009
+ }
6931
7010
  const baseSet = new Set(baseServices);
6932
7011
  const overlappingServices = profileServices.filter((service) => baseSet.has(service));
6933
7012
  if (overlappingServices.length > 0) {
@@ -6989,7 +7068,8 @@ function parseManagedRuntime(value, configPath) {
6989
7068
  baseServices,
6990
7069
  profileServices,
6991
7070
  ...prepareCommand ? { prepareCommand } : {},
6992
- ...hostPorts !== void 0 ? { hostPorts } : {}
7071
+ ...hostPorts !== void 0 ? { hostPorts } : {},
7072
+ ...freshBootstrap ? { freshBootstrap } : {}
6993
7073
  },
6994
7074
  processes
6995
7075
  };
@@ -7692,7 +7772,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
7692
7772
  const config = parseRepoConfigSource(raw, configPath);
7693
7773
  const requiredVersion = config.devrouter?.version;
7694
7774
  if (requiredVersion && !hasWarnedVersionMismatch) {
7695
- const cliVersion = true ? "0.6.1" : "0.0.0-dev";
7775
+ const cliVersion = true ? "0.6.2" : "0.0.0-dev";
7696
7776
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7697
7777
  hasWarnedVersionMismatch = true;
7698
7778
  process.stderr.write(
@@ -7844,8 +7924,8 @@ async function createDockerClient() {
7844
7924
  throw new Error(`Unsupported docker host from context: ${host}`);
7845
7925
  }
7846
7926
  async function listContainers(all = true) {
7847
- const docker = await createDockerClient();
7848
- return docker.listContainers({ all });
7927
+ const docker2 = await createDockerClient();
7928
+ return docker2.listContainers({ all });
7849
7929
  }
7850
7930
  async function findContainerByName(name) {
7851
7931
  const containers = await listContainers(true);
@@ -7856,13 +7936,13 @@ async function isContainerRunning(name) {
7856
7936
  return container?.State === "running";
7857
7937
  }
7858
7938
  async function ensureNetwork(name) {
7859
- const docker = await createDockerClient();
7939
+ const docker2 = await createDockerClient();
7860
7940
  try {
7861
- await docker.getNetwork(name).inspect();
7941
+ await docker2.getNetwork(name).inspect();
7862
7942
  return;
7863
7943
  } catch {
7864
7944
  }
7865
- await docker.createNetwork({
7945
+ await docker2.createNetwork({
7866
7946
  Name: name,
7867
7947
  Driver: "bridge",
7868
7948
  Attachable: true
@@ -10332,15 +10412,15 @@ function object2(value) {
10332
10412
  function qualifyNetworkProviderDefinition(value, provider) {
10333
10413
  const definition = object2(value);
10334
10414
  const agent = object2(definition.agent);
10335
- const docker = object2(agent.docker);
10336
- const env = object2(docker.env);
10415
+ const docker2 = object2(agent.docker);
10416
+ const env = object2(docker2.env);
10337
10417
  const options = object2(definition.options);
10338
10418
  const host = object2(options.DOCKER_HOST);
10339
10419
  const dockerPath = object2(options.DOCKER_PATH);
10340
10420
  const exec = object2(definition.exec);
10341
10421
  const command = provider === "devsy" ? '"${DEVSY}" internal sh -c "${COMMAND}"' : '"${DEVPOD}" helper sh -c "${COMMAND}"';
10342
10422
  const actualCommand = Array.isArray(exec.command) ? exec.command : [exec.command];
10343
- if (definition.name !== "docker" || ![true, "true"].includes(agent.local) || agent.driver !== void 0 && agent.driver !== "docker" && agent.driver !== "" || docker.path !== "${DOCKER_PATH}" || docker.builder !== "${DOCKER_BUILDER}" || ![false, "false"].includes(docker.install) || (provider === "devsy" ? docker.elevation !== "${DOCKER_ELEVATION}" : docker.elevation !== void 0 && docker.elevation !== "") || env.DOCKER_HOST !== "${DOCKER_HOST}" || Object.keys(env).some((key) => key !== "DOCKER_HOST") || host.global !== true || dockerPath.default !== "docker" || actualCommand.length !== 1 || typeof actualCommand[0] !== "string" || actualCommand[0].trim() !== command || Object.keys(exec).some((key) => key !== "command") || agent.path !== void 0 && agent.path !== "" || agent.downloadURL !== void 0 && agent.downloadURL !== "") {
10423
+ if (definition.name !== "docker" || ![true, "true"].includes(agent.local) || agent.driver !== void 0 && agent.driver !== "docker" && agent.driver !== "" || docker2.path !== "${DOCKER_PATH}" || docker2.builder !== "${DOCKER_BUILDER}" || ![false, "false"].includes(docker2.install) || (provider === "devsy" ? docker2.elevation !== "${DOCKER_ELEVATION}" : docker2.elevation !== void 0 && docker2.elevation !== "") || env.DOCKER_HOST !== "${DOCKER_HOST}" || Object.keys(env).some((key) => key !== "DOCKER_HOST") || host.global !== true || dockerPath.default !== "docker" || actualCommand.length !== 1 || typeof actualCommand[0] !== "string" || actualCommand[0].trim() !== command || Object.keys(exec).some((key) => key !== "command") || agent.path !== void 0 && agent.path !== "" || agent.downloadURL !== void 0 && agent.downloadURL !== "") {
10344
10424
  throw new Error("Docker provider definition is not qualified for network allocation.");
10345
10425
  }
10346
10426
  return (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
@@ -10663,16 +10743,20 @@ async function startDevsyWorkspace(options) {
10663
10743
  if (!devsyId && options.recreate) {
10664
10744
  throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
10665
10745
  }
10746
+ if (options.freshBootstrap && (!devsyId || options.recreate))
10747
+ throw new Error("Fresh bootstrap requires an explicit new provider ID without recreation.");
10666
10748
  if (options.prepareNetwork && !devsyId)
10667
10749
  throw new Error("Network allocation requires an exact provider ID before dispatch.");
10668
10750
  const network = options.prepareNetwork?.(devsyId);
10669
10751
  if (network)
10670
10752
  assertNetworkProviderBinding(network.binding, network.evidence, network.firstAllocation);
10753
+ const fresh = options.freshBootstrap?.prepare(devsyId, network?.binding);
10754
+ const binding = network?.binding ?? fresh?.binding;
10671
10755
  const args = ["workspace", "up", options.repoPath];
10672
10756
  if (devsyId) args.push("--id", devsyId);
10673
10757
  const devcontainerPath = network?.devcontainerPath ?? options.devcontainerPath;
10674
10758
  if (devcontainerPath) args.push("--devcontainer", devcontainerPath);
10675
- if (network) args.push(...networkProviderStartupArguments(network.binding));
10759
+ if (binding) args.push(...networkProviderStartupArguments(binding));
10676
10760
  args.push("--ide-launch", "skip");
10677
10761
  if (options.workspace) {
10678
10762
  args.push(
@@ -10684,7 +10768,7 @@ async function startDevsyWorkspace(options) {
10684
10768
  }
10685
10769
  args.push("--provider-option", "INACTIVITY_TIMEOUT=");
10686
10770
  if (options.recreate) args.push("--recreate");
10687
- const env = network ? networkProviderEnvironment(network.binding, process.env) : { ...process.env };
10771
+ const env = binding ? networkProviderEnvironment(binding, process.env) : { ...process.env };
10688
10772
  if (agent.binaryPath) env.DEVSY_AGENT_BINARY = agent.binaryPath;
10689
10773
  else delete env.DEVSY_AGENT_BINARY;
10690
10774
  if (options.workspace) {
@@ -10699,7 +10783,11 @@ async function startDevsyWorkspace(options) {
10699
10783
  delete env.DEVCONTAINER_COMPOSE_OVERLAY;
10700
10784
  }
10701
10785
  network?.beforeDispatch?.();
10702
- const result = await runDevsyUp(args, env, options.quiet ?? false);
10786
+ const result = await runDevsyUp(
10787
+ fresh ? [...args, "--skip-post-create", "--skip-post-start", "--skip-post-attach"] : args,
10788
+ env,
10789
+ options.quiet ?? false
10790
+ );
10703
10791
  if (result.status !== 0) {
10704
10792
  if (network) {
10705
10793
  network.retainUncertain();
@@ -10730,6 +10818,25 @@ async function startDevsyWorkspace(options) {
10730
10818
  `Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
10731
10819
  );
10732
10820
  }
10821
+ if (fresh) {
10822
+ const exactId = devsyId;
10823
+ await fresh.afterStart(async () => {
10824
+ const resumed = await runDevsyUp(args, env, options.quiet ?? false);
10825
+ if (resumed.status !== 0 || resumed.error)
10826
+ throw new Error(
10827
+ "Preserving source hooks failed after fresh initialization; data was retained."
10828
+ );
10829
+ const current = inspectDevsyWorkspaceOwnership(
10830
+ listDevsyWorkspaces(),
10831
+ exactId,
10832
+ options.repoPath
10833
+ );
10834
+ if (current.status !== "owned")
10835
+ throw new Error(
10836
+ "Fresh-bootstrap provider ownership changed during source-hook replay."
10837
+ );
10838
+ });
10839
+ }
10733
10840
  return devsyId;
10734
10841
  } catch (error) {
10735
10842
  network?.retainUncertain();
@@ -10750,9 +10857,9 @@ var ExecutionOutcomeError = class extends Error {
10750
10857
  };
10751
10858
 
10752
10859
  // src/core/reliability-lifecycle.ts
10753
- var import_node_crypto24 = require("crypto");
10754
- var import_node_path42 = __toESM(require("path"));
10755
- var import_node_util8 = require("util");
10860
+ var import_node_crypto25 = require("crypto");
10861
+ var import_node_path43 = __toESM(require("path"));
10862
+ var import_node_util9 = require("util");
10756
10863
 
10757
10864
  // src/core/capacity-policy.ts
10758
10865
  var import_node_fs27 = __toESM(require("fs"));
@@ -11409,15 +11516,467 @@ function reliabilityAttention(state) {
11409
11516
  return void 0;
11410
11517
  }
11411
11518
 
11412
- // src/core/managed-post-start.ts
11413
- var import_node_child_process20 = require("child_process");
11414
- var import_node_crypto18 = require("crypto");
11519
+ // src/core/managed-fresh-bootstrap.ts
11520
+ var import_node_child_process19 = require("child_process");
11521
+ var import_node_crypto17 = require("crypto");
11415
11522
  var import_node_fs29 = __toESM(require("fs"));
11523
+ var import_node_os6 = __toESM(require("os"));
11416
11524
  var import_node_path34 = __toESM(require("path"));
11525
+ var import_node_util7 = require("util");
11526
+ var import_yaml4 = __toESM(require("yaml"));
11527
+
11528
+ // src/core/lifecycle-progress.ts
11529
+ var ROLES = {
11530
+ validation: "lifecycle-worker",
11531
+ preparation: "repository-preparation",
11532
+ "fresh-bootstrap": "repository-fresh-bootstrap",
11533
+ provider: "provider-operation",
11534
+ "service-start": "service-reconciliation",
11535
+ "service-stop": "service-reconciliation",
11536
+ "process-start": "repository-process-adapter",
11537
+ "process-stop": "repository-process-adapter",
11538
+ "route-publication": "lifecycle-worker",
11539
+ "route-removal": "lifecycle-worker",
11540
+ readiness: "lifecycle-worker",
11541
+ rollback: "lifecycle-worker",
11542
+ stop: "lifecycle-stop"
11543
+ };
11544
+ function isLifecycleProgressPhase(value) {
11545
+ return typeof value === "string" && Object.hasOwn(ROLES, value);
11546
+ }
11547
+ var sender;
11548
+ function installLifecycleProgressSender(send) {
11549
+ let active = true;
11550
+ let pending = false;
11551
+ let latest;
11552
+ const report = (phase) => {
11553
+ if (!active || !isLifecycleProgressPhase(phase)) return;
11554
+ if (pending) {
11555
+ latest = phase;
11556
+ return;
11557
+ }
11558
+ pending = true;
11559
+ try {
11560
+ send({ lifecycleProgress: phase }, () => {
11561
+ pending = false;
11562
+ const next = latest;
11563
+ latest = void 0;
11564
+ if (active && next) report(next);
11565
+ });
11566
+ } catch {
11567
+ pending = false;
11568
+ latest = void 0;
11569
+ }
11570
+ };
11571
+ sender = report;
11572
+ return () => {
11573
+ active = false;
11574
+ latest = void 0;
11575
+ if (sender === report) sender = void 0;
11576
+ };
11577
+ }
11578
+ function reportLifecycleProgress(phase) {
11579
+ sender?.(phase);
11580
+ }
11581
+
11582
+ // src/core/managed-fresh-bootstrap.ts
11583
+ function hash(value) {
11584
+ return (0, import_node_crypto17.createHash)("sha256").update(value).digest("hex");
11585
+ }
11586
+ function object3(value) {
11587
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11588
+ }
11589
+ function qualifiedContext(source) {
11590
+ if (source.userEnvProbe !== void 0 && source.userEnvProbe !== "loginInteractiveShell" || object3(source.remoteEnv) && Object.hasOwn(source.remoteEnv, "PATH") || object3(source.secrets) && Object.keys(source.secrets).length > 0 || object3(source.features) && Object.keys(source.features).length > 0 || source.onCreateCommand || source.updateContentCommand)
11591
+ throw new Error(
11592
+ "Fresh bootstrap requires a qualified default-probe context without PATH overrides, secrets, features or earlier container hooks."
11593
+ );
11594
+ if (source.postStartCommand && !["postStartCommand", "postAttachCommand"].includes(String(source.waitFor)))
11595
+ throw new Error("Fresh bootstrap requires waitFor to include the declared post-start hook.");
11596
+ }
11597
+ function assertFreshBootstrapRequest(identity2, options) {
11598
+ if (!options.initializeNew) return;
11599
+ if (identity2.provider !== "devsy" || options.repair)
11600
+ throw new Error(
11601
+ "--initialize-new requires a fresh Devsy managed environment without --repair."
11602
+ );
11603
+ if (!loadRepoConfig(identity2.repoPath).managedRuntime?.devcontainer.freshBootstrap)
11604
+ throw new Error("--initialize-new requires managedRuntime.devcontainer.freshBootstrap.");
11605
+ if (readReliabilityOperation(identity2)?.freshBootstrap)
11606
+ throw new Error(
11607
+ "This environment already records an initialization attempt; initialization cannot be replayed. Use preserving recovery or a new disposable checkout."
11608
+ );
11609
+ if (readManagedRuntimeState(identity2.repoPath, identity2.workspace ?? void 0) || selectDevsyWorkspace(listDevsyWorkspaces(), identity2.repoPath))
11610
+ throw new Error("Fresh bootstrap refuses a retained runtime or provider registration.");
11611
+ qualifiedContext(readDevcontainerConfig(identity2.repoPath).source);
11612
+ }
11613
+ function docker(endpoint, args, cwd, environment = process.env) {
11614
+ const env = { ...environment };
11615
+ delete env.DOCKER_CONTEXT;
11616
+ delete env.DOCKER_HOST;
11617
+ const result = (0, import_node_child_process19.spawnSync)("docker", ["--host", endpoint, ...args], {
11618
+ cwd,
11619
+ env,
11620
+ encoding: "utf8",
11621
+ timeout: 15e3,
11622
+ maxBuffer: 4 * 1024 * 1024
11623
+ });
11624
+ if (result.error || result.status !== 0)
11625
+ throw new Error(
11626
+ "Fresh-bootstrap Docker evidence is unavailable; initialization was not retried."
11627
+ );
11628
+ return result.stdout;
11629
+ }
11630
+ function sourceIdentity(repoPath, plan) {
11631
+ return hash(
11632
+ JSON.stringify([
11633
+ hash(import_node_fs29.default.readFileSync(import_node_path34.default.join(repoPath, ".devrouter.yml"))),
11634
+ hash(import_node_fs29.default.readFileSync(plan.sourcePath)),
11635
+ plan.effectiveConfigSha256,
11636
+ ...plan.composeFiles.map((file) => [file, hash(import_node_fs29.default.readFileSync(file))])
11637
+ ])
11638
+ );
11639
+ }
11640
+ function render(endpoint, plan, project, workspace) {
11641
+ const args = [
11642
+ "compose",
11643
+ "--project-name",
11644
+ project,
11645
+ "--project-directory",
11646
+ plan.composeDirectory,
11647
+ ...plan.composeFiles.flatMap((file) => ["-f", file]),
11648
+ "config",
11649
+ "--format",
11650
+ "json",
11651
+ "--no-env-resolution"
11652
+ ];
11653
+ const env = managedComposeEnvironment(workspace);
11654
+ const value = JSON.parse(docker(endpoint, args, plan.composeDirectory, env));
11655
+ if (!object3(value) || !object3(value.services) || value.volumes !== void 0 && !object3(value.volumes))
11656
+ throw new Error("Invalid fresh-bootstrap Compose model.");
11657
+ return value;
11658
+ }
11659
+ function verifyFreshBootstrapMounts(repoPath, plan, model, declaration, workspace) {
11660
+ const selected = /* @__PURE__ */ new Set();
11661
+ for (const service of plan.desiredServices) {
11662
+ const entry = model.services[service];
11663
+ if (!entry)
11664
+ throw new Error("Fresh-bootstrap selected service is absent from the Compose model.");
11665
+ for (const mount of entry.volumes ?? []) {
11666
+ if (mount.type === "volume") {
11667
+ if (!mount.source || !model.volumes?.[mount.source])
11668
+ throw new Error("Fresh bootstrap requires exact named volume declarations.");
11669
+ selected.add(mount.source);
11670
+ } else if (mount.type === "bind" && !mount.read_only) {
11671
+ const source = import_node_fs29.default.realpathSync(mount.source ?? "");
11672
+ const relative = import_node_path34.default.relative(repoPath, source);
11673
+ const gitMount = workspace && service === plan.primaryService && source === import_node_fs29.default.realpathSync(workspace.gitCommonDir) && mount.target === workspace.gitCommonDir;
11674
+ if (!gitMount && (relative === ".." || relative.startsWith(`..${import_node_path34.default.sep}`) || import_node_path34.default.isAbsolute(relative)))
11675
+ throw new Error(
11676
+ "Fresh bootstrap refuses writable bind mounts outside the checkout and its exact Git common directory."
11677
+ );
11678
+ } else if (!["bind", "tmpfs"].includes(mount.type)) {
11679
+ throw new Error("Fresh bootstrap found an unsupported mount type.");
11680
+ }
11681
+ }
11682
+ }
11683
+ for (const key of [...declaration.dataVolumes, ...declaration.sharedCaches]) {
11684
+ if (!selected.has(key))
11685
+ throw new Error("Fresh-bootstrap volume binding is not mounted by the selected profile.");
11686
+ }
11687
+ for (const key of selected) {
11688
+ const volume = model.volumes?.[key];
11689
+ const cache = declaration.sharedCaches.includes(key);
11690
+ if (!volume || (cache ? volume.external !== true : volume.external === true) || volume.driver && volume.driver !== "local" || volume.driver_opts !== void 0 && (!object3(volume.driver_opts) || Object.keys(volume.driver_opts).length > 0))
11691
+ throw new Error(
11692
+ "Fresh bootstrap requires new local volumes or explicitly declared external shared caches."
11693
+ );
11694
+ }
11695
+ }
11696
+ function volumeSnapshot(endpoint) {
11697
+ const rows2 = docker(endpoint, ["volume", "ls", "--format", "{{json .Name}}"]).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
11698
+ if (rows2.length > 4096 || rows2.some(
11699
+ (name) => typeof name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(name)
11700
+ ) || new Set(rows2).size !== rows2.length)
11701
+ throw new Error("Fresh-bootstrap volume inventory is incomplete or exceeds its bound.");
11702
+ return rows2.map((name) => hash(name)).sort();
11703
+ }
11704
+ function assertNoCheckoutContainers(endpoint, repoPath) {
11705
+ const ids2 = docker(endpoint, ["ps", "--all", "--quiet", "--no-trunc"]).split(/\r?\n/).filter(Boolean);
11706
+ if (ids2.length > 4096 || ids2.some((id5) => !/^[a-f0-9]{64}$/.test(id5)) || new Set(ids2).size !== ids2.length)
11707
+ throw new Error("Fresh-bootstrap container inventory is incomplete or exceeds its bound.");
11708
+ for (let offset = 0; offset < ids2.length; offset += 64) {
11709
+ const batch = ids2.slice(offset, offset + 64);
11710
+ const rows2 = docker(endpoint, ["inspect", "--format", SAFE_INSPECT_TEMPLATE, ...batch]).split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
11711
+ if (rows2.length !== batch.length || new Set(rows2.map((row) => row.id)).size !== batch.length)
11712
+ throw new Error("Fresh-bootstrap container absence evidence changed.");
11713
+ for (const row of rows2) {
11714
+ if (!batch.includes(row.id) || !object3(row.labels) || !Array.isArray(row.mounts))
11715
+ throw new Error("Fresh-bootstrap container absence evidence is invalid.");
11716
+ if (sameWorkspacePath(
11717
+ row.labels["com.docker.compose.project.working_dir"] ?? "",
11718
+ import_node_path34.default.join(repoPath, ".devcontainer")
11719
+ ) || row.mounts.some(
11720
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source ?? "", repoPath)
11721
+ ))
11722
+ throw new Error("Fresh bootstrap refuses surviving containers for this checkout.");
11723
+ }
11724
+ }
11725
+ }
11726
+ function verifyFreshVolume(volume, key, project, prior, sharedCache) {
11727
+ if (!volume || typeof volume.Name !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(volume.Name) || volume.Driver !== "local" || volume.Scope !== "local" || typeof volume.Mountpoint !== "string" || typeof volume.CreatedAt !== "string" || !import_node_path34.default.isAbsolute(volume.Mountpoint) || !Number.isFinite(Date.parse(volume.CreatedAt)) || volume.Options !== void 0 && volume.Options !== null && !object3(volume.Options) || object3(volume.Options) && Object.keys(volume.Options).length > 0)
11728
+ throw new Error("Fresh-bootstrap volume identity is not qualified local storage.");
11729
+ if (!sharedCache && (prior.includes(hash(volume.Name)) || volume.Labels?.["com.docker.compose.project"] !== project || volume.Labels?.["com.docker.compose.volume"] !== key))
11730
+ throw new Error("Fresh bootstrap refuses retained or foreign data volumes.");
11731
+ }
11732
+ function proveVolumes(input) {
11733
+ const containers = inspectManagedStopContainers(input.project, input.endpoint);
11734
+ const result = [];
11735
+ const names = /* @__PURE__ */ new Set();
11736
+ const seen = /* @__PURE__ */ new Set();
11737
+ for (const service of input.plan.desiredServices) {
11738
+ const container = containers.find((c) => c.labels["com.docker.compose.service"] === service);
11739
+ if (!container) throw new Error("Fresh-bootstrap volume owner is absent.");
11740
+ for (const mount of input.model.services[service].volumes ?? []) {
11741
+ if (mount.type !== "volume" || !mount.source) continue;
11742
+ const key = mount.source;
11743
+ const name = input.model.volumes?.[key]?.name;
11744
+ if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,255}$/.test(name))
11745
+ throw new Error("Fresh-bootstrap volume name did not resolve exactly.");
11746
+ const inspected = JSON.parse(docker(input.endpoint, ["volume", "inspect", name]));
11747
+ if (!Array.isArray(inspected) || inspected.length !== 1 || !object3(inspected[0]) || inspected[0].Name !== name)
11748
+ throw new Error("Fresh-bootstrap volume inspection is ambiguous.");
11749
+ const volume = inspected[0];
11750
+ verifyFreshVolume(
11751
+ volume,
11752
+ key,
11753
+ input.project,
11754
+ input.prior,
11755
+ input.declaration.sharedCaches.includes(key)
11756
+ );
11757
+ const attached = container.mounts.filter(
11758
+ (m) => m.Type === "volume" && m.Destination === mount.target
11759
+ );
11760
+ if (attached.length !== 1 || attached[0].Source !== volume.Mountpoint)
11761
+ throw new Error("Fresh-bootstrap data volume is not attached to its exact owner.");
11762
+ if (seen.has(key)) continue;
11763
+ if (names.has(name)) throw new Error("Fresh-bootstrap volume bindings alias each other.");
11764
+ seen.add(key);
11765
+ names.add(name);
11766
+ if (input.declaration.dataVolumes.includes(key))
11767
+ result.push({ key, name, createdAt: volume.CreatedAt, mountpoint: volume.Mountpoint });
11768
+ }
11769
+ }
11770
+ if (result.length !== input.declaration.dataVolumes.length)
11771
+ throw new Error("Fresh-bootstrap data-volume proof is incomplete.");
11772
+ return result.sort((a, b) => a.key.localeCompare(b.key));
11773
+ }
11774
+ function createFreshBootstrap(options) {
11775
+ const repoPath = options.identity.repoPath;
11776
+ return {
11777
+ prepare(providerId, network) {
11778
+ assertFreshBootstrapRequest(options.identity, { initializeNew: true });
11779
+ if (requireReadyDevsyAgent().version !== "1.19.0")
11780
+ throw new Error(
11781
+ "Fresh bootstrap is qualified for Devsy 1.19.0; ordinary startup is unchanged."
11782
+ );
11783
+ const declaration = loadRepoConfig(repoPath).managedRuntime?.devcontainer.freshBootstrap;
11784
+ if (!declaration) throw new Error("Fresh-bootstrap declaration disappeared.");
11785
+ const plan = options.plan();
11786
+ for (const file of plan.composeFiles) {
11787
+ const source = import_yaml4.default.parse(import_node_fs29.default.readFileSync(file, "utf8"));
11788
+ if (source?.name !== void 0)
11789
+ throw new Error("Fresh bootstrap refuses an explicit shared Compose project name.");
11790
+ for (const [key, value] of Object.entries(source?.volumes ?? {})) {
11791
+ if (!declaration.sharedCaches.includes(key) && object3(value) && value.name !== void 0)
11792
+ throw new Error(
11793
+ "Fresh bootstrap refuses fixed names for non-cache volumes before provider startup."
11794
+ );
11795
+ }
11796
+ }
11797
+ const envFile = import_node_path34.default.join(plan.composeDirectory, ".env");
11798
+ if (process.env.COMPOSE_PROJECT_NAME !== void 0 || import_node_fs29.default.existsSync(envFile) && /^\s*(?:export\s+)?COMPOSE_PROJECT_NAME\s*=/m.test(import_node_fs29.default.readFileSync(envFile, "utf8")))
11799
+ throw new Error("Fresh bootstrap requires the provider's isolated project namespace.");
11800
+ const endpoint = network?.endpoint ?? resolveManagedStopEndpoint();
11801
+ if (!supportsManagedStopBaseline(endpoint))
11802
+ throw new Error("Fresh bootstrap requires a local Docker endpoint.");
11803
+ const evidence = inspectNetworkProviderBinding({
11804
+ provider: "devsy",
11805
+ providerId,
11806
+ repoPath,
11807
+ endpoint,
11808
+ ...network ? { providerContext: network.providerContext } : {}
11809
+ });
11810
+ const binding = network ?? {
11811
+ provider: "devsy",
11812
+ providerId,
11813
+ endpoint,
11814
+ daemonId: evidence.daemonId,
11815
+ definitionSha256: evidence.definitionSha256,
11816
+ providerContext: evidence.providerContext
11817
+ };
11818
+ assertNetworkProviderBinding(binding, evidence, true);
11819
+ const providerDirectory = import_node_path34.default.join(
11820
+ process.env.DEVSY_HOME || import_node_path34.default.join(import_node_os6.default.homedir(), ".devsy"),
11821
+ "contexts",
11822
+ binding.providerContext,
11823
+ "workspaces",
11824
+ providerId
11825
+ );
11826
+ try {
11827
+ import_node_fs29.default.lstatSync(providerDirectory);
11828
+ throw new Error(
11829
+ "Fresh bootstrap refuses retained provider files, even without a registration."
11830
+ );
11831
+ } catch (error) {
11832
+ if (error.code !== "ENOENT") throw error;
11833
+ }
11834
+ assertNoCheckoutContainers(endpoint, repoPath);
11835
+ verifyFreshBootstrapMounts(
11836
+ repoPath,
11837
+ plan,
11838
+ render(endpoint, plan, "fresh-bootstrap-preflight", options.workspace),
11839
+ declaration,
11840
+ options.workspace
11841
+ );
11842
+ const sourceSha256 = sourceIdentity(repoPath, plan);
11843
+ const prior = volumeSnapshot(endpoint);
11844
+ claimActiveFreshBootstrap({
11845
+ sourceSha256,
11846
+ providerId,
11847
+ providerContext: binding.providerContext,
11848
+ definitionSha256: binding.definitionSha256,
11849
+ endpoint,
11850
+ daemonId: binding.daemonId,
11851
+ previousVolumeNameDigests: prior
11852
+ });
11853
+ return {
11854
+ binding,
11855
+ async afterStart(resumeSourceHooks) {
11856
+ const proof = captureDevsyExecProof(repoPath);
11857
+ if (proof.id !== providerId || proof.endpoint !== endpoint || proof.daemon !== binding.daemonId || proof.context !== binding.providerContext)
11858
+ throw new Error("Fresh-bootstrap provider or daemon changed during provisioning.");
11859
+ const primary = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).find(
11860
+ (c) => c.id === proof.containerId
11861
+ );
11862
+ const project = primary?.labels["com.docker.compose.project"];
11863
+ if (!project) throw new Error("Fresh-bootstrap primary project is unavailable.");
11864
+ const state = {
11865
+ version: 1,
11866
+ repoPath,
11867
+ ...options.identity.workspace ? { workspace: options.identity.workspace } : {},
11868
+ devpodId: providerId,
11869
+ composeProject: project,
11870
+ profile: options.profile,
11871
+ variant: options.variant,
11872
+ ...options.hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256: options.hostPortDeclarationsSha256 } : {},
11873
+ desired: {
11874
+ apps: [...options.apps].sort(),
11875
+ services: [...plan.desiredProfileServices].sort(),
11876
+ processes: [...options.processes].sort()
11877
+ },
11878
+ sourceConfigSha256: plan.sourceConfigSha256,
11879
+ effectiveConfigSha256: plan.effectiveConfigSha256,
11880
+ status: "degraded",
11881
+ transitionPhase: "fresh-bootstrap",
11882
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
11883
+ };
11884
+ state.stopBaseline = captureManagedStopBaseline(state, plan, proof.containerId, endpoint);
11885
+ claimLifecycleEffect();
11886
+ writeManagedRuntimeState(state);
11887
+ options.onCaptured(state, { id: proof.containerId, workspacePath: proof.workspacePath });
11888
+ const metadata = JSON.parse(
11889
+ docker(endpoint, [
11890
+ "inspect",
11891
+ "--format",
11892
+ '{{json (index .Config.Labels "devcontainer.metadata")}}',
11893
+ proof.containerId
11894
+ ])
11895
+ );
11896
+ if (typeof metadata === "string") {
11897
+ const entries = JSON.parse(metadata);
11898
+ if (!Array.isArray(entries) || entries.some((entry) => !object3(entry)))
11899
+ throw new Error("Fresh-bootstrap image metadata is invalid.");
11900
+ for (const entry of entries) qualifiedContext(entry);
11901
+ } else if (metadata !== null)
11902
+ throw new Error("Fresh-bootstrap image metadata is unavailable.");
11903
+ const model = render(endpoint, plan, project, options.workspace);
11904
+ verifyFreshBootstrapMounts(repoPath, plan, model, declaration, options.workspace);
11905
+ const dataVolumes = proveVolumes({ endpoint, model, declaration, plan, project, prior });
11906
+ const resources = { primaryId: proof.containerId, project, dataVolumes };
11907
+ const revalidate = () => {
11908
+ if (sourceIdentity(repoPath, options.plan()) !== sourceSha256 || inspectManagedStopDaemon(endpoint) !== binding.daemonId)
11909
+ throw new Error("Fresh-bootstrap source or daemon changed.");
11910
+ assertNetworkProviderBinding(
11911
+ binding,
11912
+ inspectNetworkProviderBinding({
11913
+ provider: "devsy",
11914
+ providerId,
11915
+ repoPath,
11916
+ endpoint,
11917
+ providerContext: binding.providerContext
11918
+ }),
11919
+ false
11920
+ );
11921
+ revalidateDevsyExecProof(repoPath, proof);
11922
+ proveRetainedManagedStop(state);
11923
+ if (!(0, import_node_util7.isDeepStrictEqual)(
11924
+ proveVolumes({ endpoint, model, declaration, plan, project, prior }),
11925
+ dataVolumes
11926
+ ))
11927
+ throw new Error("Fresh-bootstrap volume identity changed.");
11928
+ };
11929
+ revalidate();
11930
+ advanceActiveFreshBootstrap("provisioned", resources);
11931
+ reportLifecycleProgress("fresh-bootstrap");
11932
+ advanceActiveFreshBootstrap("initializing");
11933
+ const outcome = await new Promise((resolve) => {
11934
+ revalidate();
11935
+ claimLifecycleEffect();
11936
+ const child = (0, import_node_child_process19.spawn)(
11937
+ "devsy",
11938
+ [
11939
+ "workspace",
11940
+ "exec",
11941
+ "--result-format",
11942
+ "plain",
11943
+ "--context",
11944
+ binding.providerContext,
11945
+ providerId,
11946
+ "--",
11947
+ ...declaration.command
11948
+ ],
11949
+ { stdio: "inherit", env: networkProviderEnvironment(binding, process.env) }
11950
+ );
11951
+ child.once("error", () => resolve({ code: null, error: true }));
11952
+ child.once("close", (code) => resolve({ code }));
11953
+ });
11954
+ if (outcome.error || outcome.code !== 0) {
11955
+ advanceActiveFreshBootstrap("failed");
11956
+ throw new Error(
11957
+ "Fresh initialization failed or completion is unknown; partial data and its attempt fence were retained. Initialization will not replay."
11958
+ );
11959
+ }
11960
+ advanceActiveFreshBootstrap("initialized");
11961
+ revalidate();
11962
+ await resumeSourceHooks();
11963
+ revalidate();
11964
+ advanceActiveFreshBootstrap("ready");
11965
+ }
11966
+ };
11967
+ }
11968
+ };
11969
+ }
11970
+
11971
+ // src/core/managed-post-start.ts
11972
+ var import_node_child_process21 = require("child_process");
11973
+ var import_node_crypto19 = require("crypto");
11974
+ var import_node_fs30 = __toESM(require("fs"));
11975
+ var import_node_path35 = __toESM(require("path"));
11417
11976
 
11418
11977
  // src/core/managed-host-ports.ts
11419
- var import_node_child_process19 = require("child_process");
11420
- var import_node_crypto17 = require("crypto");
11978
+ var import_node_child_process20 = require("child_process");
11979
+ var import_node_crypto18 = require("crypto");
11421
11980
  var PORT_LOOKUP_TIMEOUT_MS = 1e4;
11422
11981
  var PORT_LOOKUP_MAX_BUFFER = 64 * 1024;
11423
11982
  var LOOPBACK_BINDING = /^127\.0\.0\.1:(\d+)$/;
@@ -11425,7 +11984,7 @@ function unavailableEntry(declaration, reason2) {
11425
11984
  return { ...declaration, status: "unavailable", reason: reason2 };
11426
11985
  }
11427
11986
  function livePublishedBindings(containerId, target) {
11428
- const result = (0, import_node_child_process19.spawnSync)("docker", ["port", containerId, `${target}/tcp`], {
11987
+ const result = (0, import_node_child_process20.spawnSync)("docker", ["port", containerId, `${target}/tcp`], {
11429
11988
  encoding: "utf-8",
11430
11989
  timeout: PORT_LOOKUP_TIMEOUT_MS,
11431
11990
  maxBuffer: PORT_LOOKUP_MAX_BUFFER,
@@ -11565,7 +12124,7 @@ function managedHostPortDeclarationsIdentity(declarations) {
11565
12124
  const keys2 = Object.keys(declarations).sort();
11566
12125
  if (keys2.length === 0) return void 0;
11567
12126
  const canonical = keys2.map((key) => [key, declarations[key].service, declarations[key].target]);
11568
- return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
12127
+ return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
11569
12128
  }
11570
12129
  function managedHostPortVariables(report) {
11571
12130
  return Object.fromEntries(
@@ -11582,7 +12141,7 @@ function managedHostPortIdentity(report) {
11582
12141
  const entry = report[key];
11583
12142
  return [key, entry.status === "available" ? entry.endpoint?.port ?? null : entry.status];
11584
12143
  });
11585
- return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
12144
+ return (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify(canonical)).digest("hex");
11586
12145
  }
11587
12146
  function assertManagedHostPortsAvailable(report) {
11588
12147
  const unavailable = Object.entries(report).filter(([, entry]) => entry.status === "unavailable").map(([key]) => key).sort();
@@ -11605,7 +12164,7 @@ function renderDeliveryScript(targetPath) {
11605
12164
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
11606
12165
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
11607
12166
  }
11608
- const temporaryPrefix = import_node_path34.default.posix.basename(targetPath);
12167
+ const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
11609
12168
  return `set -eu
11610
12169
  umask 077
11611
12170
  runtime_root=/tmp/devrouter
@@ -11629,7 +12188,7 @@ function commandFailure2(result) {
11629
12188
  return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
11630
12189
  }
11631
12190
  function deliverRuntimeFile(containerId, targetPath, contents, label) {
11632
- const result = (0, import_node_child_process20.spawnSync)(
12191
+ const result = (0, import_node_child_process21.spawnSync)(
11633
12192
  "docker",
11634
12193
  ["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
11635
12194
  { input: contents, encoding: "utf-8", ...networkDockerOptions() }
@@ -11640,32 +12199,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
11640
12199
  }
11641
12200
  }
11642
12201
  function readRegularFileBytes(filePath) {
11643
- if (!import_node_fs29.default.existsSync(filePath) || !import_node_fs29.default.lstatSync(filePath).isFile()) return void 0;
11644
- return import_node_fs29.default.readFileSync(filePath);
12202
+ if (!import_node_fs30.default.existsSync(filePath) || !import_node_fs30.default.lstatSync(filePath).isFile()) return void 0;
12203
+ return import_node_fs30.default.readFileSync(filePath);
11645
12204
  }
11646
12205
  function readRegularFile(filePath) {
11647
12206
  return readRegularFileBytes(filePath)?.toString("utf-8");
11648
12207
  }
11649
12208
  function adapterFingerprint(adapter) {
11650
- return (0, import_node_crypto18.createHash)("sha256").update(adapter).digest("hex");
12209
+ return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
11651
12210
  }
11652
12211
  function resolveProcessHelperPath() {
11653
12212
  const candidates = [
11654
- import_node_path34.default.resolve(__dirname, "..", "bin", "devrouter-process"),
11655
- import_node_path34.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
12213
+ import_node_path35.default.resolve(__dirname, "..", "bin", "devrouter-process"),
12214
+ import_node_path35.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
11656
12215
  ];
11657
- const helperPath = candidates.find((candidate) => import_node_fs29.default.existsSync(candidate));
12216
+ const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
11658
12217
  if (!helperPath) {
11659
12218
  throw new Error("Could not locate the packaged devrouter-process helper.");
11660
12219
  }
11661
12220
  return helperPath;
11662
12221
  }
11663
12222
  function resolveManagedPostStartPlan(repoPath) {
11664
- const adapterPath = import_node_path34.default.join(repoPath, MANAGED_ADAPTER_PATH);
12223
+ const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
11665
12224
  const adapterBytes = readRegularFileBytes(adapterPath);
11666
12225
  const adapterText = adapterBytes?.toString("utf-8");
11667
- const dockerfilePath = import_node_path34.default.join(repoPath, ".devcontainer", "Dockerfile");
11668
- const devcontainerPath = import_node_path34.default.join(repoPath, ".devcontainer", "devcontainer.json");
12226
+ const dockerfilePath = import_node_path35.default.join(repoPath, ".devcontainer", "Dockerfile");
12227
+ const devcontainerPath = import_node_path35.default.join(repoPath, ".devcontainer", "devcontainer.json");
11669
12228
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
11670
12229
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
11671
12230
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -11716,7 +12275,7 @@ function runManagedPostStart(options) {
11716
12275
  assertManagedHostPortsAvailable(hostPorts);
11717
12276
  const portEnvironment = managedHostPortVariables(hostPorts);
11718
12277
  const portIdentity = managedHostPortIdentity(hostPorts) ?? "";
11719
- const helper = import_node_fs29.default.readFileSync(resolveProcessHelperPath());
12278
+ const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
11720
12279
  deliverRuntimeFile(
11721
12280
  options.container.id,
11722
12281
  RUNTIME_HELPER_PATH,
@@ -11730,7 +12289,7 @@ function runManagedPostStart(options) {
11730
12289
  options.plan.adapterContents,
11731
12290
  "the managed post-start adapter"
11732
12291
  );
11733
- const started2 = (0, import_node_child_process20.spawnSync)(
12292
+ const started2 = (0, import_node_child_process21.spawnSync)(
11734
12293
  "docker",
11735
12294
  [
11736
12295
  "exec",
@@ -11776,7 +12335,7 @@ function assertSafeProcessName(name) {
11776
12335
  }
11777
12336
  function runManagedProcessAction(options) {
11778
12337
  assertSafeProcessName(options.name);
11779
- const result = (0, import_node_child_process20.spawnSync)(
12338
+ const result = (0, import_node_child_process21.spawnSync)(
11780
12339
  "docker",
11781
12340
  [
11782
12341
  "exec",
@@ -12582,103 +13141,48 @@ function stepReliability(state, event, nowMs) {
12582
13141
  }
12583
13142
 
12584
13143
  // src/core/reliability-worker.ts
12585
- var import_node_child_process21 = require("child_process");
13144
+ var import_node_child_process22 = require("child_process");
13145
+ var import_node_crypto21 = require("crypto");
13146
+ var import_node_fs32 = __toESM(require("fs"));
13147
+ var import_node_path37 = __toESM(require("path"));
13148
+
13149
+ // src/core/lifecycle-diagnostics.ts
12586
13150
  var import_node_crypto20 = require("crypto");
12587
13151
  var import_node_fs31 = __toESM(require("fs"));
12588
13152
  var import_node_path36 = __toESM(require("path"));
12589
-
12590
- // src/core/lifecycle-diagnostics.ts
12591
- var import_node_crypto19 = require("crypto");
12592
- var import_node_fs30 = __toESM(require("fs"));
12593
- var import_node_path35 = __toESM(require("path"));
12594
- var import_node_util7 = require("util");
12595
-
12596
- // src/core/lifecycle-progress.ts
12597
- var ROLES = {
12598
- validation: "lifecycle-worker",
12599
- preparation: "repository-preparation",
12600
- provider: "provider-operation",
12601
- "service-start": "service-reconciliation",
12602
- "service-stop": "service-reconciliation",
12603
- "process-start": "repository-process-adapter",
12604
- "process-stop": "repository-process-adapter",
12605
- "route-publication": "lifecycle-worker",
12606
- "route-removal": "lifecycle-worker",
12607
- readiness: "lifecycle-worker",
12608
- rollback: "lifecycle-worker",
12609
- stop: "lifecycle-stop"
12610
- };
12611
- function isLifecycleProgressPhase(value) {
12612
- return typeof value === "string" && Object.hasOwn(ROLES, value);
12613
- }
12614
- var sender;
12615
- function installLifecycleProgressSender(send) {
12616
- let active = true;
12617
- let pending = false;
12618
- let latest;
12619
- const report = (phase) => {
12620
- if (!active || !isLifecycleProgressPhase(phase)) return;
12621
- if (pending) {
12622
- latest = phase;
12623
- return;
12624
- }
12625
- pending = true;
12626
- try {
12627
- send({ lifecycleProgress: phase }, () => {
12628
- pending = false;
12629
- const next = latest;
12630
- latest = void 0;
12631
- if (active && next) report(next);
12632
- });
12633
- } catch {
12634
- pending = false;
12635
- latest = void 0;
12636
- }
12637
- };
12638
- sender = report;
12639
- return () => {
12640
- active = false;
12641
- latest = void 0;
12642
- if (sender === report) sender = void 0;
12643
- };
12644
- }
12645
- function reportLifecycleProgress(phase) {
12646
- sender?.(phase);
12647
- }
12648
-
12649
- // src/core/lifecycle-diagnostics.ts
13153
+ var import_node_util8 = require("util");
12650
13154
  var MAX_TAIL_BYTES = 32768;
12651
13155
  var MAX_RECEIPT_BYTES = 65536;
12652
13156
  function privateDirectory(directory) {
12653
- const stat = import_node_fs30.default.lstatSync(directory);
13157
+ const stat = import_node_fs31.default.lstatSync(directory);
12654
13158
  return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
12655
13159
  }
12656
13160
  function lifecycleDiagnosticsPath(identity2) {
12657
- return import_node_path35.default.join(
13161
+ return import_node_path36.default.join(
12658
13162
  DEVROUTER_HOME,
12659
13163
  "lifecycle-diagnostics",
12660
- `${(0, import_node_crypto19.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
13164
+ `${(0, import_node_crypto20.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
12661
13165
  );
12662
13166
  }
12663
13167
  function readReceipt(identity2) {
12664
13168
  let descriptor;
12665
13169
  try {
12666
13170
  const file = lifecycleDiagnosticsPath(identity2);
12667
- if (!privateDirectory(import_node_path35.default.dirname(file))) return void 0;
12668
- descriptor = import_node_fs30.default.openSync(
13171
+ if (!privateDirectory(import_node_path36.default.dirname(file))) return void 0;
13172
+ descriptor = import_node_fs31.default.openSync(
12669
13173
  file,
12670
- import_node_fs30.default.constants.O_RDONLY | import_node_fs30.default.constants.O_NOFOLLOW | import_node_fs30.default.constants.O_NONBLOCK
13174
+ import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NOFOLLOW | import_node_fs31.default.constants.O_NONBLOCK
12671
13175
  );
12672
- const stat = import_node_fs30.default.fstatSync(descriptor);
13176
+ const stat = import_node_fs31.default.fstatSync(descriptor);
12673
13177
  if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
12674
13178
  return void 0;
12675
13179
  const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
12676
- const size = import_node_fs30.default.readSync(descriptor, buffer, 0, buffer.length, 0);
13180
+ const size = import_node_fs31.default.readSync(descriptor, buffer, 0, buffer.length, 0);
12677
13181
  if (size > MAX_RECEIPT_BYTES) return void 0;
12678
13182
  const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
12679
13183
  const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
12680
13184
  const optionalTime = (value) => value === null || timestamp(value);
12681
- if (receipt.version !== 1 || !(0, import_node_util7.isDeepStrictEqual)(receipt.identity, identity2) || typeof receipt.operationId !== "string" || typeof receipt.workerId !== "string" || !timestamp(receipt.startedAtMs) || !timestamp(receipt.updatedAtMs) || receipt.updatedAtMs < receipt.startedAtMs || !(receipt.phase === null || isLifecycleProgressPhase(receipt.phase)) || !optionalTime(receipt.phaseAtMs) || !optionalTime(receipt.lastOutputAtMs) || !optionalTime(receipt.lastProgressAtMs) || !(receipt.buildPackage === null || typeof receipt.buildPackage === "string" && receipt.buildPackage.length <= 128 && /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9_.-]+$/.test(receipt.buildPackage)) || typeof receipt.cancellationRequested !== "boolean" || typeof receipt.closed !== "boolean" || !Array.isArray(receipt.output) || receipt.output.length > 64)
13185
+ if (receipt.version !== 1 || !(0, import_node_util8.isDeepStrictEqual)(receipt.identity, identity2) || typeof receipt.operationId !== "string" || typeof receipt.workerId !== "string" || !timestamp(receipt.startedAtMs) || !timestamp(receipt.updatedAtMs) || receipt.updatedAtMs < receipt.startedAtMs || !(receipt.phase === null || isLifecycleProgressPhase(receipt.phase)) || !optionalTime(receipt.phaseAtMs) || !optionalTime(receipt.lastOutputAtMs) || !optionalTime(receipt.lastProgressAtMs) || !(receipt.buildPackage === null || typeof receipt.buildPackage === "string" && receipt.buildPackage.length <= 128 && /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9_.-]+$/.test(receipt.buildPackage)) || typeof receipt.cancellationRequested !== "boolean" || typeof receipt.closed !== "boolean" || !Array.isArray(receipt.output) || receipt.output.length > 64)
12682
13186
  return void 0;
12683
13187
  let bytes2 = 0;
12684
13188
  for (const chunk of receipt.output) {
@@ -12690,7 +13194,7 @@ function readReceipt(identity2) {
12690
13194
  } catch {
12691
13195
  return void 0;
12692
13196
  } finally {
12693
- if (descriptor !== void 0) import_node_fs30.default.closeSync(descriptor);
13197
+ if (descriptor !== void 0) import_node_fs31.default.closeSync(descriptor);
12694
13198
  }
12695
13199
  }
12696
13200
  function readLifecycleDiagnostics(identity2, now = Date.now()) {
@@ -12698,7 +13202,7 @@ function readLifecycleDiagnostics(identity2, now = Date.now()) {
12698
13202
  const before = readReliabilityOperation(identity2);
12699
13203
  const receipt = readReceipt(identity2);
12700
13204
  const after = readReliabilityOperation(identity2);
12701
- if (!before || !after || !receipt || before.revision !== after.revision || !(0, import_node_util7.isDeepStrictEqual)(before, after) || !(0, import_node_util7.isDeepStrictEqual)(receipt.fence, reliabilityFence(after.state)) || after.state.operation?.id !== receipt.operationId || after.state.operation.kind !== "ensure" || after.worker !== null && after.worker.id !== receipt.workerId || !receipt.closed && !after.worker || now < receipt.updatedAtMs || [receipt.phaseAtMs, receipt.lastOutputAtMs, receipt.lastProgressAtMs].some(
13205
+ if (!before || !after || !receipt || before.revision !== after.revision || !(0, import_node_util8.isDeepStrictEqual)(before, after) || !(0, import_node_util8.isDeepStrictEqual)(receipt.fence, reliabilityFence(after.state)) || after.state.operation?.id !== receipt.operationId || after.state.operation.kind !== "ensure" || after.worker !== null && after.worker.id !== receipt.workerId || !receipt.closed && !after.worker || now < receipt.updatedAtMs || [receipt.phaseAtMs, receipt.lastOutputAtMs, receipt.lastProgressAtMs].some(
12702
13206
  (time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
12703
13207
  ))
12704
13208
  return void 0;
@@ -12740,11 +13244,11 @@ function workerGroupAbsent(pid) {
12740
13244
  if (process.platform !== "linux") return false;
12741
13245
  let observedZombie = false;
12742
13246
  try {
12743
- for (const entry of import_node_fs31.default.readdirSync("/proc")) {
13247
+ for (const entry of import_node_fs32.default.readdirSync("/proc")) {
12744
13248
  if (!/^\d+$/.test(entry)) continue;
12745
13249
  let stat;
12746
13250
  try {
12747
- stat = import_node_fs31.default.readFileSync(`/proc/${entry}/stat`, "utf8");
13251
+ stat = import_node_fs32.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12748
13252
  } catch (error) {
12749
13253
  if (error.code === "ENOENT") continue;
12750
13254
  return false;
@@ -12764,20 +13268,20 @@ function workerGroupAbsent(pid) {
12764
13268
  }
12765
13269
 
12766
13270
  // src/core/workspace-ensure.ts
12767
- var import_node_child_process31 = require("child_process");
12768
- var import_node_fs36 = __toESM(require("fs"));
12769
- var import_node_path41 = __toESM(require("path"));
13271
+ var import_node_child_process32 = require("child_process");
13272
+ var import_node_fs37 = __toESM(require("fs"));
13273
+ var import_node_path42 = __toESM(require("path"));
12770
13274
 
12771
13275
  // src/core/devpod-mutation.ts
12772
- var import_node_child_process22 = require("child_process");
12773
- var import_node_fs32 = __toESM(require("fs"));
12774
- var import_node_path37 = __toESM(require("path"));
12775
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13276
+ var import_node_child_process23 = require("child_process");
13277
+ var import_node_fs33 = __toESM(require("fs"));
13278
+ var import_node_path38 = __toESM(require("path"));
13279
+ var DEVPOD_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
12776
13280
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
12777
13281
  var DevpodStartPostconditionError = class extends Error {
12778
13282
  };
12779
13283
  function withMutationLock2(activity, target, operation) {
12780
- import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13284
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12781
13285
  return withFileLockSync(
12782
13286
  DEVPOD_MUTATION_LOCK_FILE,
12783
13287
  {
@@ -12797,7 +13301,7 @@ function runDevpodAction(action, devpodId, force = false, repoPath) {
12797
13301
  const args = action === "delete" ? [action, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action, devpodId];
12798
13302
  claimLifecycleEffect();
12799
13303
  const binding = repoPath ? networkProviderEffectOptions("devpod", devpodId, repoPath) : { args: [] };
12800
- const result = (0, import_node_child_process22.spawnSync)("devpod", [...args, ...binding.args], {
13304
+ const result = (0, import_node_child_process23.spawnSync)("devpod", [...args, ...binding.args], {
12801
13305
  encoding: "utf-8",
12802
13306
  ...binding.env ? { env: binding.env } : {}
12803
13307
  });
@@ -12877,7 +13381,8 @@ async function startDevpodWorkspace(options) {
12877
13381
  recreate: options.recreate,
12878
13382
  quiet: options.quiet,
12879
13383
  workspace: options.workspace,
12880
- prepareNetwork: options.prepareNetwork
13384
+ prepareNetwork: options.prepareNetwork,
13385
+ ...options.freshBootstrap ? { freshBootstrap: options.freshBootstrap } : {}
12881
13386
  });
12882
13387
  resetWorkspaceRuntimeCaches();
12883
13388
  return result;
@@ -12889,6 +13394,8 @@ async function startDevpodWorkspace(options) {
12889
13394
  throw error;
12890
13395
  }
12891
13396
  }
13397
+ if (options.freshBootstrap)
13398
+ throw new Error("Fresh bootstrap is qualified only for the Devsy Docker provider.");
12892
13399
  const activity = options.recreate ? "DevPod recreate" : "DevPod start";
12893
13400
  return withMutationLock2(activity, options.repoPath, () => {
12894
13401
  const workspaces = listDevpodWorkspaces(options.repoPath);
@@ -12938,7 +13445,7 @@ async function startDevpodWorkspace(options) {
12938
13445
  }
12939
13446
  claimLifecycleEffect();
12940
13447
  network?.beforeDispatch?.();
12941
- const result = (0, import_node_child_process22.spawnSync)("devpod", args, {
13448
+ const result = (0, import_node_child_process23.spawnSync)("devpod", args, {
12942
13449
  stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
12943
13450
  env
12944
13451
  });
@@ -12978,8 +13485,8 @@ async function startDevpodWorkspace(options) {
12978
13485
  }
12979
13486
 
12980
13487
  // src/core/host-port-claims.ts
12981
- var import_node_child_process23 = require("child_process");
12982
- var import_node_path38 = __toESM(require("path"));
13488
+ var import_node_child_process24 = require("child_process");
13489
+ var import_node_path39 = __toESM(require("path"));
12983
13490
  var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
12984
13491
  var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
12985
13492
  var DOCKER_TIMEOUT_MS = 1e4;
@@ -13082,7 +13589,7 @@ function hostPortBindingsConflict(desired, holder) {
13082
13589
  }
13083
13590
  var HOST_PORT_INSPECT_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}}},"ports":{{json .NetworkSettings.Ports}}}';
13084
13591
  function runDocker(args) {
13085
- const result = (0, import_node_child_process23.spawnSync)("docker", args, {
13592
+ const result = (0, import_node_child_process24.spawnSync)("docker", args, {
13086
13593
  ...networkDockerOptions(),
13087
13594
  encoding: "utf-8",
13088
13595
  timeout: DOCKER_TIMEOUT_MS,
@@ -13152,7 +13659,7 @@ function listHolderSnapshots() {
13152
13659
  }
13153
13660
  function renderManagedComposeModel(plan, workspace) {
13154
13661
  const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
13155
- const result = (0, import_node_child_process23.spawnSync)(
13662
+ const result = (0, import_node_child_process24.spawnSync)(
13156
13663
  "docker",
13157
13664
  [
13158
13665
  "compose",
@@ -13187,8 +13694,8 @@ function renderManagedComposeModel(plan, workspace) {
13187
13694
  }
13188
13695
  }
13189
13696
  function attributeHolderWorktree(workingDir) {
13190
- if (import_node_path38.default.basename(workingDir) !== ".devcontainer") return void 0;
13191
- return comparableWorkspacePath(import_node_path38.default.dirname(workingDir));
13697
+ if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13698
+ return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13192
13699
  }
13193
13700
  function holderAttribution(holder, repoPath) {
13194
13701
  const attribution = {};
@@ -13223,7 +13730,7 @@ function detectHostPortClaimConflicts(options) {
13223
13730
  );
13224
13731
  if (desired.length === 0) return [];
13225
13732
  const holders = listHolderSnapshots();
13226
- const devcontainerDir = import_node_path38.default.join(options.repoPath, ".devcontainer");
13733
+ const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13227
13734
  const conflicts = [];
13228
13735
  for (const binding of desired) {
13229
13736
  for (const holder of holders) {
@@ -13247,7 +13754,7 @@ function detectHostPortClaimConflicts(options) {
13247
13754
  }
13248
13755
 
13249
13756
  // src/core/http-route-probe.ts
13250
- var import_node_child_process24 = require("child_process");
13757
+ var import_node_child_process25 = require("child_process");
13251
13758
  var DEFAULT_MAX_TIME_SECONDS = 5;
13252
13759
  var PARENT_TIMEOUT_PADDING_MS = 250;
13253
13760
  var CONTRACT_MAX_BUFFER = 1024;
@@ -13280,7 +13787,7 @@ function probeHttpRoute(host, options = {}) {
13280
13787
  args.push("--cacert", CERT_FILE);
13281
13788
  }
13282
13789
  args.push(url);
13283
- const result = (0, import_node_child_process24.spawnSync)("curl", args, { encoding: "utf-8" });
13790
+ const result = (0, import_node_child_process25.spawnSync)("curl", args, { encoding: "utf-8" });
13284
13791
  const stdout = result.stdout?.trim() ?? "";
13285
13792
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
13286
13793
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
@@ -13299,7 +13806,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
13299
13806
  }
13300
13807
  let result;
13301
13808
  try {
13302
- result = (0, import_node_child_process24.spawnSync)("curl", args, {
13809
+ result = (0, import_node_child_process25.spawnSync)("curl", args, {
13303
13810
  encoding: "utf-8",
13304
13811
  maxBuffer: CONTRACT_MAX_BUFFER,
13305
13812
  timeout: Math.ceil(parentTimeoutMs)
@@ -13383,8 +13890,8 @@ function transportFailure() {
13383
13890
  }
13384
13891
 
13385
13892
  // src/core/managed-host-preparation.ts
13386
- var import_node_child_process25 = require("child_process");
13387
- var import_node_fs33 = __toESM(require("fs"));
13893
+ var import_node_child_process26 = require("child_process");
13894
+ var import_node_fs34 = __toESM(require("fs"));
13388
13895
  var PREPARATION_TIMEOUT_MS = 6e4;
13389
13896
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
13390
13897
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -13395,7 +13902,7 @@ function runPreparationCommand(argv, cwd) {
13395
13902
  return new Promise((resolve) => {
13396
13903
  let child;
13397
13904
  try {
13398
- child = (0, import_node_child_process25.spawn)(argv[0], argv.slice(1), {
13905
+ child = (0, import_node_child_process26.spawn)(argv[0], argv.slice(1), {
13399
13906
  cwd,
13400
13907
  detached: true,
13401
13908
  shell: false,
@@ -13427,7 +13934,7 @@ function runPreparationCommand(argv, cwd) {
13427
13934
  }
13428
13935
  function readConfigBytes(configPath) {
13429
13936
  try {
13430
- return import_node_fs33.default.readFileSync(configPath);
13937
+ return import_node_fs34.default.readFileSync(configPath);
13431
13938
  } catch {
13432
13939
  return void 0;
13433
13940
  }
@@ -13498,7 +14005,7 @@ function projectLifecycleOperation(operation) {
13498
14005
  }
13499
14006
 
13500
14007
  // src/core/managed-mount-nesting.ts
13501
- var import_node_child_process26 = require("child_process");
14008
+ var import_node_child_process27 = require("child_process");
13502
14009
  var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
13503
14010
  var UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
13504
14011
  function normalizeDestination(value) {
@@ -13571,7 +14078,7 @@ function classifyManagedMountNesting(configured, effectiveDestinations, containe
13571
14078
  };
13572
14079
  }
13573
14080
  function readContainerMountinfo(containerId, options) {
13574
- const result = (0, import_node_child_process26.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
14081
+ const result = (0, import_node_child_process27.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
13575
14082
  encoding: "utf-8",
13576
14083
  timeout: options.timeoutMs ?? 5e3,
13577
14084
  stdio: ["ignore", "pipe", "pipe"],
@@ -14116,7 +14623,7 @@ function collectManagedRuntimeStatus(options) {
14116
14623
  }
14117
14624
 
14118
14625
  // src/core/network-inventory.ts
14119
- var import_node_child_process27 = require("child_process");
14626
+ var import_node_child_process28 = require("child_process");
14120
14627
  var NetworkInventoryError = class extends Error {
14121
14628
  };
14122
14629
  var MAX_IDS = 4096;
@@ -14128,7 +14635,7 @@ function readDocker(endpoint, args, timeoutMs) {
14128
14635
  const env = { ...process.env };
14129
14636
  delete env.DOCKER_CONTEXT;
14130
14637
  delete env.DOCKER_HOST;
14131
- const result = (0, import_node_child_process27.spawnSync)("docker", ["--host", endpoint, ...args], {
14638
+ const result = (0, import_node_child_process28.spawnSync)("docker", ["--host", endpoint, ...args], {
14132
14639
  env,
14133
14640
  encoding: "utf-8",
14134
14641
  timeout: timeoutMs,
@@ -14292,37 +14799,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
14292
14799
  }
14293
14800
 
14294
14801
  // src/core/network-routes.ts
14295
- var import_node_child_process28 = require("child_process");
14802
+ var import_node_child_process29 = require("child_process");
14296
14803
  var import_node_net3 = require("net");
14297
14804
 
14298
14805
  // src/core/network-local-daemon.ts
14299
- var import_node_fs34 = __toESM(require("fs"));
14300
- var import_node_path39 = __toESM(require("path"));
14806
+ var import_node_fs35 = __toESM(require("fs"));
14807
+ var import_node_path40 = __toESM(require("path"));
14301
14808
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14302
14809
  const deadline = Date.now() + 2e3;
14303
14810
  try {
14304
14811
  if (!endpoint.startsWith("unix:///")) return false;
14305
14812
  const socket = endpoint.slice("unix://".length);
14306
- const table = import_node_fs34.default.readFileSync(import_node_path39.default.join(procRoot, "net/unix"), "utf8");
14813
+ const table = import_node_fs35.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
14307
14814
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14308
14815
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14309
14816
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14310
14817
  const inode = `socket:[${rows2[0][6]}]`;
14311
- const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path39.default.join(procRoot, "self/ns/net"));
14312
- const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14818
+ const ownNamespace = import_node_fs35.default.readlinkSync(import_node_path40.default.join(procRoot, "self/ns/net"));
14819
+ const pids = import_node_fs35.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14313
14820
  if (pids.length > 4096) return false;
14314
14821
  for (const pid of pids) {
14315
14822
  if (Date.now() > deadline) return false;
14316
14823
  try {
14317
- const root = import_node_path39.default.join(procRoot, pid);
14318
- if (import_node_path39.default.basename(import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "exe"))) !== "dockerd") continue;
14319
- if (import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "ns/net")) !== ownNamespace) continue;
14320
- const descriptors = import_node_fs34.default.readdirSync(import_node_path39.default.join(root, "fd"));
14824
+ const root = import_node_path40.default.join(procRoot, pid);
14825
+ if (import_node_path40.default.basename(import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "exe"))) !== "dockerd") continue;
14826
+ if (import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "ns/net")) !== ownNamespace) continue;
14827
+ const descriptors = import_node_fs35.default.readdirSync(import_node_path40.default.join(root, "fd"));
14321
14828
  if (descriptors.length > 4096) continue;
14322
14829
  if (descriptors.some((fd) => {
14323
14830
  if (Date.now() > deadline) return false;
14324
14831
  try {
14325
- return import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "fd", fd)) === inode;
14832
+ return import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
14326
14833
  } catch {
14327
14834
  return false;
14328
14835
  }
@@ -14361,7 +14868,7 @@ function parseLinuxNetworkRoutes(output) {
14361
14868
  function collectNetworkRoutes(options) {
14362
14869
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
14363
14870
  return { status: "unknown", routes: [] };
14364
- const result = (0, import_node_child_process28.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14871
+ const result = (0, import_node_child_process29.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14365
14872
  encoding: "utf8",
14366
14873
  timeout: 5e3,
14367
14874
  maxBuffer: 1024 * 1024
@@ -14397,10 +14904,10 @@ function inspectLegacyNetworkCapacity(input) {
14397
14904
  }
14398
14905
 
14399
14906
  // src/core/network-managed.ts
14400
- var import_node_crypto23 = require("crypto");
14907
+ var import_node_crypto24 = require("crypto");
14401
14908
 
14402
14909
  // src/core/network-compose.ts
14403
- var import_node_crypto21 = require("crypto");
14910
+ var import_node_crypto22 = require("crypto");
14404
14911
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
14405
14912
  function isRecord5(value) {
14406
14913
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -15206,19 +15713,19 @@ function stableValue(value) {
15206
15713
  }
15207
15714
  function fingerprintComposeNetworkModel(input) {
15208
15715
  const projection = normalizedFingerprintProjection(input);
15209
- return (0, import_node_crypto21.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
15716
+ return (0, import_node_crypto22.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
15210
15717
  }
15211
15718
 
15212
15719
  // src/core/network-compose-files.ts
15213
- var import_node_child_process29 = require("child_process");
15214
- var import_node_crypto22 = require("crypto");
15215
- var import_node_fs35 = __toESM(require("fs"));
15216
- var import_node_path40 = __toESM(require("path"));
15217
- var import_yaml4 = __toESM(require("yaml"));
15720
+ var import_node_child_process30 = require("child_process");
15721
+ var import_node_crypto23 = require("crypto");
15722
+ var import_node_fs36 = __toESM(require("fs"));
15723
+ var import_node_path41 = __toESM(require("path"));
15724
+ var import_yaml5 = __toESM(require("yaml"));
15218
15725
  var OVERLAY = "docker-compose.devrouter-network.yml";
15219
15726
  var MARKER = "# devrouter:managed workspace network";
15220
15727
  var MAX_BYTES2 = 2 * 1024 * 1024;
15221
- function object3(value) {
15728
+ function object4(value) {
15222
15729
  if (!value || typeof value !== "object" || Array.isArray(value))
15223
15730
  throw new Error("Network Compose evidence is malformed.");
15224
15731
  return value;
@@ -15226,20 +15733,20 @@ function object3(value) {
15226
15733
  function inspectNetworkComposeFiles(input) {
15227
15734
  const authoredNetworks = {};
15228
15735
  for (const file of input.plan.composeFiles) {
15229
- const stat = import_node_fs35.default.lstatSync(file);
15736
+ const stat = import_node_fs36.default.lstatSync(file);
15230
15737
  if (!stat.isFile() || stat.size > MAX_BYTES2)
15231
15738
  throw new Error("Network Compose source is unavailable or too large.");
15232
15739
  let source;
15233
15740
  try {
15234
- source = object3(import_yaml4.default.parse(import_node_fs35.default.readFileSync(file, "utf8")));
15741
+ source = object4(import_yaml5.default.parse(import_node_fs36.default.readFileSync(file, "utf8")));
15235
15742
  } catch {
15236
15743
  throw new Error("Network Compose source could not be parsed safely.");
15237
15744
  }
15238
15745
  if (source.networks !== void 0) {
15239
- for (const [name, declaration] of Object.entries(object3(source.networks))) {
15746
+ for (const [name, declaration] of Object.entries(object4(source.networks))) {
15240
15747
  authoredNetworks[name] = {
15241
15748
  ...authoredNetworks[name],
15242
- ...declaration === null ? {} : object3(declaration)
15749
+ ...declaration === null ? {} : object4(declaration)
15243
15750
  };
15244
15751
  }
15245
15752
  }
@@ -15247,7 +15754,7 @@ function inspectNetworkComposeFiles(input) {
15247
15754
  const env = managedComposeEnvironment(input.workspace);
15248
15755
  delete env.DOCKER_CONTEXT;
15249
15756
  delete env.DOCKER_HOST;
15250
- const result = (0, import_node_child_process29.spawnSync)(
15757
+ const result = (0, import_node_child_process30.spawnSync)(
15251
15758
  "docker",
15252
15759
  [
15253
15760
  "--host",
@@ -15273,7 +15780,7 @@ function inspectNetworkComposeFiles(input) {
15273
15780
  if (result.error || result.status !== 0)
15274
15781
  throw new Error("Complete network Compose evidence is unavailable.");
15275
15782
  try {
15276
- return { compose: object3(JSON.parse(result.stdout)), authoredNetworks };
15783
+ return { compose: object4(JSON.parse(result.stdout)), authoredNetworks };
15277
15784
  } catch {
15278
15785
  throw new Error("Network Compose evidence is malformed.");
15279
15786
  }
@@ -15281,8 +15788,8 @@ function inspectNetworkComposeFiles(input) {
15281
15788
  function prepareNetworkComposeFiles(plan, subnet) {
15282
15789
  const overlay = deriveComposeNetworkOverlay(subnet);
15283
15790
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
15284
- const overlayPath = import_node_path40.default.join(plan.composeDirectory, OVERLAY);
15285
- const ignored = (0, import_node_child_process29.spawnSync)("git", [
15791
+ const overlayPath = import_node_path41.default.join(plan.composeDirectory, OVERLAY);
15792
+ const ignored = (0, import_node_child_process30.spawnSync)("git", [
15286
15793
  "-C",
15287
15794
  plan.composeDirectory,
15288
15795
  "check-ignore",
@@ -15294,11 +15801,11 @@ function prepareNetworkComposeFiles(plan, subnet) {
15294
15801
  if (ignored.status !== 0)
15295
15802
  throw new Error("Generated network overlay must be ignored before allocation.");
15296
15803
  const bytes2 = `${MARKER}
15297
- ${import_yaml4.default.stringify(overlay.overlay)}`;
15804
+ ${import_yaml5.default.stringify(overlay.overlay)}`;
15298
15805
  const assertOwnership = () => {
15299
- if (!import_node_fs35.default.existsSync(overlayPath)) return;
15300
- const stat = import_node_fs35.default.lstatSync(overlayPath);
15301
- if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs35.default.readFileSync(overlayPath, "utf8") !== bytes2)
15806
+ if (!import_node_fs36.default.existsSync(overlayPath)) return;
15807
+ const stat = import_node_fs36.default.lstatSync(overlayPath);
15808
+ if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs36.default.readFileSync(overlayPath, "utf8") !== bytes2)
15302
15809
  throw new Error("Retained network overlay changed; repair is required.");
15303
15810
  };
15304
15811
  assertOwnership();
@@ -15312,7 +15819,7 @@ ${JSON.stringify(extended.config, null, 2)}
15312
15819
  ...plan,
15313
15820
  contents,
15314
15821
  composeFiles: [...plan.composeFiles, overlayPath],
15315
- effectiveConfigSha256: (0, import_node_crypto22.createHash)("sha256").update(contents).digest("hex")
15822
+ effectiveConfigSha256: (0, import_node_crypto23.createHash)("sha256").update(contents).digest("hex")
15316
15823
  };
15317
15824
  return {
15318
15825
  plan: result,
@@ -15324,7 +15831,7 @@ ${JSON.stringify(extended.config, null, 2)}
15324
15831
  }
15325
15832
 
15326
15833
  // src/core/network-connected-route.ts
15327
- var import_node_child_process30 = require("child_process");
15834
+ var import_node_child_process31 = require("child_process");
15328
15835
  function bounds(cidr) {
15329
15836
  const canonical = parseIPv4Cidr(cidr);
15330
15837
  if (!canonical) throw new Error("Invalid connected route.");
@@ -15349,7 +15856,7 @@ function inspectClaimedBridge(claim2, networkId) {
15349
15856
  const env = { ...process.env };
15350
15857
  delete env.DOCKER_CONTEXT;
15351
15858
  delete env.DOCKER_HOST;
15352
- const result = (0, import_node_child_process30.spawnSync)(
15859
+ const result = (0, import_node_child_process31.spawnSync)(
15353
15860
  "docker",
15354
15861
  [
15355
15862
  "--host",
@@ -15376,7 +15883,7 @@ function inspectClaimedBridge(claim2, networkId) {
15376
15883
  const [first, last] = bounds(claim2.subnet);
15377
15884
  const [address] = bounds(gateway);
15378
15885
  if (address <= first || address >= last) throw new Error();
15379
- const interfaces = (0, import_node_child_process30.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
15886
+ const interfaces = (0, import_node_child_process31.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
15380
15887
  encoding: "utf8",
15381
15888
  timeout: 5e3,
15382
15889
  maxBuffer: 65536
@@ -15630,7 +16137,7 @@ function createManagedNetworkSession(input) {
15630
16137
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
15631
16138
  );
15632
16139
  const authority = readNetworkOperationAuthority(identity2);
15633
- const fingerprint = (0, import_node_crypto23.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
16140
+ const fingerprint = (0, import_node_crypto24.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
15634
16141
  if (!saved) {
15635
16142
  if (policy.status !== "valid")
15636
16143
  throw new Error("New network allocation requires valid machine policy.");
@@ -15814,11 +16321,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
15814
16321
  var POLL_INTERVAL_MS = 1e3;
15815
16322
  function assertOverlay(container, repoPath) {
15816
16323
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15817
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))) {
16324
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))) {
15818
16325
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
15819
16326
  }
15820
16327
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
15821
- const expectedOverlay = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16328
+ const expectedOverlay = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15822
16329
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
15823
16330
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
15824
16331
  }
@@ -15832,7 +16339,7 @@ function assertWorktreeOwnership(container, repoPath) {
15832
16339
  function isInsideWorktree(candidate, repoPath) {
15833
16340
  const normalizedCandidate = comparableWorkspacePath(candidate);
15834
16341
  const normalizedRoot = comparableWorkspacePath(repoPath);
15835
- return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path41.default.sep}`);
16342
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path42.default.sep}`);
15836
16343
  }
15837
16344
  function assertReady(container, label) {
15838
16345
  if (!container.state.Running) {
@@ -16024,7 +16531,7 @@ function hasExactManagedComposeProject(repoPath, state) {
16024
16531
  }).some((container) => {
16025
16532
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
16026
16533
  return Boolean(
16027
- workingDir && sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))
16534
+ workingDir && sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))
16028
16535
  );
16029
16536
  });
16030
16537
  } catch {
@@ -16132,7 +16639,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
16132
16639
  target
16133
16640
  });
16134
16641
  if (target.kind === "linked") {
16135
- const workspaceEnv = (0, import_node_child_process31.spawnSync)(
16642
+ const workspaceEnv = (0, import_node_child_process32.spawnSync)(
16136
16643
  "docker",
16137
16644
  ["exec", appContainer.id, "printenv", "WORKSPACE"],
16138
16645
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -16142,7 +16649,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
16142
16649
  `Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
16143
16650
  );
16144
16651
  }
16145
- const devrouterWorkspaceEnv = (0, import_node_child_process31.spawnSync)(
16652
+ const devrouterWorkspaceEnv = (0, import_node_child_process32.spawnSync)(
16146
16653
  "docker",
16147
16654
  ["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
16148
16655
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -16153,7 +16660,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
16153
16660
  );
16154
16661
  }
16155
16662
  }
16156
- const gitCheck = (0, import_node_child_process31.spawnSync)(
16663
+ const gitCheck = (0, import_node_child_process32.spawnSync)(
16157
16664
  "docker",
16158
16665
  [
16159
16666
  "exec",
@@ -16429,14 +16936,14 @@ function resolvePrimaryTarget(repoPath) {
16429
16936
  }
16430
16937
  function isPrimaryCheckout(repoPath) {
16431
16938
  try {
16432
- return import_node_fs36.default.statSync(import_node_path41.default.join(repoPath, ".git")).isDirectory();
16939
+ return import_node_fs37.default.statSync(import_node_path42.default.join(repoPath, ".git")).isDirectory();
16433
16940
  } catch {
16434
16941
  return false;
16435
16942
  }
16436
16943
  }
16437
16944
  function openUrls(urls) {
16438
16945
  for (const url of urls) {
16439
- const opened = (0, import_node_child_process31.spawnSync)("open", [url], { encoding: "utf-8" });
16946
+ const opened = (0, import_node_child_process32.spawnSync)("open", [url], { encoding: "utf-8" });
16440
16947
  if (opened.status !== 0) {
16441
16948
  process.stderr.write(`Warning: could not open '${url}'.
16442
16949
  `);
@@ -16466,6 +16973,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16466
16973
  let repairMutationStarted = false;
16467
16974
  let retainedRepairContainers = [];
16468
16975
  let environmentStarted = false;
16976
+ let freshBootstrapPending = false;
16469
16977
  let managedPlan;
16470
16978
  let previousManagedState;
16471
16979
  let managedComposeProject;
@@ -16492,9 +17000,21 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16492
17000
  reportLifecycleProgress("validation");
16493
17001
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
16494
17002
  let devpodId = target.devpodId;
17003
+ if (options.initializeNew) {
17004
+ assertFreshBootstrapRequest(
17005
+ {
17006
+ repoPath,
17007
+ workspace: target.workspace ?? null,
17008
+ provider: resolveWorkspaceRuntimeOrDefault(repoPath)
17009
+ },
17010
+ options
17011
+ );
17012
+ devpodId ??= wsFromBranch(import_node_path42.default.basename(repoPath));
17013
+ if (!devpodId) throw new Error("Fresh bootstrap requires an exact provider identifier.");
17014
+ }
16495
17015
  if (target.kind === "linked") {
16496
- const overlayPath = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16497
- if (!import_node_fs36.default.existsSync(overlayPath)) {
17016
+ const overlayPath = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
17017
+ if (!import_node_fs37.default.existsSync(overlayPath)) {
16498
17018
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
16499
17019
  }
16500
17020
  }
@@ -16604,7 +17124,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16604
17124
  }
16605
17125
  const apps = proxyAppsFromConfig(runtime.config);
16606
17126
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
16607
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path41.default.basename(repoPath)) ?? "app";
17127
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path42.default.basename(repoPath)) ?? "app";
16608
17128
  for (const [index, app] of apps.entries()) {
16609
17129
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
16610
17130
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -16670,6 +17190,25 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16670
17190
  managedConfigWritten = true;
16671
17191
  }
16672
17192
  const upstreamHosts = parsedUpstreams.map((upstream) => upstream.host);
17193
+ const freshBootstrap = options.initializeNew ? createFreshBootstrap({
17194
+ identity: { repoPath, workspace: target.workspace ?? null, provider: "devsy" },
17195
+ ...managedWorkspaceEnv ? { workspace: managedWorkspaceEnv } : {},
17196
+ plan: () => {
17197
+ if (!managedPlan) throw new Error("Fresh bootstrap requires a managed Compose plan.");
17198
+ return managedPlan;
17199
+ },
17200
+ profile: runtime.profile,
17201
+ variant,
17202
+ apps: apps.map((app) => app.name),
17203
+ processes: desiredProcesses,
17204
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
17205
+ onCaptured: (state, captured) => {
17206
+ freshBootstrapPending = true;
17207
+ capturedStopState = state;
17208
+ managedContainer = captured;
17209
+ managedComposeProject = state.composeProject;
17210
+ }
17211
+ }) : void 0;
16673
17212
  const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
16674
17213
  const startAndProveAttachment = async (recreate = false) => {
16675
17214
  reportLifecycleProgress("provider");
@@ -16682,6 +17221,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16682
17221
  ...network ? { prepareNetwork: network.prepare } : {},
16683
17222
  recreate,
16684
17223
  quiet: options.quiet,
17224
+ ...freshBootstrap ? { freshBootstrap } : {},
16685
17225
  ...requestedTarget.kind === "linked" ? {
16686
17226
  workspace: {
16687
17227
  token: requestedTarget.workspace,
@@ -16690,6 +17230,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16690
17230
  } : {}
16691
17231
  });
16692
17232
  environmentStarted = true;
17233
+ freshBootstrapPending = false;
16693
17234
  } catch (error) {
16694
17235
  if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
16695
17236
  throw error;
@@ -16730,7 +17271,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16730
17271
  transitionPhase = "service-start";
16731
17272
  reportLifecycleProgress("service-start");
16732
17273
  claimLifecycleEffect();
16733
- const result = (0, import_node_child_process31.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
17274
+ const result = (0, import_node_child_process32.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
16734
17275
  ...networkDockerOptions(),
16735
17276
  encoding: "utf-8",
16736
17277
  timeout: DEFAULT_READINESS_TIMEOUT_MS,
@@ -17193,6 +17734,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
17193
17734
  `${original} Reset candidate retained for recovery.${failures.length ? ` Recovery incomplete: ${failures.join("; ")}.` : " State is degraded."}`
17194
17735
  );
17195
17736
  }
17737
+ if (freshBootstrapPending) throw error;
17196
17738
  if (options.repair && !repairMutationStarted) throw error;
17197
17739
  if (options.repair && repairMutationStarted && previousManagedState && managedPlan) {
17198
17740
  const failures = [];
@@ -17489,9 +18031,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
17489
18031
  }
17490
18032
  const repairLocked = () => {
17491
18033
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
17492
- import_node_fs36.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
18034
+ import_node_fs37.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17493
18035
  return withFileLock(
17494
- import_node_path41.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
18036
+ import_node_path42.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17495
18037
  {
17496
18038
  activity: "Managed runtime repair",
17497
18039
  target: repoPath,
@@ -17669,7 +18211,7 @@ function preflightRuntimeVariant(repoPath, options) {
17669
18211
  return variant;
17670
18212
  }
17671
18213
  function capacityPolicyTargets(repoPath) {
17672
- const policy = readCapacityPolicy(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
18214
+ const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
17673
18215
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
17674
18216
  }
17675
18217
  function clearActiveLifecycleStartupWitness() {
@@ -17679,6 +18221,40 @@ function clearActiveLifecycleStartupWitness() {
17679
18221
  if (record5?.startupWitness?.operationId !== request.operationId) return;
17680
18222
  clearStartupWitness(request.identity, reliabilityFence(record5.state));
17681
18223
  }
18224
+ function claimActiveFreshBootstrap(evidence) {
18225
+ const request = activeWorker;
18226
+ if (request?.kind !== "ensure" || request.options.initializeNew !== true || request.options.repair)
18227
+ throw new Error("Fresh bootstrap requires an explicit initialization worker.");
18228
+ claimLifecycleEffect();
18229
+ return updateReliabilityOperation(request.identity, (record5) => {
18230
+ if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || record5.state.operation?.id !== request.operationId || record5.freshBootstrap)
18231
+ throw new Error(
18232
+ "Fresh-bootstrap authority is stale or already claimed; initialization was not retried."
18233
+ );
18234
+ record5.freshBootstrap = {
18235
+ ...evidence,
18236
+ version: 1,
18237
+ operationId: request.operationId,
18238
+ workerId: request.workerId,
18239
+ runtimeGeneration: request.fence.runtimeGeneration,
18240
+ stage: "claimed"
18241
+ };
18242
+ return structuredClone(record5.freshBootstrap);
18243
+ });
18244
+ }
18245
+ function advanceActiveFreshBootstrap(stage, resources) {
18246
+ const request = activeWorker;
18247
+ if (request?.kind !== "ensure" || request.options.initializeNew !== true)
18248
+ throw new Error("Fresh-bootstrap phase requires its initialization worker.");
18249
+ claimLifecycleEffect();
18250
+ updateReliabilityOperation(request.identity, (record5) => {
18251
+ const attempt = record5.freshBootstrap;
18252
+ if (!matchesFence(record5, request.fence) || record5.worker?.id !== request.workerId || attempt?.operationId !== request.operationId || attempt.workerId !== request.workerId || attempt.runtimeGeneration !== request.fence.runtimeGeneration)
18253
+ throw new Error("Fresh-bootstrap attempt changed before its next phase.");
18254
+ attempt.stage = stage;
18255
+ if (resources !== void 0) attempt.resources = structuredClone(resources);
18256
+ });
18257
+ }
17682
18258
  async function acquireWorkerLock(repoPath, operation) {
17683
18259
  const deadline = Date.now() + 3e4;
17684
18260
  for (; ; ) {
@@ -17775,7 +18351,7 @@ async function executeLifecycleWorker(request, operation) {
17775
18351
  const owned = containers.filter(
17776
18352
  (container) => sameWorkspacePath(
17777
18353
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17778
- import_node_path42.default.join(request.repoPath, ".devcontainer")
18354
+ import_node_path43.default.join(request.repoPath, ".devcontainer")
17779
18355
  )
17780
18356
  );
17781
18357
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -17862,7 +18438,7 @@ function proveLifecycleStopped() {
17862
18438
  throw new Error("Full stop proof requires an explicit stop worker.");
17863
18439
  const settle = () => {
17864
18440
  claimLifecycleEffect();
17865
- if (initialStopAbsence && !(0, import_node_util8.isDeepStrictEqual)(
18441
+ if (initialStopAbsence && !(0, import_node_util9.isDeepStrictEqual)(
17866
18442
  proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
17867
18443
  initialStopAbsence
17868
18444
  ))
@@ -17882,7 +18458,7 @@ function proveLifecycleStopped() {
17882
18458
  const remaining = inspectWorkspaceContainers().filter(
17883
18459
  (container) => sameWorkspacePath(
17884
18460
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17885
- import_node_path42.default.join(request.repoPath, ".devcontainer")
18461
+ import_node_path43.default.join(request.repoPath, ".devcontainer")
17886
18462
  )
17887
18463
  );
17888
18464
  if (remaining.some(
@@ -17912,7 +18488,7 @@ function proveLifecycleStopped() {
17912
18488
  return void 0;
17913
18489
  });
17914
18490
  if (settlement) {
17915
- const capacity = createLifecycleCapacityStore(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
18491
+ const capacity = createLifecycleCapacityStore(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
17916
18492
  for (let attempt = 0; ; attempt++) {
17917
18493
  try {
17918
18494
  const state = capacity.inspect();
@@ -17991,7 +18567,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
17991
18567
  const launch = () => {
17992
18568
  if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
17993
18569
  claimLifecycleEffect();
17994
- return (0, import_node_child_process32.spawn)("devsy", args, { stdio: "inherit" });
18570
+ return (0, import_node_child_process33.spawn)("devsy", args, { stdio: "inherit" });
17995
18571
  };
17996
18572
  child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
17997
18573
  } catch (error) {
@@ -18039,7 +18615,7 @@ function ensureGuidance2(repoPath) {
18039
18615
  return `Run 'devrouter ensure ${repoPath}' first.`;
18040
18616
  }
18041
18617
  function assertRunningDevpod(devpodId, repoPath) {
18042
- const result = (0, import_node_child_process33.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
18618
+ const result = (0, import_node_child_process34.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
18043
18619
  encoding: "utf-8"
18044
18620
  });
18045
18621
  if (result.status !== 0) {
@@ -18086,7 +18662,7 @@ async function devpodExecOutcome(repoPath, command) {
18086
18662
  }
18087
18663
  assertRunningDevpod(devpod.id, repoPath);
18088
18664
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
18089
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto25.randomUUID)()}__:`;
18665
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto26.randomUUID)()}__:`;
18090
18666
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
18091
18667
  const literalCommand = command.map(quotePosixArg).join(" ");
18092
18668
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -18218,7 +18794,7 @@ async function devpodExecOutcome(repoPath, command) {
18218
18794
  let child;
18219
18795
  try {
18220
18796
  claimLifecycleEffect();
18221
- child = (0, import_node_child_process33.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
18797
+ child = (0, import_node_child_process34.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
18222
18798
  } catch (error) {
18223
18799
  settled = true;
18224
18800
  reject(
@@ -18275,12 +18851,12 @@ async function devpodExecOutcome(repoPath, command) {
18275
18851
  }
18276
18852
 
18277
18853
  // src/core/workspace-lifecycle.ts
18278
- var import_node_child_process34 = require("child_process");
18279
- var import_node_fs38 = __toESM(require("fs"));
18280
- var import_node_path43 = __toESM(require("path"));
18854
+ var import_node_child_process35 = require("child_process");
18855
+ var import_node_fs39 = __toESM(require("fs"));
18856
+ var import_node_path44 = __toESM(require("path"));
18281
18857
 
18282
18858
  // src/core/route-state.ts
18283
- var import_node_fs37 = __toESM(require("fs"));
18859
+ var import_node_fs38 = __toESM(require("fs"));
18284
18860
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
18285
18861
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
18286
18862
  }