@devrouter/cli 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/core/devpod-exec.ts
26
- var import_node_child_process29 = require("child_process");
26
+ var import_node_child_process30 = require("child_process");
27
27
  var import_node_crypto23 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
@@ -1164,7 +1164,11 @@ function listDevpodWorkspacesRaw(options = {}) {
1164
1164
  }
1165
1165
  if (result.error || result.status !== 0) {
1166
1166
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
1167
- throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
1167
+ const failure = new Error(
1168
+ `devpod list failed: ${details || "devpod is not installed or unavailable"}`
1169
+ );
1170
+ if (result.error) failure.code = result.error.code;
1171
+ throw failure;
1168
1172
  }
1169
1173
  let parsed;
1170
1174
  try {
@@ -1303,9 +1307,11 @@ function listDevsyWorkspaces() {
1303
1307
  );
1304
1308
  if (result.status !== 0) {
1305
1309
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
1306
- throw new Error(
1310
+ const failure = new Error(
1307
1311
  `devsy workspace list failed: ${details || "devsy is not installed or unavailable"}`
1308
1312
  );
1313
+ if (result.error) failure.code = result.error.code;
1314
+ throw failure;
1309
1315
  }
1310
1316
  let parsed;
1311
1317
  try {
@@ -1775,25 +1781,23 @@ function readWorkspaceRuntimeConfig() {
1775
1781
  }
1776
1782
  return config;
1777
1783
  }
1784
+ function readRegistrySnapshot(runtime, read2, snapshots) {
1785
+ try {
1786
+ return read2();
1787
+ } catch (error) {
1788
+ if (error?.code === "ENOENT") return void 0;
1789
+ snapshots.unavailable.push(runtime);
1790
+ snapshots.errors[runtime] = error instanceof Error ? error.message : String(error);
1791
+ return void 0;
1792
+ }
1793
+ }
1778
1794
  function getWorkspaceRegistrySnapshots() {
1779
1795
  if (cachedSnapshots) return cachedSnapshots;
1780
1796
  const snapshots = { unavailable: [], errors: {} };
1781
- if (isRuntimeInstalled("devsy")) {
1782
- try {
1783
- snapshots.devsy = listDevsyWorkspaces();
1784
- } catch (error) {
1785
- snapshots.unavailable.push("devsy");
1786
- snapshots.errors.devsy = error instanceof Error ? error.message : String(error);
1787
- }
1788
- }
1789
- if (isRuntimeInstalled("devpod")) {
1790
- try {
1791
- snapshots.devpod = listDevpodWorkspacesRaw();
1792
- } catch (error) {
1793
- snapshots.unavailable.push("devpod");
1794
- snapshots.errors.devpod = error instanceof Error ? error.message : String(error);
1795
- }
1796
- }
1797
+ const devsy = readRegistrySnapshot("devsy", listDevsyWorkspaces, snapshots);
1798
+ if (devsy !== void 0) snapshots.devsy = devsy;
1799
+ const devpod = readRegistrySnapshot("devpod", listDevpodWorkspacesRaw, snapshots);
1800
+ if (devpod !== void 0) snapshots.devpod = devpod;
1797
1801
  cachedSnapshots = snapshots;
1798
1802
  return snapshots;
1799
1803
  }
@@ -1954,7 +1958,7 @@ function selectDevpodWorkspace(workspaces, repoPath) {
1954
1958
  }
1955
1959
 
1956
1960
  // src/core/devsy-exec.ts
1957
- var import_node_child_process28 = require("child_process");
1961
+ var import_node_child_process29 = require("child_process");
1958
1962
 
1959
1963
  // src/core/devsy-exec-proof.ts
1960
1964
  var import_node_fs7 = __toESM(require("fs"));
@@ -4040,11 +4044,19 @@ function absentRegistrationIdentity(state) {
4040
4044
  resetWorkspaceRuntimeCaches();
4041
4045
  if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
4042
4046
  throw new Error("Stop provider changed.");
4043
- if (!state.workspace || !isLinkedWorktree(state.repoPath))
4044
- throw new Error("Absent stop requires a linked workspace.");
4045
- const record5 = readWorkspaceOwnership(state.repoPath, state.workspace);
4046
- if (!record5 || record5.workspace !== state.workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
4047
- throw new Error("Absent stop workspace ownership changed.");
4047
+ const linked = isLinkedWorktree(state.repoPath);
4048
+ if (linked !== (state.workspace !== void 0))
4049
+ throw new Error("Absent stop requires the exact workspace identity.");
4050
+ let identity2;
4051
+ if (linked) {
4052
+ const workspace = state.workspace;
4053
+ const record5 = readWorkspaceOwnership(state.repoPath, workspace);
4054
+ if (!record5 || record5.workspace !== workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
4055
+ throw new Error("Absent stop workspace ownership changed.");
4056
+ identity2 = { kind: "linked", record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
4057
+ } else {
4058
+ identity2 = { kind: "primary", repoPath: state.repoPath, devpodId: state.devpodId };
4059
+ }
4048
4060
  for (const entries of [
4049
4061
  listDevsyWorkspaces(),
4050
4062
  listDevpodWorkspacesRaw({ allowMissingExecutable: true })
@@ -4054,7 +4066,7 @@ function absentRegistrationIdentity(state) {
4054
4066
  ))
4055
4067
  throw new Error("Absent stop requires both provider registrations to remain absent.");
4056
4068
  }
4057
- return { record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
4069
+ return identity2;
4058
4070
  }
4059
4071
  function replacementObservation(state) {
4060
4072
  resetWorkspaceRuntimeCaches();
@@ -4164,7 +4176,7 @@ function proveManagedStop(state) {
4164
4176
  const identityKeys = ["context", "providerId", "uid", "sourcePath", "sourceContainer"];
4165
4177
  const current = identityOf(owner.workspace);
4166
4178
  const changed = identityKeys.filter((key) => baseline[key] !== current[key]);
4167
- if (changed.length === 0 && state.workspace !== void 0 && isLinkedWorktree(state.repoPath) && inspectManagedStopContainers(baseline.project, baseline.endpoint).length === 0) {
4179
+ if (changed.length === 0 && inspectManagedStopContainers(baseline.project, baseline.endpoint).length === 0) {
4168
4180
  proveAbsentPopulationStop(state, baseline, current);
4169
4181
  return { status: "proven-absent", containers: [] };
4170
4182
  }
@@ -5361,7 +5373,7 @@ function retain(session, previous) {
5361
5373
 
5362
5374
  // src/core/reliability-operation-store.ts
5363
5375
  function installedCliVersion() {
5364
- return true ? "0.1.2" : "0.0.0-dev";
5376
+ return true ? "0.1.3" : "0.0.0-dev";
5365
5377
  }
5366
5378
  function compareCliVersions(left, right) {
5367
5379
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -5501,6 +5513,7 @@ function validate2(record5, identity2) {
5501
5513
  "revision",
5502
5514
  "state",
5503
5515
  "worker",
5516
+ "stopWorkStarted",
5504
5517
  "effectSequence",
5505
5518
  "outcome",
5506
5519
  "result",
@@ -5682,6 +5695,9 @@ function validate2(record5, identity2) {
5682
5695
  throw new Error("Reliability worker identity is invalid.");
5683
5696
  }
5684
5697
  }
5698
+ if (record5.stopWorkStarted !== void 0 && record5.stopWorkStarted !== null) {
5699
+ if (record5.stopWorkStarted !== true) throw new Error("Invalid stop work marker.");
5700
+ }
5685
5701
  }
5686
5702
  var ReliabilityJournalError = class extends Error {
5687
5703
  constructor(cause, location, message) {
@@ -7294,7 +7310,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
7294
7310
  const config = parseConfig(parsed ?? {}, configPath);
7295
7311
  const requiredVersion = config.devrouter?.version;
7296
7312
  if (requiredVersion && !hasWarnedVersionMismatch) {
7297
- const cliVersion = true ? "0.1.2" : "0.0.0-dev";
7313
+ const cliVersion = true ? "0.1.3" : "0.0.0-dev";
7298
7314
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7299
7315
  hasWarnedVersionMismatch = true;
7300
7316
  process.stderr.write(
@@ -8372,6 +8388,41 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
8372
8388
  throw new Error("Initial managed stop left a workload running.");
8373
8389
  return true;
8374
8390
  }
8391
+ function restoreRecordedManagedDevcontainerConfig(repoPath) {
8392
+ const plan = recordedManagedDevcontainerRestorePlan(repoPath);
8393
+ if (!plan) return false;
8394
+ writeManagedDevcontainerConfig(plan);
8395
+ if (inspectManagedDevcontainerGeneratedConfig(plan).status !== "valid") {
8396
+ throw new Error(
8397
+ `Managed Dev Container path '${plan.generatedPath}' could not be restored from the recorded state.`
8398
+ );
8399
+ }
8400
+ return true;
8401
+ }
8402
+ function recordedManagedDevcontainerRestorePlan(repoPath) {
8403
+ try {
8404
+ if (!import_node_fs21.default.existsSync(import_node_path25.default.join(repoPath, ".devrouter.yml"))) return void 0;
8405
+ const linked = isLinkedWorktree(repoPath);
8406
+ const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
8407
+ const state = readManagedRuntimeState(repoPath, workspace);
8408
+ if (!state) return void 0;
8409
+ const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
8410
+ if (!runtime.config.managedRuntime || runtime.profile !== state.profile || runtime.workspace !== workspace)
8411
+ return void 0;
8412
+ const plan = inspectManagedDevcontainerConfig({
8413
+ repoPath,
8414
+ config: runtime.config,
8415
+ profile: runtime.resolvedProfile,
8416
+ linked
8417
+ });
8418
+ if (inspectManagedDevcontainerGeneratedConfig(plan).status !== "missing") return void 0;
8419
+ if (plan.sourceConfigSha256 !== state.sourceConfigSha256 || plan.effectiveConfigSha256 !== state.effectiveConfigSha256)
8420
+ return void 0;
8421
+ return plan;
8422
+ } catch {
8423
+ return void 0;
8424
+ }
8425
+ }
8375
8426
  function stopRetainedManagedDevsyWorkspace(options) {
8376
8427
  const { repoPath, devsyId } = options;
8377
8428
  const linked = isLinkedWorktree(repoPath);
@@ -10127,6 +10178,7 @@ function inspectExactOwnership(devsyId, worktreePath) {
10127
10178
  }
10128
10179
  function mutateOwnedDevsyWorkspace(action, devsyId, worktreePath) {
10129
10180
  return withMutationLock(`Devsy ${action}`, worktreePath, () => {
10181
+ restoreRecordedManagedDevcontainerConfig(worktreePath);
10130
10182
  if (action === "stop") {
10131
10183
  const stopped = stopRetainedManagedDevsyWorkspace({
10132
10184
  repoPath: worktreePath,
@@ -10647,7 +10699,7 @@ function handleOperationRequest(state, event) {
10647
10699
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
10648
10700
  return blocked(
10649
10701
  state,
10650
- `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish.`
10702
+ `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish, or when no stop worker is running rerun 'devrouter stop <path> --delete', removing the exact provider registration first when the stop cannot prove the recorded population.`
10651
10703
  );
10652
10704
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
10653
10705
  return blocked(
@@ -11174,7 +11226,7 @@ function workerGroupAbsent(pid) {
11174
11226
  }
11175
11227
 
11176
11228
  // src/core/workspace-ensure.ts
11177
- var import_node_child_process27 = require("child_process");
11229
+ var import_node_child_process28 = require("child_process");
11178
11230
  var import_node_fs34 = __toESM(require("fs"));
11179
11231
  var import_node_path39 = __toESM(require("path"));
11180
11232
 
@@ -12082,6 +12134,96 @@ function runManagedProcessAction(options) {
12082
12134
  return "drifted";
12083
12135
  }
12084
12136
 
12137
+ // src/core/managed-mount-nesting.ts
12138
+ var import_node_child_process23 = require("child_process");
12139
+ var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
12140
+ var UNREADABLE_MOUNT_REASON = "the container's own mount table could not be read";
12141
+ function normalizeDestination(value) {
12142
+ const trimmed = value.trim().replace(/\/+$/, "");
12143
+ return trimmed === "" ? "/" : trimmed;
12144
+ }
12145
+ function isNestedWithin(destination, candidate) {
12146
+ return candidate === "/" ? destination !== "/" : destination.startsWith(`${candidate}/`);
12147
+ }
12148
+ function unescapeMountField(value) {
12149
+ return value.replace(
12150
+ /\\([0-7]{3})/g,
12151
+ (_match, octal) => String.fromCharCode(Number.parseInt(octal, 8))
12152
+ );
12153
+ }
12154
+ function parseMountinfoDestinations(text4) {
12155
+ const destinations = /* @__PURE__ */ new Set();
12156
+ for (const line of text4.split("\n")) {
12157
+ const fields2 = line.split(" ");
12158
+ if (fields2.length < 5 || !/^\d+$/.test(fields2[0] ?? "")) continue;
12159
+ destinations.add(normalizeDestination(unescapeMountField(fields2[4] ?? "")));
12160
+ }
12161
+ return [...destinations];
12162
+ }
12163
+ function isNestedIndex(destinations, index) {
12164
+ return destinations.some(
12165
+ (candidate, other) => other !== index && isNestedWithin(destinations[index] ?? "", candidate)
12166
+ );
12167
+ }
12168
+ function configuredDestinations(configured) {
12169
+ return configured.map((mount) => normalizeDestination(mount.Destination));
12170
+ }
12171
+ function classifyManagedMountNesting(configured, effectiveDestinations, containerId) {
12172
+ const destinations = configuredDestinations(configured);
12173
+ const nested = configured.filter((_mount, index) => isNestedIndex(destinations, index));
12174
+ const identity2 = containerId ? { container: containerId } : {};
12175
+ if (nested.length === 0) {
12176
+ return { status: "not-applicable", checked: [], unwound: [], reason: NESTED_MOUNT_REASON };
12177
+ }
12178
+ if (effectiveDestinations === null) {
12179
+ return {
12180
+ status: "unverified",
12181
+ checked: [],
12182
+ unwound: [],
12183
+ ...identity2,
12184
+ reason: UNREADABLE_MOUNT_REASON
12185
+ };
12186
+ }
12187
+ const effective = new Set(effectiveDestinations.map(normalizeDestination));
12188
+ const checked = [];
12189
+ const unwound = [];
12190
+ for (const mount of nested) {
12191
+ const destination = normalizeDestination(mount.Destination);
12192
+ if (effective.has(destination)) {
12193
+ checked.push(mount.Destination);
12194
+ continue;
12195
+ }
12196
+ const nestedWithin = destinations.filter((candidate) => isNestedWithin(destination, candidate)).sort((a, b) => a.length - b.length)[0];
12197
+ unwound.push({
12198
+ destination: mount.Destination,
12199
+ source: mount.Source,
12200
+ nestedWithin: nestedWithin ?? "/"
12201
+ });
12202
+ }
12203
+ return {
12204
+ status: unwound.length > 0 ? "unwound" : "effective",
12205
+ checked,
12206
+ unwound,
12207
+ ...identity2
12208
+ };
12209
+ }
12210
+ function readContainerMountinfo(containerId, options) {
12211
+ const result = (0, import_node_child_process23.spawnSync)("docker", ["exec", containerId, "cat", "/proc/self/mountinfo"], {
12212
+ encoding: "utf-8",
12213
+ timeout: options.timeoutMs ?? 5e3,
12214
+ stdio: ["ignore", "pipe", "pipe"],
12215
+ ...networkDockerOptions()
12216
+ });
12217
+ if (result.status !== 0 || typeof result.stdout !== "string") return null;
12218
+ const destinations = parseMountinfoDestinations(result.stdout);
12219
+ return destinations.length > 0 ? destinations : null;
12220
+ }
12221
+ function observeManagedMountNesting(container, options = {}) {
12222
+ const destinations = configuredDestinations(container.mounts);
12223
+ const effective = destinations.some((_destination, index) => isNestedIndex(destinations, index)) ? readContainerMountinfo(container.id, options) : null;
12224
+ return classifyManagedMountNesting(container.mounts, effective, container.id);
12225
+ }
12226
+
12085
12227
  // src/core/managed-runtime-status.ts
12086
12228
  function recoveryPath(repoPath) {
12087
12229
  if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(repoPath)) return repoPath;
@@ -12362,6 +12504,7 @@ function inspectManagedRuntime(options) {
12362
12504
  (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
12363
12505
  )?.Destination;
12364
12506
  const primaryActive = Boolean(primary?.state.Running);
12507
+ const mountNesting = primary && primaryActive ? observeManagedMountNesting(primary) : void 0;
12365
12508
  if (generatedConfigMissing && (state || primaryActive || managedServicesActive)) {
12366
12509
  drift.push("managed generated Dev Container configuration is missing");
12367
12510
  }
@@ -12428,7 +12571,8 @@ function inspectManagedRuntime(options) {
12428
12571
  processStatuses,
12429
12572
  activeServices: sortedUnique(activeServices),
12430
12573
  activeProcesses: sortedUnique(activeProcesses),
12431
- drift
12574
+ drift,
12575
+ ...mountNesting ? { mountNesting } : {}
12432
12576
  };
12433
12577
  }
12434
12578
  function collectManagedRuntimeStatus(options) {
@@ -12576,6 +12720,7 @@ function collectManagedRuntimeStatus(options) {
12576
12720
  baseServiceStatuses: inspection.baseStatuses,
12577
12721
  processStatuses: inspection.processStatuses,
12578
12722
  drift: sortedUnique(drift),
12723
+ ...inspection.mountNesting ? { mountNesting: inspection.mountNesting } : {},
12579
12724
  ...inspection.plan?.sourceConfigSha256 || state?.sourceConfigSha256 ? { sourceConfigSha256: inspection.plan?.sourceConfigSha256 ?? state?.sourceConfigSha256 } : {},
12580
12725
  ...inspection.plan?.effectiveConfigSha256 || state?.effectiveConfigSha256 ? {
12581
12726
  effectiveConfigSha256: inspection.plan?.effectiveConfigSha256 ?? state?.effectiveConfigSha256
@@ -12585,19 +12730,19 @@ function collectManagedRuntimeStatus(options) {
12585
12730
  }
12586
12731
 
12587
12732
  // src/core/network-inventory.ts
12588
- var import_node_child_process23 = require("child_process");
12733
+ var import_node_child_process24 = require("child_process");
12589
12734
  var NetworkInventoryError = class extends Error {
12590
12735
  };
12591
12736
  var MAX_IDS = 4096;
12592
12737
  var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
12593
12738
  var FULL_ID = /^[a-f0-9]{64}$/;
12594
- var NETWORK_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"driver":{{json .Driver}},"ipam":{{json .IPAM.Config}},"activeEndpoints":{{len .Containers}},"project":{{json (index .Labels "com.docker.compose.project")}},"network":{{json (index .Labels "com.docker.compose.network")}}';
12739
+ var NETWORK_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"driver":{{json .Driver}},"ipam":{{json .IPAM.Config}},"activeEndpoints":{{len .Containers}},"project":{{json (index .Labels "com.docker.compose.project")}},"network":{{json (index .Labels "com.docker.compose.network")}}}';
12595
12740
  var CONTAINER_TEMPLATE = '{"id":{{json .Id}},"networks":{{json .NetworkSettings.Networks}}}';
12596
12741
  function readDocker(endpoint, args, timeoutMs) {
12597
12742
  const env = { ...process.env };
12598
12743
  delete env.DOCKER_CONTEXT;
12599
12744
  delete env.DOCKER_HOST;
12600
- const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
12745
+ const result = (0, import_node_child_process24.spawnSync)("docker", ["--host", endpoint, ...args], {
12601
12746
  env,
12602
12747
  encoding: "utf-8",
12603
12748
  timeout: timeoutMs,
@@ -12628,7 +12773,14 @@ function id4(value) {
12628
12773
  }
12629
12774
  function rows(output) {
12630
12775
  const trimmed = output.trim();
12631
- return trimmed ? trimmed.split(/\r?\n/).map((line) => JSON.parse(line)) : [];
12776
+ return trimmed ? trimmed.split(/\r?\n/).map((line) => parseRecord(line)) : [];
12777
+ }
12778
+ function parseRecord(line) {
12779
+ try {
12780
+ return JSON.parse(line);
12781
+ } catch {
12782
+ throw new NetworkInventoryError("Docker network inventory contains a malformed record.");
12783
+ }
12632
12784
  }
12633
12785
  function ids(output) {
12634
12786
  const values = output.trim() ? output.trim().split(/\r?\n/).map(id4) : [];
@@ -12673,7 +12825,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
12673
12825
  return output;
12674
12826
  };
12675
12827
  const info = record4(
12676
- JSON.parse(
12828
+ parseRecord(
12677
12829
  read2(["info", "--format", '{"id":{{json .ID}},"pools":{{json .DefaultAddressPools}}}'])
12678
12830
  )
12679
12831
  );
@@ -12754,7 +12906,7 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
12754
12906
  }
12755
12907
 
12756
12908
  // src/core/network-routes.ts
12757
- var import_node_child_process24 = require("child_process");
12909
+ var import_node_child_process25 = require("child_process");
12758
12910
  var import_node_net3 = require("net");
12759
12911
 
12760
12912
  // src/core/network-local-daemon.ts
@@ -12823,7 +12975,7 @@ function parseLinuxNetworkRoutes(output) {
12823
12975
  function collectNetworkRoutes(options) {
12824
12976
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
12825
12977
  return { status: "unknown", routes: [] };
12826
- const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
12978
+ const result = (0, import_node_child_process25.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
12827
12979
  encoding: "utf8",
12828
12980
  timeout: 5e3,
12829
12981
  maxBuffer: 1024 * 1024
@@ -13672,7 +13824,7 @@ function fingerprintComposeNetworkModel(input) {
13672
13824
  }
13673
13825
 
13674
13826
  // src/core/network-compose-files.ts
13675
- var import_node_child_process25 = require("child_process");
13827
+ var import_node_child_process26 = require("child_process");
13676
13828
  var import_node_crypto20 = require("crypto");
13677
13829
  var import_node_fs33 = __toESM(require("fs"));
13678
13830
  var import_node_path38 = __toESM(require("path"));
@@ -13709,7 +13861,7 @@ function inspectNetworkComposeFiles(input) {
13709
13861
  const env = managedComposeEnvironment(input.workspace);
13710
13862
  delete env.DOCKER_CONTEXT;
13711
13863
  delete env.DOCKER_HOST;
13712
- const result = (0, import_node_child_process25.spawnSync)(
13864
+ const result = (0, import_node_child_process26.spawnSync)(
13713
13865
  "docker",
13714
13866
  [
13715
13867
  "--host",
@@ -13744,7 +13896,7 @@ function prepareNetworkComposeFiles(plan, subnet) {
13744
13896
  const overlay = deriveComposeNetworkOverlay(subnet);
13745
13897
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
13746
13898
  const overlayPath = import_node_path38.default.join(plan.composeDirectory, OVERLAY);
13747
- const ignored = (0, import_node_child_process25.spawnSync)("git", [
13899
+ const ignored = (0, import_node_child_process26.spawnSync)("git", [
13748
13900
  "-C",
13749
13901
  plan.composeDirectory,
13750
13902
  "check-ignore",
@@ -13786,7 +13938,7 @@ ${JSON.stringify(extended.config, null, 2)}
13786
13938
  }
13787
13939
 
13788
13940
  // src/core/network-connected-route.ts
13789
- var import_node_child_process26 = require("child_process");
13941
+ var import_node_child_process27 = require("child_process");
13790
13942
  function bounds(cidr) {
13791
13943
  const canonical = parseIPv4Cidr(cidr);
13792
13944
  if (!canonical) throw new Error("Invalid connected route.");
@@ -13811,7 +13963,7 @@ function inspectClaimedBridge(claim2, networkId) {
13811
13963
  const env = { ...process.env };
13812
13964
  delete env.DOCKER_CONTEXT;
13813
13965
  delete env.DOCKER_HOST;
13814
- const result = (0, import_node_child_process26.spawnSync)(
13966
+ const result = (0, import_node_child_process27.spawnSync)(
13815
13967
  "docker",
13816
13968
  [
13817
13969
  "--host",
@@ -13838,7 +13990,7 @@ function inspectClaimedBridge(claim2, networkId) {
13838
13990
  const [first, last] = bounds(claim2.subnet);
13839
13991
  const [address] = bounds(gateway);
13840
13992
  if (address <= first || address >= last) throw new Error();
13841
- const interfaces = (0, import_node_child_process26.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
13993
+ const interfaces = (0, import_node_child_process27.spawnSync)("ip", ["-j", "-d", "-4", "address", "show", "dev", bridge], {
13842
13994
  encoding: "utf8",
13843
13995
  timeout: 5e3,
13844
13996
  maxBuffer: 65536
@@ -14594,7 +14746,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
14594
14746
  target
14595
14747
  });
14596
14748
  if (target.kind === "linked") {
14597
- const workspaceEnv = (0, import_node_child_process27.spawnSync)(
14749
+ const workspaceEnv = (0, import_node_child_process28.spawnSync)(
14598
14750
  "docker",
14599
14751
  ["exec", appContainer.id, "printenv", "WORKSPACE"],
14600
14752
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -14604,7 +14756,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
14604
14756
  `Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
14605
14757
  );
14606
14758
  }
14607
- const devrouterWorkspaceEnv = (0, import_node_child_process27.spawnSync)(
14759
+ const devrouterWorkspaceEnv = (0, import_node_child_process28.spawnSync)(
14608
14760
  "docker",
14609
14761
  ["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
14610
14762
  { encoding: "utf-8", ...networkDockerOptions() }
@@ -14615,7 +14767,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
14615
14767
  );
14616
14768
  }
14617
14769
  }
14618
- const gitCheck = (0, import_node_child_process27.spawnSync)(
14770
+ const gitCheck = (0, import_node_child_process28.spawnSync)(
14619
14771
  "docker",
14620
14772
  [
14621
14773
  "exec",
@@ -14895,7 +15047,7 @@ function isPrimaryCheckout(repoPath) {
14895
15047
  }
14896
15048
  function openUrls(urls) {
14897
15049
  for (const url of urls) {
14898
- const opened = (0, import_node_child_process27.spawnSync)("open", [url], { encoding: "utf-8" });
15050
+ const opened = (0, import_node_child_process28.spawnSync)("open", [url], { encoding: "utf-8" });
14899
15051
  if (opened.status !== 0) {
14900
15052
  process.stderr.write(`Warning: could not open '${url}'.
14901
15053
  `);
@@ -15178,7 +15330,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
15178
15330
  transitionPhase = "service-start";
15179
15331
  reportLifecycleProgress("service-start");
15180
15332
  claimLifecycleEffect();
15181
- const result = (0, import_node_child_process27.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
15333
+ const result = (0, import_node_child_process28.spawnSync)("docker", ["start", ...stopped.map((entry) => entry.id)], {
15182
15334
  ...networkDockerOptions(),
15183
15335
  encoding: "utf-8",
15184
15336
  timeout: DEFAULT_READINESS_TIMEOUT_MS,
@@ -16122,6 +16274,11 @@ async function executeLifecycleWorker(request, operation) {
16122
16274
  ];
16123
16275
  for (const project of stopProjects) inspectManagedStopContainers(project);
16124
16276
  }
16277
+ updateReliabilityOperation(request.identity, (record5) => {
16278
+ if (!matchesFence(record5, request.fence))
16279
+ throw new Error("Lifecycle intent changed before stop work began.");
16280
+ record5.stopWorkStarted = true;
16281
+ });
16125
16282
  } else {
16126
16283
  updateReliabilityOperation(request.identity, (record5) => {
16127
16284
  stepRecord(record5, {
@@ -16233,6 +16390,7 @@ function proveLifecycleStopped() {
16233
16390
  const settlement = updateReliabilityOperation(request.identity, (record5) => {
16234
16391
  if (!matchesFence(record5, request.fence) || record5.worker)
16235
16392
  throw new Error("Stop proof was superseded or an earlier worker remains.");
16393
+ delete record5.stopWorkStarted;
16236
16394
  if (record5.version === 2) {
16237
16395
  if (record5.capacity) record5.capacity.validUntilMs = 0;
16238
16396
  return record5.state.environmentId;
@@ -16271,6 +16429,7 @@ function proveLifecycleStopped() {
16271
16429
  if (!matchesFence(record5, request.fence) || record5.worker || record5.state.environmentId !== settlement || record5.capacity && record5.capacity.validUntilMs !== 0)
16272
16430
  throw new Error("Capacity settlement was superseded before journal confirmation.");
16273
16431
  record5.capacity = null;
16432
+ delete record5.stopWorkStarted;
16274
16433
  if (record5.enrollment) {
16275
16434
  record5.phaseSettlement = null;
16276
16435
  record5.activeProfile = null;
@@ -16324,7 +16483,7 @@ async function devsyExecOutcome(repoPath, command, retainedProof) {
16324
16483
  const launch = () => {
16325
16484
  if (retainedProof) revalidateDevsyExecProof(repoPath, retainedProof);
16326
16485
  claimLifecycleEffect();
16327
- return (0, import_node_child_process28.spawn)("devsy", args, { stdio: "inherit" });
16486
+ return (0, import_node_child_process29.spawn)("devsy", args, { stdio: "inherit" });
16328
16487
  };
16329
16488
  child = retainedProof ? withMutationLock("Execute retained workspace", repoPath, launch) : launch();
16330
16489
  } catch (error) {
@@ -16372,7 +16531,7 @@ function ensureGuidance2(repoPath) {
16372
16531
  return `Run 'devrouter ensure ${repoPath}' first.`;
16373
16532
  }
16374
16533
  function assertRunningDevpod(devpodId, repoPath) {
16375
- const result = (0, import_node_child_process29.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
16534
+ const result = (0, import_node_child_process30.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
16376
16535
  encoding: "utf-8"
16377
16536
  });
16378
16537
  if (result.status !== 0) {
@@ -16551,7 +16710,7 @@ async function devpodExecOutcome(repoPath, command) {
16551
16710
  let child;
16552
16711
  try {
16553
16712
  claimLifecycleEffect();
16554
- child = (0, import_node_child_process29.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
16713
+ child = (0, import_node_child_process30.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
16555
16714
  } catch (error) {
16556
16715
  settled = true;
16557
16716
  reject(
@@ -16608,7 +16767,7 @@ async function devpodExecOutcome(repoPath, command) {
16608
16767
  }
16609
16768
 
16610
16769
  // src/core/workspace-lifecycle.ts
16611
- var import_node_child_process30 = require("child_process");
16770
+ var import_node_child_process31 = require("child_process");
16612
16771
  var import_node_fs36 = __toESM(require("fs"));
16613
16772
  var import_node_path41 = __toESM(require("path"));
16614
16773