@devrouter/cli 0.6.0 → 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_crypto24 = 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"));
@@ -2695,6 +2695,72 @@ function assertReadableComposeFile(file) {
2695
2695
  throw new Error("Managed container Compose configuration contains an unreadable file.");
2696
2696
  }
2697
2697
  }
2698
+ function resolveManagedServiceNetworkNamespace(options) {
2699
+ const { plan, containers, container, serviceConfig, env } = options;
2700
+ const service = container.labels["com.docker.compose.service"];
2701
+ const targetService = String(serviceConfig.network_mode).slice("service:".length);
2702
+ const target = containers.filter(
2703
+ (item) => item.labels["com.docker.compose.service"] === targetService
2704
+ );
2705
+ const primary = containers.filter(
2706
+ (item) => item.labels["com.docker.compose.service"] === plan.primaryService
2707
+ );
2708
+ const repoPath = import_node_path12.default.dirname(plan.composeDirectory);
2709
+ const owned = (item) => /^[a-f0-9]{64}$/.test(item.id) && containers.filter((candidate) => candidate.id === item.id).length === 1 && containers.filter(
2710
+ (candidate) => candidate.labels["com.docker.compose.service"] === item.labels["com.docker.compose.service"]
2711
+ ).length === 1 && plan.desiredServices.includes(item.labels["com.docker.compose.service"] ?? "") && hasExactComposeIdentity(item, {
2712
+ repoPath,
2713
+ service: item.labels["com.docker.compose.service"] ?? "",
2714
+ composeProject: container.labels["com.docker.compose.project"],
2715
+ composeFiles: plan.composeFiles
2716
+ });
2717
+ if (targetService === service || target.length !== 1 || primary.length !== 1 || !owned(container) || !owned(target[0]) || !owned(primary[0]) || primary[0].mounts.filter(
2718
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
2719
+ ).length !== 1) {
2720
+ throw new Error("Managed service network namespace ownership is unavailable.");
2721
+ }
2722
+ const namespace = `container:${target[0].id}`;
2723
+ const ids2 = [container.id, target[0].id];
2724
+ const inspect = () => {
2725
+ const result = (0, import_node_child_process10.spawnSync)(
2726
+ "docker",
2727
+ [
2728
+ "inspect",
2729
+ "--type",
2730
+ "container",
2731
+ "--format",
2732
+ '{"id":{{json .Id}},"networkMode":{{json .HostConfig.NetworkMode}}}',
2733
+ ...ids2
2734
+ ],
2735
+ {
2736
+ env,
2737
+ encoding: "utf-8",
2738
+ stdio: ["ignore", "pipe", "ignore"],
2739
+ timeout: COMPOSE_HASH_TIMEOUT_MS,
2740
+ maxBuffer: 1024 * 1024
2741
+ }
2742
+ );
2743
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
2744
+ throw new Error("Managed service network namespace inspection failed.");
2745
+ }
2746
+ const rows2 = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
2747
+ if (rows2.length !== ids2.length || ids2.some(
2748
+ (id5) => rows2.filter(
2749
+ (row) => isObject2(row) && row.id === id5 && typeof row.networkMode === "string"
2750
+ ).length !== 1
2751
+ ) || !rows2.some((row) => isObject2(row) && row.id === container.id && row.networkMode === namespace)) {
2752
+ throw new Error("Managed service network namespace identity changed.");
2753
+ }
2754
+ return ids2.map((id5) => rows2.find((row) => isObject2(row) && row.id === id5));
2755
+ };
2756
+ const before = JSON.stringify(inspect());
2757
+ serviceConfig.network_mode = namespace;
2758
+ return () => {
2759
+ if (JSON.stringify(inspect()) !== before) {
2760
+ throw new Error("Managed service network namespace changed during configuration inspection.");
2761
+ }
2762
+ };
2763
+ }
2698
2764
  function assertManagedContainerConfigUnchanged(options) {
2699
2765
  if (options.containers.length === 0) {
2700
2766
  throw new Error("Managed container configuration cannot be verified without containers.");
@@ -2730,6 +2796,10 @@ function assertManagedContainerConfigUnchanged(options) {
2730
2796
  workingDirectory
2731
2797
  ];
2732
2798
  const env = managedComposeEnvironment(options.workspace);
2799
+ if (options.endpoint) {
2800
+ env.DOCKER_HOST = options.endpoint;
2801
+ delete env.DOCKER_CONTEXT;
2802
+ }
2733
2803
  const rendered = (0, import_node_child_process10.spawnSync)(
2734
2804
  "docker",
2735
2805
  [...composeArgs, ...fileArgs, "config", "--format", "json"],
@@ -2745,19 +2815,38 @@ function assertManagedContainerConfigUnchanged(options) {
2745
2815
  if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
2746
2816
  throw new Error("Compose configuration rendering failed.");
2747
2817
  }
2748
- return (0, import_node_child_process10.spawnSync)(
2818
+ const hash2 = (input2) => (0, import_node_child_process10.spawnSync)(
2749
2819
  "docker",
2750
2820
  [...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
2751
2821
  {
2752
2822
  cwd: workingDirectory,
2753
2823
  encoding: "utf-8",
2754
2824
  env,
2755
- input: rendered.stdout.replaceAll("$$", "$"),
2825
+ input: input2,
2756
2826
  stdio: ["pipe", "pipe", "ignore"],
2757
2827
  timeout: COMPOSE_HASH_TIMEOUT_MS,
2758
2828
  maxBuffer: 1024 * 1024
2759
2829
  }
2760
2830
  );
2831
+ const input = rendered.stdout.replaceAll("$$", "$");
2832
+ const result2 = hash2(input);
2833
+ const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
2834
+ if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
2835
+ return result2;
2836
+ const model = JSON.parse(input);
2837
+ const serviceConfig = isObject2(model) && isObject2(model.services) ? model.services[service] : void 0;
2838
+ if (!isObject2(serviceConfig) || typeof serviceConfig.network_mode !== "string" || !serviceConfig.network_mode.startsWith("service:"))
2839
+ return result2;
2840
+ const revalidate = resolveManagedServiceNetworkNamespace({
2841
+ plan: options.plan,
2842
+ containers: options.containers,
2843
+ container,
2844
+ serviceConfig,
2845
+ env
2846
+ });
2847
+ const resolved = hash2(JSON.stringify(model));
2848
+ revalidate();
2849
+ return resolved;
2761
2850
  } catch {
2762
2851
  throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
2763
2852
  }
@@ -4083,11 +4172,20 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4083
4172
  );
4084
4173
  if (currentOwner.status !== "owned") throw new Error("Stop recovery owner is unavailable.");
4085
4174
  proveLocalDockerSelection(currentOwner.workspace);
4086
- assertManagedStopContainersAbsent(
4087
- baseline.endpoint,
4088
- baseline.containers.map((container) => container.id)
4089
- );
4090
4175
  const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
4176
+ const missingIds = [];
4177
+ for (const recorded of baseline.containers) {
4178
+ const retained = containers.find((container) => container.id === recorded.id);
4179
+ if (!retained) {
4180
+ missingIds.push(recorded.id);
4181
+ } else if (!(0, import_node_util3.isDeepStrictEqual)(
4182
+ { ...recorded, mounts: orderedMounts(recorded.mounts) },
4183
+ identity(retained)
4184
+ )) {
4185
+ throw new Error("Stop recovery retained container identity changed.");
4186
+ }
4187
+ }
4188
+ if (missingIds.length > 0) assertManagedStopContainersAbsent(baseline.endpoint, missingIds);
4091
4189
  if (!(0, import_node_util3.isDeepStrictEqual)(
4092
4190
  containers.map((container) => container.labels["com.docker.compose.service"]).sort(),
4093
4191
  [...plan.desiredServices].sort()
@@ -4107,6 +4205,7 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4107
4205
  assertManagedContainerConfigUnchanged({
4108
4206
  plan,
4109
4207
  containers,
4208
+ endpoint: baseline.endpoint,
4110
4209
  workspace: state.workspace ? {
4111
4210
  token: state.workspace,
4112
4211
  gitCommonDir: resolveGitCommonDir(state.repoPath)
@@ -5490,7 +5589,7 @@ function retain(session, previous) {
5490
5589
 
5491
5590
  // src/core/reliability-operation-store.ts
5492
5591
  function installedCliVersion() {
5493
- return true ? "0.6.0" : "0.0.0-dev";
5592
+ return true ? "0.6.2" : "0.0.0-dev";
5494
5593
  }
5495
5594
  function compareCliVersions(left, right) {
5496
5595
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -5638,6 +5737,7 @@ function validate2(record5, identity2) {
5638
5737
  "state",
5639
5738
  "worker",
5640
5739
  "stopWorkStarted",
5740
+ "freshBootstrap",
5641
5741
  "effectSequence",
5642
5742
  "outcome",
5643
5743
  "result",
@@ -5655,6 +5755,47 @@ function validate2(record5, identity2) {
5655
5755
  ]);
5656
5756
  validateConsumerProtection(record5);
5657
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
+ }
5658
5799
  if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
5659
5800
  throw new Error(
5660
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.`
@@ -6134,10 +6275,30 @@ function updateReliabilityOperation(identity2, operation) {
6134
6275
  const previousEnrollment = record5.enrollment && { ...record5.enrollment };
6135
6276
  const previousProtection = structuredClone(record5.consumerProtection);
6136
6277
  const previousIdleProtection = structuredClone(record5.idleProtection);
6278
+ const previousBootstrap = structuredClone(record5.freshBootstrap);
6137
6279
  const result = operation(record5);
6138
6280
  if (result && typeof result.then === "function") {
6139
6281
  throw new Error("Reliability record updates must be synchronous.");
6140
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
+ }
6141
6302
  if (record5.version < previousVersion || previousEnrollment && !(0, import_node_util4.isDeepStrictEqual)(record5.enrollment, previousEnrollment))
6142
6303
  throw new Error("Durable capacity enrollment cannot be downgraded or replaced.");
6143
6304
  if (!(0, import_node_util4.isDeepStrictEqual)(record5.consumerProtection, previousProtection))
@@ -6603,7 +6764,7 @@ function parseApp(value, index) {
6603
6764
  if (objectValue.readiness !== void 0) {
6604
6765
  throw new Error(`${pathLabel}.readiness is only supported for HTTP proxy apps.`);
6605
6766
  }
6606
- const docker = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
6767
+ const docker2 = parseDockerConfig(objectValue.docker, `${pathLabel}.docker`);
6607
6768
  if (protocol === "http") {
6608
6769
  return {
6609
6770
  name,
@@ -6611,7 +6772,7 @@ function parseApp(value, index) {
6611
6772
  protocol: "http",
6612
6773
  runtime: "docker",
6613
6774
  dependencies,
6614
- docker
6775
+ docker: docker2
6615
6776
  };
6616
6777
  }
6617
6778
  if (protocol === "tcp") {
@@ -6628,7 +6789,7 @@ function parseApp(value, index) {
6628
6789
  tcpProtocol,
6629
6790
  runtime: "docker",
6630
6791
  dependencies,
6631
- docker
6792
+ docker: docker2
6632
6793
  };
6633
6794
  }
6634
6795
  }
@@ -6814,7 +6975,7 @@ function parseManagedRuntime(value, configPath) {
6814
6975
  );
6815
6976
  ensureAllowedKeys(
6816
6977
  devcontainer,
6817
- ["baseServices", "profileServices", "prepareCommand", "hostPorts"],
6978
+ ["baseServices", "profileServices", "prepareCommand", "hostPorts", "freshBootstrap"],
6818
6979
  `${configPath}.managedRuntime.devcontainer`
6819
6980
  );
6820
6981
  const baseServices = parseRequiredUniqueStringArray(
@@ -6829,6 +6990,23 @@ function parseManagedRuntime(value, configPath) {
6829
6990
  devcontainer.prepareCommand,
6830
6991
  `${configPath}.managedRuntime.devcontainer.prepareCommand`
6831
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
+ }
6832
7010
  const baseSet = new Set(baseServices);
6833
7011
  const overlappingServices = profileServices.filter((service) => baseSet.has(service));
6834
7012
  if (overlappingServices.length > 0) {
@@ -6890,7 +7068,8 @@ function parseManagedRuntime(value, configPath) {
6890
7068
  baseServices,
6891
7069
  profileServices,
6892
7070
  ...prepareCommand ? { prepareCommand } : {},
6893
- ...hostPorts !== void 0 ? { hostPorts } : {}
7071
+ ...hostPorts !== void 0 ? { hostPorts } : {},
7072
+ ...freshBootstrap ? { freshBootstrap } : {}
6894
7073
  },
6895
7074
  processes
6896
7075
  };
@@ -7593,7 +7772,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
7593
7772
  const config = parseRepoConfigSource(raw, configPath);
7594
7773
  const requiredVersion = config.devrouter?.version;
7595
7774
  if (requiredVersion && !hasWarnedVersionMismatch) {
7596
- const cliVersion = true ? "0.6.0" : "0.0.0-dev";
7775
+ const cliVersion = true ? "0.6.2" : "0.0.0-dev";
7597
7776
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7598
7777
  hasWarnedVersionMismatch = true;
7599
7778
  process.stderr.write(
@@ -7745,8 +7924,8 @@ async function createDockerClient() {
7745
7924
  throw new Error(`Unsupported docker host from context: ${host}`);
7746
7925
  }
7747
7926
  async function listContainers(all = true) {
7748
- const docker = await createDockerClient();
7749
- return docker.listContainers({ all });
7927
+ const docker2 = await createDockerClient();
7928
+ return docker2.listContainers({ all });
7750
7929
  }
7751
7930
  async function findContainerByName(name) {
7752
7931
  const containers = await listContainers(true);
@@ -7757,13 +7936,13 @@ async function isContainerRunning(name) {
7757
7936
  return container?.State === "running";
7758
7937
  }
7759
7938
  async function ensureNetwork(name) {
7760
- const docker = await createDockerClient();
7939
+ const docker2 = await createDockerClient();
7761
7940
  try {
7762
- await docker.getNetwork(name).inspect();
7941
+ await docker2.getNetwork(name).inspect();
7763
7942
  return;
7764
7943
  } catch {
7765
7944
  }
7766
- await docker.createNetwork({
7945
+ await docker2.createNetwork({
7767
7946
  Name: name,
7768
7947
  Driver: "bridge",
7769
7948
  Attachable: true
@@ -8636,6 +8815,7 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
8636
8815
  assertManagedContainerConfigUnchanged({
8637
8816
  plan,
8638
8817
  containers: population,
8818
+ endpoint,
8639
8819
  workspace: workspaceEnv
8640
8820
  });
8641
8821
  if (!(0, import_node_util5.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util5.isDeepStrictEqual)(registration2(), owner) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
@@ -10232,15 +10412,15 @@ function object2(value) {
10232
10412
  function qualifyNetworkProviderDefinition(value, provider) {
10233
10413
  const definition = object2(value);
10234
10414
  const agent = object2(definition.agent);
10235
- const docker = object2(agent.docker);
10236
- const env = object2(docker.env);
10415
+ const docker2 = object2(agent.docker);
10416
+ const env = object2(docker2.env);
10237
10417
  const options = object2(definition.options);
10238
10418
  const host = object2(options.DOCKER_HOST);
10239
10419
  const dockerPath = object2(options.DOCKER_PATH);
10240
10420
  const exec = object2(definition.exec);
10241
10421
  const command = provider === "devsy" ? '"${DEVSY}" internal sh -c "${COMMAND}"' : '"${DEVPOD}" helper sh -c "${COMMAND}"';
10242
10422
  const actualCommand = Array.isArray(exec.command) ? exec.command : [exec.command];
10243
- 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 !== "") {
10244
10424
  throw new Error("Docker provider definition is not qualified for network allocation.");
10245
10425
  }
10246
10426
  return (0, import_node_crypto14.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
@@ -10563,16 +10743,20 @@ async function startDevsyWorkspace(options) {
10563
10743
  if (!devsyId && options.recreate) {
10564
10744
  throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
10565
10745
  }
10746
+ if (options.freshBootstrap && (!devsyId || options.recreate))
10747
+ throw new Error("Fresh bootstrap requires an explicit new provider ID without recreation.");
10566
10748
  if (options.prepareNetwork && !devsyId)
10567
10749
  throw new Error("Network allocation requires an exact provider ID before dispatch.");
10568
10750
  const network = options.prepareNetwork?.(devsyId);
10569
10751
  if (network)
10570
10752
  assertNetworkProviderBinding(network.binding, network.evidence, network.firstAllocation);
10753
+ const fresh = options.freshBootstrap?.prepare(devsyId, network?.binding);
10754
+ const binding = network?.binding ?? fresh?.binding;
10571
10755
  const args = ["workspace", "up", options.repoPath];
10572
10756
  if (devsyId) args.push("--id", devsyId);
10573
10757
  const devcontainerPath = network?.devcontainerPath ?? options.devcontainerPath;
10574
10758
  if (devcontainerPath) args.push("--devcontainer", devcontainerPath);
10575
- if (network) args.push(...networkProviderStartupArguments(network.binding));
10759
+ if (binding) args.push(...networkProviderStartupArguments(binding));
10576
10760
  args.push("--ide-launch", "skip");
10577
10761
  if (options.workspace) {
10578
10762
  args.push(
@@ -10584,7 +10768,7 @@ async function startDevsyWorkspace(options) {
10584
10768
  }
10585
10769
  args.push("--provider-option", "INACTIVITY_TIMEOUT=");
10586
10770
  if (options.recreate) args.push("--recreate");
10587
- const env = network ? networkProviderEnvironment(network.binding, process.env) : { ...process.env };
10771
+ const env = binding ? networkProviderEnvironment(binding, process.env) : { ...process.env };
10588
10772
  if (agent.binaryPath) env.DEVSY_AGENT_BINARY = agent.binaryPath;
10589
10773
  else delete env.DEVSY_AGENT_BINARY;
10590
10774
  if (options.workspace) {
@@ -10599,7 +10783,11 @@ async function startDevsyWorkspace(options) {
10599
10783
  delete env.DEVCONTAINER_COMPOSE_OVERLAY;
10600
10784
  }
10601
10785
  network?.beforeDispatch?.();
10602
- 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
+ );
10603
10791
  if (result.status !== 0) {
10604
10792
  if (network) {
10605
10793
  network.retainUncertain();
@@ -10630,6 +10818,25 @@ async function startDevsyWorkspace(options) {
10630
10818
  `Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
10631
10819
  );
10632
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
+ }
10633
10840
  return devsyId;
10634
10841
  } catch (error) {
10635
10842
  network?.retainUncertain();
@@ -10650,9 +10857,9 @@ var ExecutionOutcomeError = class extends Error {
10650
10857
  };
10651
10858
 
10652
10859
  // src/core/reliability-lifecycle.ts
10653
- var import_node_crypto23 = require("crypto");
10654
- var import_node_path41 = __toESM(require("path"));
10655
- var import_node_util7 = require("util");
10860
+ var import_node_crypto25 = require("crypto");
10861
+ var import_node_path43 = __toESM(require("path"));
10862
+ var import_node_util9 = require("util");
10656
10863
 
10657
10864
  // src/core/capacity-policy.ts
10658
10865
  var import_node_fs27 = __toESM(require("fs"));
@@ -11309,15 +11516,467 @@ function reliabilityAttention(state) {
11309
11516
  return void 0;
11310
11517
  }
11311
11518
 
11312
- // src/core/managed-post-start.ts
11313
- var import_node_child_process20 = require("child_process");
11314
- 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");
11315
11522
  var import_node_fs29 = __toESM(require("fs"));
11523
+ var import_node_os6 = __toESM(require("os"));
11316
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"));
11317
11976
 
11318
11977
  // src/core/managed-host-ports.ts
11319
- var import_node_child_process19 = require("child_process");
11320
- var import_node_crypto17 = require("crypto");
11978
+ var import_node_child_process20 = require("child_process");
11979
+ var import_node_crypto18 = require("crypto");
11321
11980
  var PORT_LOOKUP_TIMEOUT_MS = 1e4;
11322
11981
  var PORT_LOOKUP_MAX_BUFFER = 64 * 1024;
11323
11982
  var LOOPBACK_BINDING = /^127\.0\.0\.1:(\d+)$/;
@@ -11325,7 +11984,7 @@ function unavailableEntry(declaration, reason2) {
11325
11984
  return { ...declaration, status: "unavailable", reason: reason2 };
11326
11985
  }
11327
11986
  function livePublishedBindings(containerId, target) {
11328
- 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`], {
11329
11988
  encoding: "utf-8",
11330
11989
  timeout: PORT_LOOKUP_TIMEOUT_MS,
11331
11990
  maxBuffer: PORT_LOOKUP_MAX_BUFFER,
@@ -11465,7 +12124,7 @@ function managedHostPortDeclarationsIdentity(declarations) {
11465
12124
  const keys2 = Object.keys(declarations).sort();
11466
12125
  if (keys2.length === 0) return void 0;
11467
12126
  const canonical = keys2.map((key) => [key, declarations[key].service, declarations[key].target]);
11468
- 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");
11469
12128
  }
11470
12129
  function managedHostPortVariables(report) {
11471
12130
  return Object.fromEntries(
@@ -11482,7 +12141,7 @@ function managedHostPortIdentity(report) {
11482
12141
  const entry = report[key];
11483
12142
  return [key, entry.status === "available" ? entry.endpoint?.port ?? null : entry.status];
11484
12143
  });
11485
- 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");
11486
12145
  }
11487
12146
  function assertManagedHostPortsAvailable(report) {
11488
12147
  const unavailable = Object.entries(report).filter(([, entry]) => entry.status === "unavailable").map(([key]) => key).sort();
@@ -11505,7 +12164,7 @@ function renderDeliveryScript(targetPath) {
11505
12164
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
11506
12165
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
11507
12166
  }
11508
- const temporaryPrefix = import_node_path34.default.posix.basename(targetPath);
12167
+ const temporaryPrefix = import_node_path35.default.posix.basename(targetPath);
11509
12168
  return `set -eu
11510
12169
  umask 077
11511
12170
  runtime_root=/tmp/devrouter
@@ -11529,7 +12188,7 @@ function commandFailure2(result) {
11529
12188
  return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
11530
12189
  }
11531
12190
  function deliverRuntimeFile(containerId, targetPath, contents, label) {
11532
- const result = (0, import_node_child_process20.spawnSync)(
12191
+ const result = (0, import_node_child_process21.spawnSync)(
11533
12192
  "docker",
11534
12193
  ["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
11535
12194
  { input: contents, encoding: "utf-8", ...networkDockerOptions() }
@@ -11540,32 +12199,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
11540
12199
  }
11541
12200
  }
11542
12201
  function readRegularFileBytes(filePath) {
11543
- if (!import_node_fs29.default.existsSync(filePath) || !import_node_fs29.default.lstatSync(filePath).isFile()) return void 0;
11544
- 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);
11545
12204
  }
11546
12205
  function readRegularFile(filePath) {
11547
12206
  return readRegularFileBytes(filePath)?.toString("utf-8");
11548
12207
  }
11549
12208
  function adapterFingerprint(adapter) {
11550
- return (0, import_node_crypto18.createHash)("sha256").update(adapter).digest("hex");
12209
+ return (0, import_node_crypto19.createHash)("sha256").update(adapter).digest("hex");
11551
12210
  }
11552
12211
  function resolveProcessHelperPath() {
11553
12212
  const candidates = [
11554
- import_node_path34.default.resolve(__dirname, "..", "bin", "devrouter-process"),
11555
- 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")
11556
12215
  ];
11557
- const helperPath = candidates.find((candidate) => import_node_fs29.default.existsSync(candidate));
12216
+ const helperPath = candidates.find((candidate) => import_node_fs30.default.existsSync(candidate));
11558
12217
  if (!helperPath) {
11559
12218
  throw new Error("Could not locate the packaged devrouter-process helper.");
11560
12219
  }
11561
12220
  return helperPath;
11562
12221
  }
11563
12222
  function resolveManagedPostStartPlan(repoPath) {
11564
- const adapterPath = import_node_path34.default.join(repoPath, MANAGED_ADAPTER_PATH);
12223
+ const adapterPath = import_node_path35.default.join(repoPath, MANAGED_ADAPTER_PATH);
11565
12224
  const adapterBytes = readRegularFileBytes(adapterPath);
11566
12225
  const adapterText = adapterBytes?.toString("utf-8");
11567
- const dockerfilePath = import_node_path34.default.join(repoPath, ".devcontainer", "Dockerfile");
11568
- 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");
11569
12228
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
11570
12229
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
11571
12230
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -11616,7 +12275,7 @@ function runManagedPostStart(options) {
11616
12275
  assertManagedHostPortsAvailable(hostPorts);
11617
12276
  const portEnvironment = managedHostPortVariables(hostPorts);
11618
12277
  const portIdentity = managedHostPortIdentity(hostPorts) ?? "";
11619
- const helper = import_node_fs29.default.readFileSync(resolveProcessHelperPath());
12278
+ const helper = import_node_fs30.default.readFileSync(resolveProcessHelperPath());
11620
12279
  deliverRuntimeFile(
11621
12280
  options.container.id,
11622
12281
  RUNTIME_HELPER_PATH,
@@ -11630,7 +12289,7 @@ function runManagedPostStart(options) {
11630
12289
  options.plan.adapterContents,
11631
12290
  "the managed post-start adapter"
11632
12291
  );
11633
- const started2 = (0, import_node_child_process20.spawnSync)(
12292
+ const started2 = (0, import_node_child_process21.spawnSync)(
11634
12293
  "docker",
11635
12294
  [
11636
12295
  "exec",
@@ -11676,7 +12335,7 @@ function assertSafeProcessName(name) {
11676
12335
  }
11677
12336
  function runManagedProcessAction(options) {
11678
12337
  assertSafeProcessName(options.name);
11679
- const result = (0, import_node_child_process20.spawnSync)(
12338
+ const result = (0, import_node_child_process21.spawnSync)(
11680
12339
  "docker",
11681
12340
  [
11682
12341
  "exec",
@@ -12482,62 +13141,96 @@ function stepReliability(state, event, nowMs) {
12482
13141
  }
12483
13142
 
12484
13143
  // src/core/reliability-worker.ts
12485
- var import_node_child_process21 = require("child_process");
12486
- var import_node_crypto19 = require("crypto");
12487
- var import_node_fs30 = __toESM(require("fs"));
12488
- var import_node_path35 = __toESM(require("path"));
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"));
12489
13148
 
12490
- // src/core/lifecycle-progress.ts
12491
- var ROLES = {
12492
- validation: "lifecycle-worker",
12493
- preparation: "repository-preparation",
12494
- provider: "provider-operation",
12495
- "service-start": "service-reconciliation",
12496
- "service-stop": "service-reconciliation",
12497
- "process-start": "repository-process-adapter",
12498
- "process-stop": "repository-process-adapter",
12499
- "route-publication": "lifecycle-worker",
12500
- "route-removal": "lifecycle-worker",
12501
- readiness: "lifecycle-worker",
12502
- rollback: "lifecycle-worker",
12503
- stop: "lifecycle-stop"
12504
- };
12505
- function isLifecycleProgressPhase(value) {
12506
- return typeof value === "string" && Object.hasOwn(ROLES, value);
13149
+ // src/core/lifecycle-diagnostics.ts
13150
+ var import_node_crypto20 = require("crypto");
13151
+ var import_node_fs31 = __toESM(require("fs"));
13152
+ var import_node_path36 = __toESM(require("path"));
13153
+ var import_node_util8 = require("util");
13154
+ var MAX_TAIL_BYTES = 32768;
13155
+ var MAX_RECEIPT_BYTES = 65536;
13156
+ function privateDirectory(directory) {
13157
+ const stat = import_node_fs31.default.lstatSync(directory);
13158
+ return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
13159
+ }
13160
+ function lifecycleDiagnosticsPath(identity2) {
13161
+ return import_node_path36.default.join(
13162
+ DEVROUTER_HOME,
13163
+ "lifecycle-diagnostics",
13164
+ `${(0, import_node_crypto20.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
13165
+ );
12507
13166
  }
12508
- var sender;
12509
- function installLifecycleProgressSender(send) {
12510
- let active = true;
12511
- let pending = false;
12512
- let latest;
12513
- const report = (phase) => {
12514
- if (!active || !isLifecycleProgressPhase(phase)) return;
12515
- if (pending) {
12516
- latest = phase;
12517
- return;
12518
- }
12519
- pending = true;
12520
- try {
12521
- send({ lifecycleProgress: phase }, () => {
12522
- pending = false;
12523
- const next = latest;
12524
- latest = void 0;
12525
- if (active && next) report(next);
12526
- });
12527
- } catch {
12528
- pending = false;
12529
- latest = void 0;
13167
+ function readReceipt(identity2) {
13168
+ let descriptor;
13169
+ try {
13170
+ const file = lifecycleDiagnosticsPath(identity2);
13171
+ if (!privateDirectory(import_node_path36.default.dirname(file))) return void 0;
13172
+ descriptor = import_node_fs31.default.openSync(
13173
+ file,
13174
+ import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NOFOLLOW | import_node_fs31.default.constants.O_NONBLOCK
13175
+ );
13176
+ const stat = import_node_fs31.default.fstatSync(descriptor);
13177
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
13178
+ return void 0;
13179
+ const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
13180
+ const size = import_node_fs31.default.readSync(descriptor, buffer, 0, buffer.length, 0);
13181
+ if (size > MAX_RECEIPT_BYTES) return void 0;
13182
+ const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
13183
+ const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
13184
+ const optionalTime = (value) => value === null || timestamp(value);
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)
13186
+ return void 0;
13187
+ let bytes2 = 0;
13188
+ for (const chunk of receipt.output) {
13189
+ if (!chunk || !["stdout", "stderr"].includes(chunk.stream) || typeof chunk.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(chunk.data))
13190
+ return void 0;
13191
+ bytes2 += Buffer.byteLength(chunk.data, "base64");
12530
13192
  }
12531
- };
12532
- sender = report;
12533
- return () => {
12534
- active = false;
12535
- latest = void 0;
12536
- if (sender === report) sender = void 0;
12537
- };
13193
+ return bytes2 <= MAX_TAIL_BYTES ? receipt : void 0;
13194
+ } catch {
13195
+ return void 0;
13196
+ } finally {
13197
+ if (descriptor !== void 0) import_node_fs31.default.closeSync(descriptor);
13198
+ }
12538
13199
  }
12539
- function reportLifecycleProgress(phase) {
12540
- sender?.(phase);
13200
+ function readLifecycleDiagnostics(identity2, now = Date.now()) {
13201
+ try {
13202
+ const before = readReliabilityOperation(identity2);
13203
+ const receipt = readReceipt(identity2);
13204
+ const after = readReliabilityOperation(identity2);
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(
13206
+ (time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
13207
+ ))
13208
+ return void 0;
13209
+ const age = (time) => time === null ? null : now - time;
13210
+ const metadata = {
13211
+ stage: receipt.phase ?? "unknown",
13212
+ elapsedMs: Math.max(0, (receipt.closed ? receipt.updatedAtMs : now) - receipt.startedAtMs),
13213
+ stageAgeMs: age(receipt.phaseAtMs),
13214
+ outputAgeMs: age(receipt.lastOutputAtMs),
13215
+ progressAgeMs: age(receipt.lastProgressAtMs),
13216
+ evidence: now - receipt.updatedAtMs < 3e4 ? "recent" : "stale",
13217
+ buildPackage: receipt.buildPackage,
13218
+ liveness: "unknown",
13219
+ executionDeadlineAtMs: null,
13220
+ handshakeTimeoutMs: 3e4,
13221
+ cancellationGraceMs: 2e3,
13222
+ cancellationRequested: receipt.cancellationRequested,
13223
+ workerDrained: after.state.operation.drained
13224
+ };
13225
+ return {
13226
+ operationId: receipt.operationId,
13227
+ revision: after.revision,
13228
+ metadata,
13229
+ output: receipt.output
13230
+ };
13231
+ } catch {
13232
+ return void 0;
13233
+ }
12541
13234
  }
12542
13235
 
12543
13236
  // src/core/reliability-worker.ts
@@ -12551,11 +13244,11 @@ function workerGroupAbsent(pid) {
12551
13244
  if (process.platform !== "linux") return false;
12552
13245
  let observedZombie = false;
12553
13246
  try {
12554
- for (const entry of import_node_fs30.default.readdirSync("/proc")) {
13247
+ for (const entry of import_node_fs32.default.readdirSync("/proc")) {
12555
13248
  if (!/^\d+$/.test(entry)) continue;
12556
13249
  let stat;
12557
13250
  try {
12558
- stat = import_node_fs30.default.readFileSync(`/proc/${entry}/stat`, "utf8");
13251
+ stat = import_node_fs32.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12559
13252
  } catch (error) {
12560
13253
  if (error.code === "ENOENT") continue;
12561
13254
  return false;
@@ -12575,20 +13268,20 @@ function workerGroupAbsent(pid) {
12575
13268
  }
12576
13269
 
12577
13270
  // src/core/workspace-ensure.ts
12578
- var import_node_child_process31 = require("child_process");
12579
- var import_node_fs35 = __toESM(require("fs"));
12580
- var import_node_path40 = __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"));
12581
13274
 
12582
13275
  // src/core/devpod-mutation.ts
12583
- var import_node_child_process22 = require("child_process");
12584
- var import_node_fs31 = __toESM(require("fs"));
12585
- var import_node_path36 = __toESM(require("path"));
12586
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path36.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");
12587
13280
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
12588
13281
  var DevpodStartPostconditionError = class extends Error {
12589
13282
  };
12590
13283
  function withMutationLock2(activity, target, operation) {
12591
- import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13284
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12592
13285
  return withFileLockSync(
12593
13286
  DEVPOD_MUTATION_LOCK_FILE,
12594
13287
  {
@@ -12608,7 +13301,7 @@ function runDevpodAction(action, devpodId, force = false, repoPath) {
12608
13301
  const args = action === "delete" ? [action, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action, devpodId];
12609
13302
  claimLifecycleEffect();
12610
13303
  const binding = repoPath ? networkProviderEffectOptions("devpod", devpodId, repoPath) : { args: [] };
12611
- 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], {
12612
13305
  encoding: "utf-8",
12613
13306
  ...binding.env ? { env: binding.env } : {}
12614
13307
  });
@@ -12688,7 +13381,8 @@ async function startDevpodWorkspace(options) {
12688
13381
  recreate: options.recreate,
12689
13382
  quiet: options.quiet,
12690
13383
  workspace: options.workspace,
12691
- prepareNetwork: options.prepareNetwork
13384
+ prepareNetwork: options.prepareNetwork,
13385
+ ...options.freshBootstrap ? { freshBootstrap: options.freshBootstrap } : {}
12692
13386
  });
12693
13387
  resetWorkspaceRuntimeCaches();
12694
13388
  return result;
@@ -12700,6 +13394,8 @@ async function startDevpodWorkspace(options) {
12700
13394
  throw error;
12701
13395
  }
12702
13396
  }
13397
+ if (options.freshBootstrap)
13398
+ throw new Error("Fresh bootstrap is qualified only for the Devsy Docker provider.");
12703
13399
  const activity = options.recreate ? "DevPod recreate" : "DevPod start";
12704
13400
  return withMutationLock2(activity, options.repoPath, () => {
12705
13401
  const workspaces = listDevpodWorkspaces(options.repoPath);
@@ -12749,7 +13445,7 @@ async function startDevpodWorkspace(options) {
12749
13445
  }
12750
13446
  claimLifecycleEffect();
12751
13447
  network?.beforeDispatch?.();
12752
- const result = (0, import_node_child_process22.spawnSync)("devpod", args, {
13448
+ const result = (0, import_node_child_process23.spawnSync)("devpod", args, {
12753
13449
  stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
12754
13450
  env
12755
13451
  });
@@ -12789,8 +13485,8 @@ async function startDevpodWorkspace(options) {
12789
13485
  }
12790
13486
 
12791
13487
  // src/core/host-port-claims.ts
12792
- var import_node_child_process23 = require("child_process");
12793
- var import_node_path37 = __toESM(require("path"));
13488
+ var import_node_child_process24 = require("child_process");
13489
+ var import_node_path39 = __toESM(require("path"));
12794
13490
  var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
12795
13491
  var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
12796
13492
  var DOCKER_TIMEOUT_MS = 1e4;
@@ -12893,7 +13589,7 @@ function hostPortBindingsConflict(desired, holder) {
12893
13589
  }
12894
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}}}';
12895
13591
  function runDocker(args) {
12896
- const result = (0, import_node_child_process23.spawnSync)("docker", args, {
13592
+ const result = (0, import_node_child_process24.spawnSync)("docker", args, {
12897
13593
  ...networkDockerOptions(),
12898
13594
  encoding: "utf-8",
12899
13595
  timeout: DOCKER_TIMEOUT_MS,
@@ -12963,7 +13659,7 @@ function listHolderSnapshots() {
12963
13659
  }
12964
13660
  function renderManagedComposeModel(plan, workspace) {
12965
13661
  const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
12966
- const result = (0, import_node_child_process23.spawnSync)(
13662
+ const result = (0, import_node_child_process24.spawnSync)(
12967
13663
  "docker",
12968
13664
  [
12969
13665
  "compose",
@@ -12998,8 +13694,8 @@ function renderManagedComposeModel(plan, workspace) {
12998
13694
  }
12999
13695
  }
13000
13696
  function attributeHolderWorktree(workingDir) {
13001
- if (import_node_path37.default.basename(workingDir) !== ".devcontainer") return void 0;
13002
- return comparableWorkspacePath(import_node_path37.default.dirname(workingDir));
13697
+ if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13698
+ return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13003
13699
  }
13004
13700
  function holderAttribution(holder, repoPath) {
13005
13701
  const attribution = {};
@@ -13034,7 +13730,7 @@ function detectHostPortClaimConflicts(options) {
13034
13730
  );
13035
13731
  if (desired.length === 0) return [];
13036
13732
  const holders = listHolderSnapshots();
13037
- const devcontainerDir = import_node_path37.default.join(options.repoPath, ".devcontainer");
13733
+ const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13038
13734
  const conflicts = [];
13039
13735
  for (const binding of desired) {
13040
13736
  for (const holder of holders) {
@@ -13058,7 +13754,7 @@ function detectHostPortClaimConflicts(options) {
13058
13754
  }
13059
13755
 
13060
13756
  // src/core/http-route-probe.ts
13061
- var import_node_child_process24 = require("child_process");
13757
+ var import_node_child_process25 = require("child_process");
13062
13758
  var DEFAULT_MAX_TIME_SECONDS = 5;
13063
13759
  var PARENT_TIMEOUT_PADDING_MS = 250;
13064
13760
  var CONTRACT_MAX_BUFFER = 1024;
@@ -13091,7 +13787,7 @@ function probeHttpRoute(host, options = {}) {
13091
13787
  args.push("--cacert", CERT_FILE);
13092
13788
  }
13093
13789
  args.push(url);
13094
- 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" });
13095
13791
  const stdout = result.stdout?.trim() ?? "";
13096
13792
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
13097
13793
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
@@ -13110,7 +13806,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
13110
13806
  }
13111
13807
  let result;
13112
13808
  try {
13113
- result = (0, import_node_child_process24.spawnSync)("curl", args, {
13809
+ result = (0, import_node_child_process25.spawnSync)("curl", args, {
13114
13810
  encoding: "utf-8",
13115
13811
  maxBuffer: CONTRACT_MAX_BUFFER,
13116
13812
  timeout: Math.ceil(parentTimeoutMs)
@@ -13194,8 +13890,8 @@ function transportFailure() {
13194
13890
  }
13195
13891
 
13196
13892
  // src/core/managed-host-preparation.ts
13197
- var import_node_child_process25 = require("child_process");
13198
- var import_node_fs32 = __toESM(require("fs"));
13893
+ var import_node_child_process26 = require("child_process");
13894
+ var import_node_fs34 = __toESM(require("fs"));
13199
13895
  var PREPARATION_TIMEOUT_MS = 6e4;
13200
13896
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
13201
13897
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -13206,7 +13902,7 @@ function runPreparationCommand(argv, cwd) {
13206
13902
  return new Promise((resolve) => {
13207
13903
  let child;
13208
13904
  try {
13209
- 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), {
13210
13906
  cwd,
13211
13907
  detached: true,
13212
13908
  shell: false,
@@ -13238,7 +13934,7 @@ function runPreparationCommand(argv, cwd) {
13238
13934
  }
13239
13935
  function readConfigBytes(configPath) {
13240
13936
  try {
13241
- return import_node_fs32.default.readFileSync(configPath);
13937
+ return import_node_fs34.default.readFileSync(configPath);
13242
13938
  } catch {
13243
13939
  return void 0;
13244
13940
  }
@@ -13271,8 +13967,45 @@ async function runManagedHostPreparation(repoPath, config) {
13271
13967
  }
13272
13968
  }
13273
13969
 
13970
+ // src/core/lifecycle-operation-status.ts
13971
+ function projectLifecycleOperation(operation) {
13972
+ const result = {
13973
+ operationId: operation.id,
13974
+ phase: "terminal",
13975
+ outcome: null,
13976
+ reason: null,
13977
+ exitCode: null
13978
+ };
13979
+ switch (operation.status) {
13980
+ case "NOT_STARTED":
13981
+ if (operation.drained) result.outcome = "NOT_STARTED";
13982
+ else result.phase = "queued";
13983
+ break;
13984
+ case "DISPATCH_PENDING":
13985
+ case "DISPATCH_RECORDED":
13986
+ case "RUNNING":
13987
+ if (operation.drained) {
13988
+ result.outcome = "COMPLETION_UNKNOWN";
13989
+ result.reason = "worker-drained-without-result";
13990
+ } else result.phase = operation.status === "RUNNING" ? "running" : "dispatching";
13991
+ break;
13992
+ case "COMPLETED":
13993
+ result.outcome = "COMPLETED";
13994
+ result.exitCode = operation.exitCode;
13995
+ break;
13996
+ case "NOT_LAUNCHED":
13997
+ result.outcome = "NOT_STARTED";
13998
+ break;
13999
+ case "INTERRUPTED":
14000
+ case "COMPLETION_UNKNOWN":
14001
+ result.outcome = operation.status;
14002
+ break;
14003
+ }
14004
+ return result;
14005
+ }
14006
+
13274
14007
  // src/core/managed-mount-nesting.ts
13275
- var import_node_child_process26 = require("child_process");
14008
+ var import_node_child_process27 = require("child_process");
13276
14009
  var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
13277
14010
  var UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
13278
14011
  function normalizeDestination(value) {
@@ -13345,7 +14078,7 @@ function classifyManagedMountNesting(configured, effectiveDestinations, containe
13345
14078
  };
13346
14079
  }
13347
14080
  function readContainerMountinfo(containerId, options) {
13348
- 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"], {
13349
14082
  encoding: "utf-8",
13350
14083
  timeout: options.timeoutMs ?? 5e3,
13351
14084
  stdio: ["ignore", "pipe", "pipe"],
@@ -13423,6 +14156,8 @@ function readReliabilityStatus(options) {
13423
14156
  return void 0;
13424
14157
  }
13425
14158
  if (!record5) return void 0;
14159
+ const diagnosticReceipt = record5.state.operation ? readLifecycleDiagnostics(record5.identity) : void 0;
14160
+ const diagnostics = diagnosticReceipt?.operationId === record5.state.operation?.id && diagnosticReceipt?.revision === record5.revision ? diagnosticReceipt?.metadata : void 0;
13426
14161
  const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
13427
14162
  return {
13428
14163
  desired: record5.state.desired,
@@ -13433,6 +14168,14 @@ function readReliabilityStatus(options) {
13433
14168
  correctiveActionsTaken: record5.state.incident.correctiveActionsTaken,
13434
14169
  actionLimit: record5.state.incident.actionLimit
13435
14170
  } : null,
14171
+ ...record5.state.operation ? {
14172
+ operation: {
14173
+ ...projectLifecycleOperation(record5.state.operation),
14174
+ kind: record5.state.operation.kind,
14175
+ drained: record5.state.operation.drained,
14176
+ ...diagnostics ? { diagnostics } : {}
14177
+ }
14178
+ } : {},
13436
14179
  ...reason2 ? {
13437
14180
  attention: {
13438
14181
  reason: reason2,
@@ -13880,7 +14623,7 @@ function collectManagedRuntimeStatus(options) {
13880
14623
  }
13881
14624
 
13882
14625
  // src/core/network-inventory.ts
13883
- var import_node_child_process27 = require("child_process");
14626
+ var import_node_child_process28 = require("child_process");
13884
14627
  var NetworkInventoryError = class extends Error {
13885
14628
  };
13886
14629
  var MAX_IDS = 4096;
@@ -13892,7 +14635,7 @@ function readDocker(endpoint, args, timeoutMs) {
13892
14635
  const env = { ...process.env };
13893
14636
  delete env.DOCKER_CONTEXT;
13894
14637
  delete env.DOCKER_HOST;
13895
- 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], {
13896
14639
  env,
13897
14640
  encoding: "utf-8",
13898
14641
  timeout: timeoutMs,
@@ -14056,37 +14799,37 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
14056
14799
  }
14057
14800
 
14058
14801
  // src/core/network-routes.ts
14059
- var import_node_child_process28 = require("child_process");
14802
+ var import_node_child_process29 = require("child_process");
14060
14803
  var import_node_net3 = require("net");
14061
14804
 
14062
14805
  // src/core/network-local-daemon.ts
14063
- var import_node_fs33 = __toESM(require("fs"));
14064
- var import_node_path38 = __toESM(require("path"));
14806
+ var import_node_fs35 = __toESM(require("fs"));
14807
+ var import_node_path40 = __toESM(require("path"));
14065
14808
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14066
14809
  const deadline = Date.now() + 2e3;
14067
14810
  try {
14068
14811
  if (!endpoint.startsWith("unix:///")) return false;
14069
14812
  const socket = endpoint.slice("unix://".length);
14070
- const table = import_node_fs33.default.readFileSync(import_node_path38.default.join(procRoot, "net/unix"), "utf8");
14813
+ const table = import_node_fs35.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
14071
14814
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14072
14815
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14073
14816
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14074
14817
  const inode = `socket:[${rows2[0][6]}]`;
14075
- const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path38.default.join(procRoot, "self/ns/net"));
14076
- const pids = import_node_fs33.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));
14077
14820
  if (pids.length > 4096) return false;
14078
14821
  for (const pid of pids) {
14079
14822
  if (Date.now() > deadline) return false;
14080
14823
  try {
14081
- const root = import_node_path38.default.join(procRoot, pid);
14082
- if (import_node_path38.default.basename(import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "exe"))) !== "dockerd") continue;
14083
- if (import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "ns/net")) !== ownNamespace) continue;
14084
- const descriptors = import_node_fs33.default.readdirSync(import_node_path38.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"));
14085
14828
  if (descriptors.length > 4096) continue;
14086
14829
  if (descriptors.some((fd) => {
14087
14830
  if (Date.now() > deadline) return false;
14088
14831
  try {
14089
- return import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "fd", fd)) === inode;
14832
+ return import_node_fs35.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
14090
14833
  } catch {
14091
14834
  return false;
14092
14835
  }
@@ -14125,7 +14868,7 @@ function parseLinuxNetworkRoutes(output) {
14125
14868
  function collectNetworkRoutes(options) {
14126
14869
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
14127
14870
  return { status: "unknown", routes: [] };
14128
- 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"], {
14129
14872
  encoding: "utf8",
14130
14873
  timeout: 5e3,
14131
14874
  maxBuffer: 1024 * 1024
@@ -14161,10 +14904,10 @@ function inspectLegacyNetworkCapacity(input) {
14161
14904
  }
14162
14905
 
14163
14906
  // src/core/network-managed.ts
14164
- var import_node_crypto22 = require("crypto");
14907
+ var import_node_crypto24 = require("crypto");
14165
14908
 
14166
14909
  // src/core/network-compose.ts
14167
- var import_node_crypto20 = require("crypto");
14910
+ var import_node_crypto22 = require("crypto");
14168
14911
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
14169
14912
  function isRecord5(value) {
14170
14913
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -14970,19 +15713,19 @@ function stableValue(value) {
14970
15713
  }
14971
15714
  function fingerprintComposeNetworkModel(input) {
14972
15715
  const projection = normalizedFingerprintProjection(input);
14973
- return (0, import_node_crypto20.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
15716
+ return (0, import_node_crypto22.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
14974
15717
  }
14975
15718
 
14976
15719
  // src/core/network-compose-files.ts
14977
- var import_node_child_process29 = require("child_process");
14978
- var import_node_crypto21 = require("crypto");
14979
- var import_node_fs34 = __toESM(require("fs"));
14980
- var import_node_path39 = __toESM(require("path"));
14981
- 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"));
14982
15725
  var OVERLAY = "docker-compose.devrouter-network.yml";
14983
15726
  var MARKER = "# devrouter:managed workspace network";
14984
15727
  var MAX_BYTES2 = 2 * 1024 * 1024;
14985
- function object3(value) {
15728
+ function object4(value) {
14986
15729
  if (!value || typeof value !== "object" || Array.isArray(value))
14987
15730
  throw new Error("Network Compose evidence is malformed.");
14988
15731
  return value;
@@ -14990,20 +15733,20 @@ function object3(value) {
14990
15733
  function inspectNetworkComposeFiles(input) {
14991
15734
  const authoredNetworks = {};
14992
15735
  for (const file of input.plan.composeFiles) {
14993
- const stat = import_node_fs34.default.lstatSync(file);
15736
+ const stat = import_node_fs36.default.lstatSync(file);
14994
15737
  if (!stat.isFile() || stat.size > MAX_BYTES2)
14995
15738
  throw new Error("Network Compose source is unavailable or too large.");
14996
15739
  let source;
14997
15740
  try {
14998
- source = object3(import_yaml4.default.parse(import_node_fs34.default.readFileSync(file, "utf8")));
15741
+ source = object4(import_yaml5.default.parse(import_node_fs36.default.readFileSync(file, "utf8")));
14999
15742
  } catch {
15000
15743
  throw new Error("Network Compose source could not be parsed safely.");
15001
15744
  }
15002
15745
  if (source.networks !== void 0) {
15003
- for (const [name, declaration] of Object.entries(object3(source.networks))) {
15746
+ for (const [name, declaration] of Object.entries(object4(source.networks))) {
15004
15747
  authoredNetworks[name] = {
15005
15748
  ...authoredNetworks[name],
15006
- ...declaration === null ? {} : object3(declaration)
15749
+ ...declaration === null ? {} : object4(declaration)
15007
15750
  };
15008
15751
  }
15009
15752
  }
@@ -15011,7 +15754,7 @@ function inspectNetworkComposeFiles(input) {
15011
15754
  const env = managedComposeEnvironment(input.workspace);
15012
15755
  delete env.DOCKER_CONTEXT;
15013
15756
  delete env.DOCKER_HOST;
15014
- const result = (0, import_node_child_process29.spawnSync)(
15757
+ const result = (0, import_node_child_process30.spawnSync)(
15015
15758
  "docker",
15016
15759
  [
15017
15760
  "--host",
@@ -15037,7 +15780,7 @@ function inspectNetworkComposeFiles(input) {
15037
15780
  if (result.error || result.status !== 0)
15038
15781
  throw new Error("Complete network Compose evidence is unavailable.");
15039
15782
  try {
15040
- return { compose: object3(JSON.parse(result.stdout)), authoredNetworks };
15783
+ return { compose: object4(JSON.parse(result.stdout)), authoredNetworks };
15041
15784
  } catch {
15042
15785
  throw new Error("Network Compose evidence is malformed.");
15043
15786
  }
@@ -15045,8 +15788,8 @@ function inspectNetworkComposeFiles(input) {
15045
15788
  function prepareNetworkComposeFiles(plan, subnet) {
15046
15789
  const overlay = deriveComposeNetworkOverlay(subnet);
15047
15790
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
15048
- const overlayPath = import_node_path39.default.join(plan.composeDirectory, OVERLAY);
15049
- 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", [
15050
15793
  "-C",
15051
15794
  plan.composeDirectory,
15052
15795
  "check-ignore",
@@ -15058,11 +15801,11 @@ function prepareNetworkComposeFiles(plan, subnet) {
15058
15801
  if (ignored.status !== 0)
15059
15802
  throw new Error("Generated network overlay must be ignored before allocation.");
15060
15803
  const bytes2 = `${MARKER}
15061
- ${import_yaml4.default.stringify(overlay.overlay)}`;
15804
+ ${import_yaml5.default.stringify(overlay.overlay)}`;
15062
15805
  const assertOwnership = () => {
15063
- if (!import_node_fs34.default.existsSync(overlayPath)) return;
15064
- const stat = import_node_fs34.default.lstatSync(overlayPath);
15065
- if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs34.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)
15066
15809
  throw new Error("Retained network overlay changed; repair is required.");
15067
15810
  };
15068
15811
  assertOwnership();
@@ -15076,7 +15819,7 @@ ${JSON.stringify(extended.config, null, 2)}
15076
15819
  ...plan,
15077
15820
  contents,
15078
15821
  composeFiles: [...plan.composeFiles, overlayPath],
15079
- effectiveConfigSha256: (0, import_node_crypto21.createHash)("sha256").update(contents).digest("hex")
15822
+ effectiveConfigSha256: (0, import_node_crypto23.createHash)("sha256").update(contents).digest("hex")
15080
15823
  };
15081
15824
  return {
15082
15825
  plan: result,
@@ -15088,7 +15831,7 @@ ${JSON.stringify(extended.config, null, 2)}
15088
15831
  }
15089
15832
 
15090
15833
  // src/core/network-connected-route.ts
15091
- var import_node_child_process30 = require("child_process");
15834
+ var import_node_child_process31 = require("child_process");
15092
15835
  function bounds(cidr) {
15093
15836
  const canonical = parseIPv4Cidr(cidr);
15094
15837
  if (!canonical) throw new Error("Invalid connected route.");
@@ -15113,7 +15856,7 @@ function inspectClaimedBridge(claim2, networkId) {
15113
15856
  const env = { ...process.env };
15114
15857
  delete env.DOCKER_CONTEXT;
15115
15858
  delete env.DOCKER_HOST;
15116
- const result = (0, import_node_child_process30.spawnSync)(
15859
+ const result = (0, import_node_child_process31.spawnSync)(
15117
15860
  "docker",
15118
15861
  [
15119
15862
  "--host",
@@ -15140,7 +15883,7 @@ function inspectClaimedBridge(claim2, networkId) {
15140
15883
  const [first, last] = bounds(claim2.subnet);
15141
15884
  const [address] = bounds(gateway);
15142
15885
  if (address <= first || address >= last) throw new Error();
15143
- 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], {
15144
15887
  encoding: "utf8",
15145
15888
  timeout: 5e3,
15146
15889
  maxBuffer: 65536
@@ -15394,7 +16137,7 @@ function createManagedNetworkSession(input) {
15394
16137
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
15395
16138
  );
15396
16139
  const authority = readNetworkOperationAuthority(identity2);
15397
- const fingerprint = (0, import_node_crypto22.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");
15398
16141
  if (!saved) {
15399
16142
  if (policy.status !== "valid")
15400
16143
  throw new Error("New network allocation requires valid machine policy.");
@@ -15578,11 +16321,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
15578
16321
  var POLL_INTERVAL_MS = 1e3;
15579
16322
  function assertOverlay(container, repoPath) {
15580
16323
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15581
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))) {
16324
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))) {
15582
16325
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
15583
16326
  }
15584
16327
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
15585
- const expectedOverlay = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16328
+ const expectedOverlay = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15586
16329
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
15587
16330
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
15588
16331
  }
@@ -15596,7 +16339,7 @@ function assertWorktreeOwnership(container, repoPath) {
15596
16339
  function isInsideWorktree(candidate, repoPath) {
15597
16340
  const normalizedCandidate = comparableWorkspacePath(candidate);
15598
16341
  const normalizedRoot = comparableWorkspacePath(repoPath);
15599
- return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path40.default.sep}`);
16342
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path42.default.sep}`);
15600
16343
  }
15601
16344
  function assertReady(container, label) {
15602
16345
  if (!container.state.Running) {
@@ -15788,7 +16531,7 @@ function hasExactManagedComposeProject(repoPath, state) {
15788
16531
  }).some((container) => {
15789
16532
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15790
16533
  return Boolean(
15791
- workingDir && sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))
16534
+ workingDir && sameWorkspacePath(workingDir, import_node_path42.default.join(repoPath, ".devcontainer"))
15792
16535
  );
15793
16536
  });
15794
16537
  } catch {
@@ -15896,7 +16639,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
15896
16639
  target
15897
16640
  });
15898
16641
  if (target.kind === "linked") {
15899
- const workspaceEnv = (0, import_node_child_process31.spawnSync)(
16642
+ const workspaceEnv = (0, import_node_child_process32.spawnSync)(
15900
16643
  "docker",
15901
16644
  ["exec", appContainer.id, "printenv", "WORKSPACE"],
15902
16645
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -15906,7 +16649,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
15906
16649
  `Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
15907
16650
  );
15908
16651
  }
15909
- const devrouterWorkspaceEnv = (0, import_node_child_process31.spawnSync)(
16652
+ const devrouterWorkspaceEnv = (0, import_node_child_process32.spawnSync)(
15910
16653
  "docker",
15911
16654
  ["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
15912
16655
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -15917,7 +16660,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
15917
16660
  );
15918
16661
  }
15919
16662
  }
15920
- const gitCheck = (0, import_node_child_process31.spawnSync)(
16663
+ const gitCheck = (0, import_node_child_process32.spawnSync)(
15921
16664
  "docker",
15922
16665
  [
15923
16666
  "exec",
@@ -16193,14 +16936,14 @@ function resolvePrimaryTarget(repoPath) {
16193
16936
  }
16194
16937
  function isPrimaryCheckout(repoPath) {
16195
16938
  try {
16196
- return import_node_fs35.default.statSync(import_node_path40.default.join(repoPath, ".git")).isDirectory();
16939
+ return import_node_fs37.default.statSync(import_node_path42.default.join(repoPath, ".git")).isDirectory();
16197
16940
  } catch {
16198
16941
  return false;
16199
16942
  }
16200
16943
  }
16201
16944
  function openUrls(urls) {
16202
16945
  for (const url of urls) {
16203
- 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" });
16204
16947
  if (opened.status !== 0) {
16205
16948
  process.stderr.write(`Warning: could not open '${url}'.
16206
16949
  `);
@@ -16230,6 +16973,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16230
16973
  let repairMutationStarted = false;
16231
16974
  let retainedRepairContainers = [];
16232
16975
  let environmentStarted = false;
16976
+ let freshBootstrapPending = false;
16233
16977
  let managedPlan;
16234
16978
  let previousManagedState;
16235
16979
  let managedComposeProject;
@@ -16256,9 +17000,21 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16256
17000
  reportLifecycleProgress("validation");
16257
17001
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
16258
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
+ }
16259
17015
  if (target.kind === "linked") {
16260
- const overlayPath = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16261
- if (!import_node_fs35.default.existsSync(overlayPath)) {
17016
+ const overlayPath = import_node_path42.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
17017
+ if (!import_node_fs37.default.existsSync(overlayPath)) {
16262
17018
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
16263
17019
  }
16264
17020
  }
@@ -16278,6 +17034,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16278
17034
  if (variant !== "default" && managedPostStart.kind !== "runtime")
16279
17035
  throw new Error("Runtime variants require a managed runtime post-start adapter.");
16280
17036
  const managedRuntime = runtime.config.managedRuntime;
17037
+ const hostPortDeclarationsSha256 = managedHostPortDeclarationsIdentity(
17038
+ managedRuntime?.devcontainer.hostPorts ?? {}
17039
+ );
16281
17040
  if (options.repair && !managedRuntime)
16282
17041
  throw new Error("Repair requires managedRuntime configuration.");
16283
17042
  previousRoutes = listHostRouteState().filter(
@@ -16365,7 +17124,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16365
17124
  }
16366
17125
  const apps = proxyAppsFromConfig(runtime.config);
16367
17126
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
16368
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path40.default.basename(repoPath)) ?? "app";
17127
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path42.default.basename(repoPath)) ?? "app";
16369
17128
  for (const [index, app] of apps.entries()) {
16370
17129
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
16371
17130
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -16431,6 +17190,25 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16431
17190
  managedConfigWritten = true;
16432
17191
  }
16433
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;
16434
17212
  const currentTarget = () => target.kind === "linked" ? target : { ...target, devpodId };
16435
17213
  const startAndProveAttachment = async (recreate = false) => {
16436
17214
  reportLifecycleProgress("provider");
@@ -16443,6 +17221,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16443
17221
  ...network ? { prepareNetwork: network.prepare } : {},
16444
17222
  recreate,
16445
17223
  quiet: options.quiet,
17224
+ ...freshBootstrap ? { freshBootstrap } : {},
16446
17225
  ...requestedTarget.kind === "linked" ? {
16447
17226
  workspace: {
16448
17227
  token: requestedTarget.workspace,
@@ -16451,6 +17230,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16451
17230
  } : {}
16452
17231
  });
16453
17232
  environmentStarted = true;
17233
+ freshBootstrapPending = false;
16454
17234
  } catch (error) {
16455
17235
  if (error instanceof DevpodStartPostconditionError) environmentStarted = true;
16456
17236
  throw error;
@@ -16491,7 +17271,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16491
17271
  transitionPhase = "service-start";
16492
17272
  reportLifecycleProgress("service-start");
16493
17273
  claimLifecycleEffect();
16494
- 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)], {
16495
17275
  ...networkDockerOptions(),
16496
17276
  encoding: "utf-8",
16497
17277
  timeout: DEFAULT_READINESS_TIMEOUT_MS,
@@ -16602,9 +17382,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16602
17382
  composeProject: captureProject,
16603
17383
  profile: runtime.profile,
16604
17384
  variant,
16605
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16606
- managedRuntime?.devcontainer.hostPorts ?? {}
16607
- ),
17385
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16608
17386
  desired: {
16609
17387
  apps: apps.map((app) => app.name).sort(),
16610
17388
  services: [...capturePlan.desiredProfileServices].sort(),
@@ -16815,9 +17593,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16815
17593
  composeProject: managedComposeProject,
16816
17594
  profile: runtime.profile,
16817
17595
  variant,
16818
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16819
- managedRuntime?.devcontainer.hostPorts ?? {}
16820
- ),
17596
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16821
17597
  desired: {
16822
17598
  apps: apps.map((app) => app.name).sort(),
16823
17599
  services: [...managedPlan.desiredProfileServices].sort(),
@@ -16958,6 +17734,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16958
17734
  `${original} Reset candidate retained for recovery.${failures.length ? ` Recovery incomplete: ${failures.join("; ")}.` : " State is degraded."}`
16959
17735
  );
16960
17736
  }
17737
+ if (freshBootstrapPending) throw error;
16961
17738
  if (options.repair && !repairMutationStarted) throw error;
16962
17739
  if (options.repair && repairMutationStarted && previousManagedState && managedPlan) {
16963
17740
  const failures = [];
@@ -17254,9 +18031,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
17254
18031
  }
17255
18032
  const repairLocked = () => {
17256
18033
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
17257
- import_node_fs35.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
18034
+ import_node_fs37.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17258
18035
  return withFileLock(
17259
- import_node_path40.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
18036
+ import_node_path42.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17260
18037
  {
17261
18038
  activity: "Managed runtime repair",
17262
18039
  target: repoPath,
@@ -17434,7 +18211,7 @@ function preflightRuntimeVariant(repoPath, options) {
17434
18211
  return variant;
17435
18212
  }
17436
18213
  function capacityPolicyTargets(repoPath) {
17437
- const policy = readCapacityPolicy(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
18214
+ const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
17438
18215
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
17439
18216
  }
17440
18217
  function clearActiveLifecycleStartupWitness() {
@@ -17444,6 +18221,40 @@ function clearActiveLifecycleStartupWitness() {
17444
18221
  if (record5?.startupWitness?.operationId !== request.operationId) return;
17445
18222
  clearStartupWitness(request.identity, reliabilityFence(record5.state));
17446
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
+ }
17447
18258
  async function acquireWorkerLock(repoPath, operation) {
17448
18259
  const deadline = Date.now() + 3e4;
17449
18260
  for (; ; ) {
@@ -17540,7 +18351,7 @@ async function executeLifecycleWorker(request, operation) {
17540
18351
  const owned = containers.filter(
17541
18352
  (container) => sameWorkspacePath(
17542
18353
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17543
- import_node_path41.default.join(request.repoPath, ".devcontainer")
18354
+ import_node_path43.default.join(request.repoPath, ".devcontainer")
17544
18355
  )
17545
18356
  );
17546
18357
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -17627,7 +18438,7 @@ function proveLifecycleStopped() {
17627
18438
  throw new Error("Full stop proof requires an explicit stop worker.");
17628
18439
  const settle = () => {
17629
18440
  claimLifecycleEffect();
17630
- if (initialStopAbsence && !(0, import_node_util7.isDeepStrictEqual)(
18441
+ if (initialStopAbsence && !(0, import_node_util9.isDeepStrictEqual)(
17631
18442
  proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
17632
18443
  initialStopAbsence
17633
18444
  ))
@@ -17647,7 +18458,7 @@ function proveLifecycleStopped() {
17647
18458
  const remaining = inspectWorkspaceContainers().filter(
17648
18459
  (container) => sameWorkspacePath(
17649
18460
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17650
- import_node_path41.default.join(request.repoPath, ".devcontainer")
18461
+ import_node_path43.default.join(request.repoPath, ".devcontainer")
17651
18462
  )
17652
18463
  );
17653
18464
  if (remaining.some(
@@ -17677,7 +18488,7 @@ function proveLifecycleStopped() {
17677
18488
  return void 0;
17678
18489
  });
17679
18490
  if (settlement) {
17680
- const capacity = createLifecycleCapacityStore(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
18491
+ const capacity = createLifecycleCapacityStore(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
17681
18492
  for (let attempt = 0; ; attempt++) {
17682
18493
  try {
17683
18494
  const state = capacity.inspect();
@@ -17756,7 +18567,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
17756
18567
  const launch = () => {
17757
18568
  if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
17758
18569
  claimLifecycleEffect();
17759
- return (0, import_node_child_process32.spawn)("devsy", args, { stdio: "inherit" });
18570
+ return (0, import_node_child_process33.spawn)("devsy", args, { stdio: "inherit" });
17760
18571
  };
17761
18572
  child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
17762
18573
  } catch (error) {
@@ -17804,7 +18615,7 @@ function ensureGuidance2(repoPath) {
17804
18615
  return `Run 'devrouter ensure ${repoPath}' first.`;
17805
18616
  }
17806
18617
  function assertRunningDevpod(devpodId, repoPath) {
17807
- 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"], {
17808
18619
  encoding: "utf-8"
17809
18620
  });
17810
18621
  if (result.status !== 0) {
@@ -17851,7 +18662,7 @@ async function devpodExecOutcome(repoPath, command) {
17851
18662
  }
17852
18663
  assertRunningDevpod(devpod.id, repoPath);
17853
18664
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
17854
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto24.randomUUID)()}__:`;
18665
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto26.randomUUID)()}__:`;
17855
18666
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
17856
18667
  const literalCommand = command.map(quotePosixArg).join(" ");
17857
18668
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -17983,7 +18794,7 @@ async function devpodExecOutcome(repoPath, command) {
17983
18794
  let child;
17984
18795
  try {
17985
18796
  claimLifecycleEffect();
17986
- 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"] });
17987
18798
  } catch (error) {
17988
18799
  settled = true;
17989
18800
  reject(
@@ -18040,12 +18851,12 @@ async function devpodExecOutcome(repoPath, command) {
18040
18851
  }
18041
18852
 
18042
18853
  // src/core/workspace-lifecycle.ts
18043
- var import_node_child_process34 = require("child_process");
18044
- var import_node_fs37 = __toESM(require("fs"));
18045
- var import_node_path42 = __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"));
18046
18857
 
18047
18858
  // src/core/route-state.ts
18048
- var import_node_fs36 = __toESM(require("fs"));
18859
+ var import_node_fs38 = __toESM(require("fs"));
18049
18860
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
18050
18861
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
18051
18862
  }