@devrouter/cli 0.0.60 → 0.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/devrouter.js CHANGED
@@ -630,9 +630,9 @@ function tryReclaimStaleLock(lockPath, staleLinkPath, livenessCache) {
630
630
  throw error;
631
631
  }
632
632
  try {
633
- const record3 = parseLockRecord(import_node_fs3.default.readFileSync(fd, "utf-8"));
634
- if (record3.owner && isLockOwnerLive(record3.owner, livenessCache)) {
635
- return { kind: "live", pid: record3.owner.pid, acquiredAtMs: record3.acquiredAtMs };
633
+ const record4 = parseLockRecord(import_node_fs3.default.readFileSync(fd, "utf-8"));
634
+ if (record4.owner && isLockOwnerLive(record4.owner, livenessCache)) {
635
+ return { kind: "live", pid: record4.owner.pid, acquiredAtMs: record4.acquiredAtMs };
636
636
  }
637
637
  const staleStat = import_node_fs3.default.fstatSync(fd);
638
638
  if (staleStat.nlink !== 1) {
@@ -724,12 +724,12 @@ function acquireFileLock(lockPath, options) {
724
724
  continue;
725
725
  }
726
726
  const acquiredAtMs = Date.now();
727
- const record3 = `${owner}:${acquiredAtMs}`;
728
- import_node_fs3.default.writeFileSync(candidatePath, `${record3}
727
+ const record4 = `${owner}:${acquiredAtMs}`;
728
+ import_node_fs3.default.writeFileSync(candidatePath, `${record4}
729
729
  `, "utf-8");
730
730
  try {
731
731
  import_node_fs3.default.linkSync(candidatePath, lockPath);
732
- return record3;
732
+ return record4;
733
733
  } catch (error) {
734
734
  if (error.code !== "EEXIST") {
735
735
  throw error;
@@ -1350,11 +1350,11 @@ function listHostRouteState() {
1350
1350
  function upsertHostRoute(input2) {
1351
1351
  return withStateLock(() => {
1352
1352
  const routes = readHostRouteStateLocked();
1353
- const id2 = buildHostRouteId(input2.repoPath, input2.name);
1354
- const existing = routes.find((route) => route.id === id2);
1353
+ const id3 = buildHostRouteId(input2.repoPath, input2.name);
1354
+ const existing = routes.find((route) => route.id === id3);
1355
1355
  const now = (/* @__PURE__ */ new Date()).toISOString();
1356
1356
  const next = hostRouteFromInput(input2, existing, now);
1357
- const remaining = routes.filter((route) => route.id !== id2);
1357
+ const remaining = routes.filter((route) => route.id !== id3);
1358
1358
  remaining.push(next);
1359
1359
  remaining.sort((a, b) => a.name.localeCompare(b.name) || a.repoPath.localeCompare(b.repoPath));
1360
1360
  writeState(remaining);
@@ -1388,10 +1388,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
1388
1388
  }
1389
1389
  const now = (/* @__PURE__ */ new Date()).toISOString();
1390
1390
  const replacements = inputs.map((input2) => {
1391
- const id2 = buildHostRouteId(input2.repoPath, input2.name);
1391
+ const id3 = buildHostRouteId(input2.repoPath, input2.name);
1392
1392
  return hostRouteFromInput(
1393
1393
  input2,
1394
- routes.find((route) => route.id === id2),
1394
+ routes.find((route) => route.id === id3),
1395
1395
  now
1396
1396
  );
1397
1397
  });
@@ -1402,10 +1402,10 @@ function replaceHostRoutesForRepo(repoPath, inputs) {
1402
1402
  return replacements;
1403
1403
  });
1404
1404
  }
1405
- function removeHostRouteById(id2) {
1405
+ function removeHostRouteById(id3) {
1406
1406
  return withStateLock(() => {
1407
1407
  const routes = readHostRouteStateLocked();
1408
- const next = routes.filter((route) => route.id !== id2);
1408
+ const next = routes.filter((route) => route.id !== id3);
1409
1409
  if (next.length === routes.length) {
1410
1410
  return false;
1411
1411
  }
@@ -2450,7 +2450,7 @@ function loadRepoConfig(repoPath, read = (file) => import_node_fs6.default.readF
2450
2450
  const config = parseConfig(parsed ?? {}, configPath);
2451
2451
  const requiredVersion = config.devrouter?.version;
2452
2452
  if (requiredVersion && !hasWarnedVersionMismatch) {
2453
- const cliVersion = true ? "0.0.60" : "0.0.0-dev";
2453
+ const cliVersion = true ? "0.0.62" : "0.0.0-dev";
2454
2454
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
2455
2455
  hasWarnedVersionMismatch = true;
2456
2456
  process.stderr.write(
@@ -3424,29 +3424,29 @@ var init_reliability_contract = __esm({
3424
3424
  });
3425
3425
 
3426
3426
  // src/core/reliability-operation-store.ts
3427
- function identityKey(identity) {
3428
- if (!import_node_path10.default.isAbsolute(identity.repoPath) || import_node_path10.default.resolve(identity.repoPath) !== identity.repoPath || identity.repoPath.includes("\0") || identity.workspace !== null && !isReliabilityId(identity.workspace) || !["devpod", "devsy"].includes(identity.provider)) {
3427
+ function identityKey(identity2) {
3428
+ if (!import_node_path10.default.isAbsolute(identity2.repoPath) || import_node_path10.default.resolve(identity2.repoPath) !== identity2.repoPath || identity2.repoPath.includes("\0") || identity2.workspace !== null && !isReliabilityId(identity2.workspace) || !["devpod", "devsy"].includes(identity2.provider)) {
3429
3429
  throw new Error("Invalid reliability workspace identity.");
3430
3430
  }
3431
- return (0, import_node_crypto6.createHash)("sha256").update(identity.repoPath).digest("hex");
3431
+ return (0, import_node_crypto6.createHash)("sha256").update(identity2.repoPath).digest("hex");
3432
3432
  }
3433
- function reliabilityOperationPath(identity) {
3434
- return import_node_path10.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity)}.json`);
3433
+ function reliabilityOperationPath(identity2) {
3434
+ return import_node_path10.default.join(DEVROUTER_HOME, "reliability", `${identityKey(identity2)}.json`);
3435
3435
  }
3436
3436
  function keys(value, expected) {
3437
3437
  if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !expected.includes(key))) {
3438
3438
  throw new Error("Reliability record contains unsupported fields.");
3439
3439
  }
3440
3440
  }
3441
- function validate(record3, identity) {
3442
- keys(record3, ["version", "identity", "revision", "state", "worker", "effectSequence", "outcome"]);
3443
- keys(record3.identity, ["repoPath", "workspace", "provider"]);
3444
- if (record3.version !== 1 || record3.identity.repoPath !== identity.repoPath || record3.identity.workspace !== identity.workspace || record3.identity.provider !== identity.provider || !isReliabilityCounter(record3.revision) || !isReliabilityCounter(record3.effectSequence)) {
3441
+ function validate(record4, identity2) {
3442
+ keys(record4, ["version", "identity", "revision", "state", "worker", "effectSequence", "outcome"]);
3443
+ keys(record4.identity, ["repoPath", "workspace", "provider"]);
3444
+ if (record4.version !== 1 || record4.identity.repoPath !== identity2.repoPath || record4.identity.workspace !== identity2.workspace || record4.identity.provider !== identity2.provider || !isReliabilityCounter(record4.revision) || !isReliabilityCounter(record4.effectSequence)) {
3445
3445
  throw new Error("Reliability record has invalid version, ownership, or counters.");
3446
3446
  }
3447
- const state = record3.state;
3447
+ const state = record4.state;
3448
3448
  assertReliabilityState(state);
3449
- if (state.environmentId !== identityKey(identity) || state.executionPolicy !== "manual") {
3449
+ if (state.environmentId !== identityKey(identity2) || state.executionPolicy !== "manual") {
3450
3450
  throw new Error("Reliability record does not grant manual authority for this workspace.");
3451
3451
  }
3452
3452
  keys(state, [
@@ -3490,24 +3490,24 @@ function validate(record3, identity) {
3490
3490
  throw new Error("Reliability operation and retained result disagree.");
3491
3491
  }
3492
3492
  }
3493
- if (record3.outcome !== null) {
3494
- keys(record3.outcome, ["operationId", "status", "exitCode", "transport"]);
3495
- keys(record3.outcome.transport, ["exitCode", "signal"]);
3496
- const outcome = record3.outcome;
3493
+ if (record4.outcome !== null) {
3494
+ keys(record4.outcome, ["operationId", "status", "exitCode", "transport"]);
3495
+ keys(record4.outcome.transport, ["exitCode", "signal"]);
3496
+ const outcome = record4.outcome;
3497
3497
  const validExit = (value) => Number.isInteger(value) && Number(value) >= 0 && Number(value) <= 255;
3498
3498
  if (outcome.operationId !== state.operation?.id || !["completed", "not-started", "completion-unknown"].includes(outcome.status) || (outcome.status === "completed" ? !validExit(outcome.exitCode) : outcome.exitCode !== null) || outcome.transport.exitCode !== null && !validExit(outcome.transport.exitCode) || outcome.transport.signal !== null && (typeof outcome.transport.signal !== "string" || !/^SIG[A-Z0-9]{1,16}$/.test(outcome.transport.signal))) {
3499
3499
  throw new Error("Reliability execution outcome is invalid.");
3500
3500
  }
3501
3501
  }
3502
- if (record3.worker !== null) {
3503
- keys(record3.worker, ["id", "operationId", "pid", "birth"]);
3504
- if (!isReliabilityId(record3.worker.id) || !isReliabilityId(record3.worker.operationId) || typeof record3.worker.birth !== "string" || !/^(proc|ps):[a-zA-Z0-9]+$/.test(record3.worker.birth) || !isReliabilityCounter(record3.worker.pid) || record3.worker.pid === 0 || record3.worker.operationId !== state.operation?.id) {
3502
+ if (record4.worker !== null) {
3503
+ keys(record4.worker, ["id", "operationId", "pid", "birth"]);
3504
+ if (!isReliabilityId(record4.worker.id) || !isReliabilityId(record4.worker.operationId) || typeof record4.worker.birth !== "string" || !/^(proc|ps):[a-zA-Z0-9]+$/.test(record4.worker.birth) || !isReliabilityCounter(record4.worker.pid) || record4.worker.pid === 0 || record4.worker.operationId !== state.operation?.id) {
3505
3505
  throw new Error("Reliability worker identity is invalid.");
3506
3506
  }
3507
3507
  }
3508
3508
  }
3509
- function readReliabilityOperation(identity) {
3510
- const file = reliabilityOperationPath(identity);
3509
+ function readReliabilityOperation(identity2) {
3510
+ const file = reliabilityOperationPath(identity2);
3511
3511
  let descriptor;
3512
3512
  try {
3513
3513
  descriptor = import_node_fs9.default.openSync(
@@ -3526,27 +3526,27 @@ function readReliabilityOperation(identity) {
3526
3526
  const bytes = Buffer.alloc(MAX_RECORD_BYTES + 1);
3527
3527
  const count = import_node_fs9.default.readSync(descriptor, bytes, 0, bytes.length, 0);
3528
3528
  if (count > MAX_RECORD_BYTES) throw new Error("Reliability record exceeds its byte limit.");
3529
- const record3 = JSON.parse(
3529
+ const record4 = JSON.parse(
3530
3530
  bytes.subarray(0, count).toString("utf8")
3531
3531
  );
3532
- validate(record3, identity);
3533
- return record3;
3532
+ validate(record4, identity2);
3533
+ return record4;
3534
3534
  } finally {
3535
3535
  import_node_fs9.default.closeSync(descriptor);
3536
3536
  }
3537
3537
  }
3538
- function persist(record3) {
3539
- validate(record3, record3.identity);
3540
- const contents = `${JSON.stringify(record3)}
3538
+ function persist(record4) {
3539
+ validate(record4, record4.identity);
3540
+ const contents = `${JSON.stringify(record4)}
3541
3541
  `;
3542
3542
  if (Buffer.byteLength(contents) > MAX_RECORD_BYTES)
3543
3543
  throw new Error("Reliability record exceeds its byte limit.");
3544
- const file = reliabilityOperationPath(record3.identity);
3544
+ const file = reliabilityOperationPath(record4.identity);
3545
3545
  try {
3546
3546
  writeFileAtomically(file, contents);
3547
3547
  } catch (error) {
3548
- const reread = readReliabilityOperation(record3.identity);
3549
- if (!reread || JSON.stringify(reread) !== JSON.stringify(record3)) throw error;
3548
+ const reread = readReliabilityOperation(record4.identity);
3549
+ if (!reread || JSON.stringify(reread) !== JSON.stringify(record4)) throw error;
3550
3550
  const descriptor = import_node_fs9.default.openSync(file, import_node_fs9.default.constants.O_RDONLY | import_node_fs9.default.constants.O_NOFOLLOW);
3551
3551
  try {
3552
3552
  import_node_fs9.default.fsyncSync(descriptor);
@@ -3561,38 +3561,38 @@ function persist(record3) {
3561
3561
  }
3562
3562
  }
3563
3563
  }
3564
- function updateReliabilityOperation(identity, operation) {
3565
- const file = reliabilityOperationPath(identity);
3564
+ function updateReliabilityOperation(identity2, operation) {
3565
+ const file = reliabilityOperationPath(identity2);
3566
3566
  import_node_fs9.default.mkdirSync(import_node_path10.default.dirname(file), { recursive: true, mode: 448 });
3567
3567
  return withFileLockSync(`${file}.lock`, { activity: "reliability intent", waitMs: 2e3 }, () => {
3568
- const record3 = readReliabilityOperation(identity) ?? {
3568
+ const record4 = readReliabilityOperation(identity2) ?? {
3569
3569
  version: 1,
3570
- identity: { ...identity },
3570
+ identity: { ...identity2 },
3571
3571
  revision: 0,
3572
- state: createReliabilityState(identityKey(identity), 0, "manual"),
3572
+ state: createReliabilityState(identityKey(identity2), 0, "manual"),
3573
3573
  worker: null,
3574
3574
  effectSequence: 0,
3575
3575
  outcome: null
3576
3576
  };
3577
- if (record3.revision === Number.MAX_SAFE_INTEGER)
3577
+ if (record4.revision === Number.MAX_SAFE_INTEGER)
3578
3578
  throw new Error("Reliability record revision is exhausted.");
3579
- const result = operation(record3);
3579
+ const result = operation(record4);
3580
3580
  if (result && typeof result.then === "function") {
3581
3581
  throw new Error("Reliability record updates must be synchronous.");
3582
3582
  }
3583
- validate(record3, identity);
3584
- record3.revision += 1;
3585
- persist(record3);
3583
+ validate(record4, identity2);
3584
+ record4.revision += 1;
3585
+ persist(record4);
3586
3586
  return result;
3587
3587
  });
3588
3588
  }
3589
- function withReliabilityObservationFence(identity, expectedRevision, publish) {
3590
- const file = reliabilityOperationPath(identity);
3591
- const before = readReliabilityOperation(identity);
3589
+ function withReliabilityObservationFence(identity2, expectedRevision, publish) {
3590
+ const file = reliabilityOperationPath(identity2);
3591
+ const before = readReliabilityOperation(identity2);
3592
3592
  if (!before || before.revision !== expectedRevision)
3593
3593
  throw new Error("Reliability observation fence changed or is absent.");
3594
3594
  return withFileLockSync(`${file}.lock`, { activity: "observer publication", waitMs: 100 }, () => {
3595
- const current = readReliabilityOperation(identity);
3595
+ const current = readReliabilityOperation(identity2);
3596
3596
  if (!current || current.revision !== expectedRevision)
3597
3597
  throw new Error("Reliability observation fence changed or is absent.");
3598
3598
  const result = publish(current);
@@ -3731,8 +3731,8 @@ var init_controller_monitor = __esm({
3731
3731
  if (this.stopped) return;
3732
3732
  const snapshot = this.sessions.read();
3733
3733
  const current = new Set(snapshot.environments.map((env) => env.id));
3734
- for (const [id2, abort] of this.active) if (!current.has(id2)) abort.abort();
3735
- for (const id2 of this.lastStarted.keys()) if (!current.has(id2)) this.lastStarted.delete(id2);
3734
+ for (const [id3, abort] of this.active) if (!current.has(id3)) abort.abort();
3735
+ for (const id3 of this.lastStarted.keys()) if (!current.has(id3)) this.lastStarted.delete(id3);
3736
3736
  const now = this.clock();
3737
3737
  const candidates = snapshot.environments.filter(
3738
3738
  (env) => !this.active.has(env.id) && now - (this.lastStarted.get(env.id) ?? -Infinity) >= 5e3
@@ -3852,10 +3852,10 @@ async function observeControllerProcess(containerId, name, signal) {
3852
3852
  ],
3853
3853
  signal
3854
3854
  );
3855
- const identity = output2.trim();
3856
- if (!/^[1-9][0-9]* [0-9]+ [a-zA-Z0-9][a-zA-Z0-9._:-]{0,4095}$/.test(identity))
3855
+ const identity2 = output2.trim();
3856
+ if (!/^[1-9][0-9]* [0-9]+ [a-zA-Z0-9][a-zA-Z0-9._:-]{0,4095}$/.test(identity2))
3857
3857
  throw new Error("Process observation is unavailable.");
3858
- return identity;
3858
+ return identity2;
3859
3859
  }
3860
3860
  var PROCESS_OBSERVATION;
3861
3861
  var init_controller_process_observation = __esm({
@@ -4260,10 +4260,17 @@ function parseDockerLines(stdout) {
4260
4260
  }
4261
4261
  return lines;
4262
4262
  }
4263
- function runManagedStopDocker(args) {
4264
- const result = (0, import_node_child_process6.spawnSync)("docker", args, {
4263
+ function runManagedStopDocker(args, endpoint, timeoutMs = MANAGED_STOP_DOCKER_TIMEOUT_MS) {
4264
+ if (endpoint !== void 0) assertManagedStopEndpoint(endpoint);
4265
+ const env = { ...process.env };
4266
+ if (endpoint !== void 0) {
4267
+ delete env.DOCKER_CONTEXT;
4268
+ delete env.DOCKER_HOST;
4269
+ }
4270
+ const result = (0, import_node_child_process6.spawnSync)("docker", endpoint ? ["--host", endpoint, ...args] : args, {
4271
+ ...endpoint ? { env } : {},
4265
4272
  encoding: "utf-8",
4266
- timeout: MANAGED_STOP_DOCKER_TIMEOUT_MS,
4273
+ timeout: timeoutMs,
4267
4274
  maxBuffer: MANAGED_STOP_DOCKER_MAX_BUFFER
4268
4275
  });
4269
4276
  if (result.error || result.status !== 0) {
@@ -4274,17 +4281,21 @@ function runManagedStopDocker(args) {
4274
4281
  }
4275
4282
  return result.stdout;
4276
4283
  }
4277
- function listManagedStopContainerIds(composeProject) {
4278
- const stdout = runManagedStopDocker([
4279
- "ps",
4280
- "-a",
4281
- "--no-trunc",
4282
- "--filter",
4283
- `label=${MANAGED_STOP_COMPOSE_LABEL}=${composeProject}`,
4284
- "--format",
4285
- "{{.ID}}"
4286
- ]);
4284
+ function listManagedStopContainerIds(composeProject, endpoint) {
4285
+ const stdout = runManagedStopDocker(
4286
+ [
4287
+ "ps",
4288
+ "-a",
4289
+ "--no-trunc",
4290
+ "--filter",
4291
+ `label=${MANAGED_STOP_COMPOSE_LABEL}=${composeProject}`,
4292
+ "--format",
4293
+ "{{.ID}}"
4294
+ ],
4295
+ endpoint
4296
+ );
4287
4297
  const ids = parseDockerLines(stdout);
4298
+ if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
4288
4299
  ids.forEach(assertFullContainerId);
4289
4300
  assertUniqueContainerIds(ids);
4290
4301
  return ids;
@@ -4378,13 +4389,11 @@ function validateManagedStopSnapshot(value, composeProject) {
4378
4389
  networks: {}
4379
4390
  };
4380
4391
  }
4381
- function inspectManagedStopPopulation(ids, composeProject) {
4382
- const stdout = runManagedStopDocker([
4383
- "inspect",
4384
- "--format",
4385
- MANAGED_STOP_INSPECT_TEMPLATE,
4386
- ...ids
4387
- ]);
4392
+ function inspectManagedStopPopulation(ids, composeProject, endpoint) {
4393
+ const stdout = runManagedStopDocker(
4394
+ ["inspect", "--format", MANAGED_STOP_INSPECT_TEMPLATE, ...ids],
4395
+ endpoint
4396
+ );
4388
4397
  const lines = parseDockerLines(stdout);
4389
4398
  if (lines.length !== ids.length) {
4390
4399
  throw new Error("Managed stop Docker inspection returned an incomplete population.");
@@ -4412,22 +4421,22 @@ function requireSameContainerPopulation(expected, actual) {
4412
4421
  throw new Error("Managed stop Docker inspection observed a changed population.");
4413
4422
  }
4414
4423
  const expectedSet = new Set(expected);
4415
- if (actual.some((id2) => !expectedSet.has(id2))) {
4424
+ if (actual.some((id3) => !expectedSet.has(id3))) {
4416
4425
  throw new Error("Managed stop Docker inspection observed a changed population.");
4417
4426
  }
4418
4427
  }
4419
- function inspectManagedStopContainers(composeProject) {
4428
+ function inspectManagedStopContainers(composeProject, endpoint) {
4420
4429
  if (typeof composeProject !== "string" || composeProject.length === 0 || composeProject !== composeProject.trim() || !MANAGED_STOP_PROJECT_PATTERN.test(composeProject)) {
4421
4430
  throw new Error("Managed stop Docker inspection requires a safe Compose project.");
4422
4431
  }
4423
- const listedIds = listManagedStopContainerIds(composeProject);
4432
+ const listedIds = listManagedStopContainerIds(composeProject, endpoint);
4424
4433
  if (listedIds.length === 0) {
4425
- const confirmedIds2 = listManagedStopContainerIds(composeProject);
4434
+ const confirmedIds2 = listManagedStopContainerIds(composeProject, endpoint);
4426
4435
  requireSameContainerPopulation(listedIds, confirmedIds2);
4427
4436
  return [];
4428
4437
  }
4429
- const snapshots = inspectManagedStopPopulation(listedIds, composeProject);
4430
- const confirmedIds = listManagedStopContainerIds(composeProject);
4438
+ const snapshots = inspectManagedStopPopulation(listedIds, composeProject, endpoint);
4439
+ const confirmedIds = listManagedStopContainerIds(composeProject, endpoint);
4431
4440
  requireSameContainerPopulation(listedIds, confirmedIds);
4432
4441
  return snapshots;
4433
4442
  }
@@ -4450,7 +4459,7 @@ function inspectWorkspaceContainers(options) {
4450
4459
  `docker ps failed: ${(listed.stderr || listed.stdout || listed.error?.message || "unknown error").trim()}`
4451
4460
  );
4452
4461
  }
4453
- ids = listed.stdout.split(/\r?\n/).map((id2) => id2.trim()).filter(Boolean);
4462
+ ids = listed.stdout.split(/\r?\n/).map((id3) => id3.trim()).filter(Boolean);
4454
4463
  }
4455
4464
  if (ids.length === 0) return [];
4456
4465
  const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
@@ -4514,6 +4523,121 @@ function resolveRunningWorkspaceContainer(repoPath) {
4514
4523
  }
4515
4524
  return { id: container.id, workspacePath: repoMount.Destination };
4516
4525
  }
4526
+ function supportsManagedStopBaseline(endpoint) {
4527
+ if (!endpoint || endpoint.length > 4096 || endpoint.trim() !== endpoint || [...endpoint].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
4528
+ throw new Error("Docker endpoint is malformed.");
4529
+ }
4530
+ if (endpoint.startsWith("unix://")) {
4531
+ if (!endpoint.startsWith("unix:///") || endpoint.length === 8 || import_node_path14.default.normalize(endpoint.slice(7)) !== endpoint.slice(7)) {
4532
+ throw new Error("Docker endpoint is malformed.");
4533
+ }
4534
+ return true;
4535
+ }
4536
+ if (/\s/.test(endpoint)) throw new Error("Docker endpoint is malformed.");
4537
+ if (/^npipe:\/\/\/\/\.\/pipe\/[a-zA-Z0-9_.-]+$/.test(endpoint)) return false;
4538
+ let url;
4539
+ try {
4540
+ url = new URL(endpoint);
4541
+ } catch {
4542
+ throw new Error("Docker endpoint is malformed.");
4543
+ }
4544
+ if (url.protocol !== "tcp:" && url.protocol !== "ssh:" || !url.hostname || url.password || url.search || url.hash || url.protocol === "tcp:" && (url.username || url.pathname && url.pathname !== "/") || url.port && (!/^\d+$/.test(url.port) || Number(url.port) < 1 || Number(url.port) > 65535)) {
4545
+ throw new Error("Docker endpoint is malformed or unsupported.");
4546
+ }
4547
+ return false;
4548
+ }
4549
+ function assertManagedStopEndpoint(endpoint) {
4550
+ if (!supportsManagedStopBaseline(endpoint)) {
4551
+ throw new Error("Managed stop requires an exact local Docker endpoint.");
4552
+ }
4553
+ }
4554
+ function parseManagedStopJson(output2) {
4555
+ try {
4556
+ return JSON.parse(output2);
4557
+ } catch {
4558
+ throw new Error("Managed stop Docker response is malformed.");
4559
+ }
4560
+ }
4561
+ function resolveManagedStopEndpoint() {
4562
+ const endpoint = process.env.DOCKER_HOST && !process.env.DOCKER_CONTEXT ? process.env.DOCKER_HOST : parseManagedStopJson(
4563
+ runManagedStopDocker([
4564
+ "context",
4565
+ "inspect",
4566
+ "--format",
4567
+ "{{json .Endpoints.docker.Host}}"
4568
+ ])
4569
+ );
4570
+ if (typeof endpoint !== "string") throw new Error("Docker endpoint is unavailable.");
4571
+ supportsManagedStopBaseline(endpoint);
4572
+ return endpoint;
4573
+ }
4574
+ function inspectManagedStopDaemon(endpoint) {
4575
+ const value = parseManagedStopJson(
4576
+ runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
4577
+ );
4578
+ if (typeof value !== "string" || !value.length || value.length > 256 || /\s/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
4579
+ throw new Error("Docker daemon identity is unavailable.");
4580
+ }
4581
+ return value;
4582
+ }
4583
+ function inspectManagedStopRunnerId(endpoint, containerId) {
4584
+ assertFullContainerId(containerId);
4585
+ const value = parseManagedStopJson(
4586
+ runManagedStopDocker(
4587
+ ["inspect", "--format", '{{json (index .Config.Labels "dev.containers.id")}}', containerId],
4588
+ endpoint
4589
+ )
4590
+ );
4591
+ if (typeof value !== "string" || value.length > 4096)
4592
+ throw new Error("Provider container binding is unavailable.");
4593
+ return value;
4594
+ }
4595
+ function inspectProviderRunnerContainers(endpoint, runnerId) {
4596
+ if (!runnerId || runnerId.length > 4096 || /[\r\n\0]/.test(runnerId))
4597
+ throw new Error("Provider runner identity is malformed.");
4598
+ const ids = parseDockerLines(
4599
+ runManagedStopDocker(
4600
+ [
4601
+ "ps",
4602
+ "-a",
4603
+ "--no-trunc",
4604
+ "--filter",
4605
+ `label=dev.containers.id=${runnerId}`,
4606
+ "--format",
4607
+ "{{.ID}}"
4608
+ ],
4609
+ endpoint
4610
+ )
4611
+ );
4612
+ if (ids.length > 256) throw new Error("Provider runner population exceeds its bound.");
4613
+ ids.forEach(assertFullContainerId);
4614
+ assertUniqueContainerIds(ids);
4615
+ return ids;
4616
+ }
4617
+ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
4618
+ const ids = parseDockerLines(
4619
+ runManagedStopDocker(
4620
+ [
4621
+ "ps",
4622
+ "-a",
4623
+ "--no-trunc",
4624
+ "--filter",
4625
+ `label=com.docker.compose.project.working_dir=${composeDirectory}`,
4626
+ "--format",
4627
+ "{{.ID}}"
4628
+ ],
4629
+ endpoint
4630
+ )
4631
+ );
4632
+ if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
4633
+ ids.forEach(assertFullContainerId);
4634
+ assertUniqueContainerIds(ids);
4635
+ return ids;
4636
+ }
4637
+ function stopPinnedManagedContainer(endpoint, containerId) {
4638
+ assertFullContainerId(containerId);
4639
+ runManagedStopDocker(["stop", containerId], endpoint, 3e4);
4640
+ }
4517
4641
  var import_node_child_process6, import_node_path14, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE, MANAGED_STOP_INSPECT_TEMPLATE, MANAGED_STOP_PROJECT_PATTERN, FULL_CONTAINER_ID_PATTERN, MANAGED_STOP_DOCKER_TIMEOUT_MS, MANAGED_STOP_DOCKER_MAX_BUFFER, MANAGED_STOP_COMPOSE_LABEL, MANAGED_STOP_IDENTITY_LABELS;
4518
4642
  var init_devpod_environment = __esm({
4519
4643
  "src/core/devpod-environment.ts"() {
@@ -5084,12 +5208,147 @@ var init_http_route_probe = __esm({
5084
5208
  }
5085
5209
  });
5086
5210
 
5211
+ // src/core/managed-stop-baseline.ts
5212
+ function record2(value, keys2) {
5213
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
5214
+ throw new Error("Invalid managed stop baseline fields.");
5215
+ }
5216
+ return value;
5217
+ }
5218
+ function text(value, empty = false) {
5219
+ if (typeof value !== "string" || value.length > 4096 || !empty && !value.length || value.trim() !== value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
5220
+ throw new Error("Invalid managed stop baseline identity.");
5221
+ }
5222
+ return value;
5223
+ }
5224
+ function absolute(value) {
5225
+ const result = text(value);
5226
+ if (!import_node_path16.default.isAbsolute(result) || import_node_path16.default.normalize(result) !== result) {
5227
+ throw new Error("Invalid managed stop baseline path.");
5228
+ }
5229
+ return result;
5230
+ }
5231
+ function list(value, parse2 = text) {
5232
+ if (!Array.isArray(value) || !value.length || value.length > 256) {
5233
+ throw new Error("Invalid managed stop baseline population.");
5234
+ }
5235
+ const result = value.map((item) => parse2(item));
5236
+ if (new Set(result).size !== result.length) throw new Error("Duplicate stop baseline identity.");
5237
+ return result;
5238
+ }
5239
+ function id(value) {
5240
+ const result = text(value);
5241
+ if (!/^[a-f0-9]{64}$/.test(result)) throw new Error("Invalid stop baseline container ID.");
5242
+ return result;
5243
+ }
5244
+ function validateManagedStopBaseline(value, state) {
5245
+ const v = record2(value, [
5246
+ "version",
5247
+ "provider",
5248
+ "context",
5249
+ "providerId",
5250
+ "uid",
5251
+ "sourcePath",
5252
+ "sourceContainer",
5253
+ "endpoint",
5254
+ "daemonId",
5255
+ "sourceConfigSha256",
5256
+ "effectiveConfigSha256",
5257
+ "project",
5258
+ "primaryService",
5259
+ "requiredServices",
5260
+ "allowedServices",
5261
+ "composeDirectory",
5262
+ "composeFiles",
5263
+ "featureDirectory",
5264
+ "containers"
5265
+ ]);
5266
+ if (v.version !== 1 || v.provider !== "devsy" || Buffer.byteLength(JSON.stringify(value)) > 1024 * 1024) {
5267
+ throw new Error("Unsupported or oversized managed stop baseline.");
5268
+ }
5269
+ const endpoint = text(v.endpoint);
5270
+ if (!endpoint.startsWith("unix://"))
5271
+ throw new Error("Stop baseline requires a pinned local Docker endpoint.");
5272
+ absolute(endpoint.slice(7));
5273
+ const result = {
5274
+ version: 1,
5275
+ provider: "devsy",
5276
+ context: text(v.context),
5277
+ providerId: text(v.providerId),
5278
+ uid: text(v.uid, true),
5279
+ sourcePath: absolute(v.sourcePath),
5280
+ sourceContainer: text(v.sourceContainer, true),
5281
+ endpoint,
5282
+ daemonId: text(v.daemonId),
5283
+ sourceConfigSha256: id(v.sourceConfigSha256),
5284
+ effectiveConfigSha256: id(v.effectiveConfigSha256),
5285
+ project: text(v.project),
5286
+ primaryService: text(v.primaryService),
5287
+ requiredServices: list(v.requiredServices),
5288
+ allowedServices: list(v.allowedServices),
5289
+ composeDirectory: absolute(v.composeDirectory),
5290
+ composeFiles: list(v.composeFiles, absolute),
5291
+ featureDirectory: absolute(v.featureDirectory),
5292
+ containers: []
5293
+ };
5294
+ if (result.providerId !== state.devpodId || result.sourcePath !== state.repoPath || result.project !== state.composeProject || result.sourceConfigSha256 !== state.sourceConfigSha256 || result.effectiveConfigSha256 !== state.effectiveConfigSha256 || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(result.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(result.providerId) || !/^[a-z0-9][a-z0-9_-]*$/.test(result.project) || !result.requiredServices.includes(result.primaryService) || result.requiredServices.some((service) => !result.allowedServices.includes(service))) {
5295
+ throw new Error("Managed stop baseline does not bind the retained generation.");
5296
+ }
5297
+ if (!Array.isArray(v.containers) || !v.containers.length || v.containers.length > 256) {
5298
+ throw new Error("Invalid managed stop baseline population.");
5299
+ }
5300
+ result.containers = v.containers.map((entry) => {
5301
+ const c = record2(entry, ["id", "service", "configFiles", "mounts"]);
5302
+ const configFiles = list(c.configFiles, absolute);
5303
+ if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
5304
+ (file) => import_node_path16.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
5305
+ import_node_path16.default.basename(file)
5306
+ )
5307
+ )) {
5308
+ throw new Error("Invalid stop baseline Compose provenance.");
5309
+ }
5310
+ if (!Array.isArray(c.mounts) || c.mounts.length > 256)
5311
+ throw new Error("Invalid baseline mounts.");
5312
+ return {
5313
+ id: id(c.id),
5314
+ service: text(c.service),
5315
+ configFiles,
5316
+ mounts: c.mounts.map((item) => {
5317
+ const mount = record2(item, ["Type", "Source", "Destination"]);
5318
+ return {
5319
+ Type: text(mount.Type),
5320
+ Source: mount.Type === "tmpfs" && mount.Source === "" ? "" : absolute(mount.Source),
5321
+ Destination: absolute(mount.Destination)
5322
+ };
5323
+ })
5324
+ };
5325
+ });
5326
+ const services = result.containers.map((container) => container.service);
5327
+ if (new Set(services).size !== services.length || new Set(result.containers.map((container) => container.id)).size !== result.containers.length || services.some((service) => !result.allowedServices.includes(service)) || result.requiredServices.some((service) => !services.includes(service))) {
5328
+ throw new Error("Incomplete or duplicate stop baseline population.");
5329
+ }
5330
+ const primary = result.containers.find(
5331
+ (container) => container.service === result.primaryService
5332
+ );
5333
+ if (primary?.mounts.filter((mount) => mount.Type === "bind" && mount.Source === state.repoPath).length !== 1 || result.sourceContainer && result.sourceContainer !== primary.id) {
5334
+ throw new Error("Stop baseline has no exact primary source binding.");
5335
+ }
5336
+ return result;
5337
+ }
5338
+ var import_node_path16;
5339
+ var init_managed_stop_baseline = __esm({
5340
+ "src/core/managed-stop-baseline.ts"() {
5341
+ "use strict";
5342
+ import_node_path16 = __toESM(require("path"));
5343
+ }
5344
+ });
5345
+
5087
5346
  // src/core/managed-runtime-state.ts
5088
5347
  function stateKey(repoPath, workspace) {
5089
5348
  return (0, import_node_crypto10.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
5090
5349
  }
5091
5350
  function managedRuntimeStatePath(repoPath, workspace) {
5092
- return import_node_path16.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
5351
+ return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
5093
5352
  }
5094
5353
  function isStringArray(value) {
5095
5354
  return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
@@ -5131,7 +5390,7 @@ function validateState(value, repoPath, workspace) {
5131
5390
  if (typeof candidate.updatedAt !== "string" || !Number.isFinite(Date.parse(candidate.updatedAt))) {
5132
5391
  throw new Error("Managed runtime state has an invalid timestamp.");
5133
5392
  }
5134
- return {
5393
+ const result = {
5135
5394
  version: 1,
5136
5395
  repoPath,
5137
5396
  ...workspace !== void 0 ? { workspace } : {},
@@ -5149,6 +5408,10 @@ function validateState(value, repoPath, workspace) {
5149
5408
  ...candidate.transitionPhase ? { transitionPhase: candidate.transitionPhase } : {},
5150
5409
  updatedAt: candidate.updatedAt
5151
5410
  };
5411
+ if (Object.hasOwn(candidate, "stopBaseline")) {
5412
+ result.stopBaseline = validateManagedStopBaseline(candidate.stopBaseline, result);
5413
+ }
5414
+ return result;
5152
5415
  }
5153
5416
  function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
5154
5417
  const statePath = managedRuntimeStatePath(repoPath, workspace);
@@ -5161,14 +5424,15 @@ function readManagedRuntimeState(repoPath, workspace, read = (file) => import_no
5161
5424
  }
5162
5425
  return validateState(parsed, repoPath, workspace);
5163
5426
  }
5164
- var import_node_crypto10, import_node_fs13, import_node_path16;
5427
+ var import_node_crypto10, import_node_fs13, import_node_path17;
5165
5428
  var init_managed_runtime_state = __esm({
5166
5429
  "src/core/managed-runtime-state.ts"() {
5167
5430
  "use strict";
5168
5431
  import_node_crypto10 = require("crypto");
5169
5432
  import_node_fs13 = __toESM(require("fs"));
5170
- import_node_path16 = __toESM(require("path"));
5433
+ import_node_path17 = __toESM(require("path"));
5171
5434
  init_atomic_file();
5435
+ init_managed_stop_baseline();
5172
5436
  init_router();
5173
5437
  }
5174
5438
  });
@@ -5197,7 +5461,7 @@ async function containers(project, signal) {
5197
5461
  signal
5198
5462
  );
5199
5463
  const ids = listed.trim() ? listed.trim().split(/\r?\n/).sort() : [];
5200
- if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((id2) => !/^[a-f0-9]{64}$/.test(id2)))
5464
+ if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((id3) => !/^[a-f0-9]{64}$/.test(id3)))
5201
5465
  throw new Error("Observation container population is invalid.");
5202
5466
  if (!ids.length) return [];
5203
5467
  const output2 = await runControllerProbe(
@@ -5214,12 +5478,12 @@ async function containers(project, signal) {
5214
5478
  }
5215
5479
  return values.sort((a, b) => a.id.localeCompare(b.id));
5216
5480
  }
5217
- var import_node_crypto11, import_node_path17, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
5481
+ var import_node_crypto11, import_node_path18, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
5218
5482
  var init_controller_observation = __esm({
5219
5483
  "src/core/controller-observation.ts"() {
5220
5484
  "use strict";
5221
5485
  import_node_crypto11 = require("crypto");
5222
- import_node_path17 = __toESM(require("path"));
5486
+ import_node_path18 = __toESM(require("path"));
5223
5487
  init_controller_binding();
5224
5488
  init_controller_monitor();
5225
5489
  init_controller_probe();
@@ -5262,14 +5526,14 @@ var init_controller_observation = __esm({
5262
5526
  )).trim().split("\n");
5263
5527
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
5264
5528
  const [gitDir, commonDir] = metadata;
5265
- const sourcePath = import_node_path17.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
5529
+ const sourcePath = import_node_path18.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
5266
5530
  const files = [
5267
- import_node_path17.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
5268
- import_node_path17.default.join(environment.repoPath, ".devrouter.yml"),
5269
- import_node_path17.default.join(gitDir, "devrouter-workspace"),
5531
+ import_node_path18.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
5532
+ import_node_path18.default.join(environment.repoPath, ".devrouter.yml"),
5533
+ import_node_path18.default.join(gitDir, "devrouter-workspace"),
5270
5534
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
5271
5535
  sourcePath,
5272
- import_node_path17.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
5536
+ import_node_path18.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
5273
5537
  ];
5274
5538
  const persisted = captureControllerEvidence(files);
5275
5539
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -5285,16 +5549,16 @@ var init_controller_observation = __esm({
5285
5549
  const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
5286
5550
  if (!state || state.devpodId !== environment.providerId || state.profile !== environment.profile || typeof source.service !== "string" || !source.service || (0, import_node_crypto11.createHash)("sha256").update(persisted.contents[4]).digest("hex") !== state.sourceConfigSha256 || (0, import_node_crypto11.createHash)("sha256").update(persisted.contents[5]).digest("hex") !== state.effectiveConfigSha256)
5287
5551
  throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
5288
- const identity = {
5552
+ const identity2 = {
5289
5553
  repoPath: environment.repoPath,
5290
5554
  workspace: environment.workspace,
5291
5555
  provider: environment.provider
5292
5556
  };
5293
- const journal = readReliabilityOperation(identity);
5557
+ const journal = readReliabilityOperation(identity2);
5294
5558
  if (!journal) throw new Error("Observation journal is unavailable.");
5295
5559
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
5296
5560
  const before = await containers(state.composeProject, signal);
5297
- const composeDirectory = import_node_path17.default.join(environment.repoPath, ".devcontainer");
5561
+ const composeDirectory = import_node_path18.default.join(environment.repoPath, ".devcontainer");
5298
5562
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
5299
5563
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
5300
5564
  throw new Error("Observation Compose source is unavailable.");
@@ -5311,7 +5575,7 @@ var init_controller_observation = __esm({
5311
5575
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
5312
5576
  throw new Error("Observation Compose ownership is unavailable.");
5313
5577
  const files2 = recorded.split(",").map((file) => file.trim());
5314
- if (files2.some((file) => !import_node_path17.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
5578
+ if (files2.some((file) => !import_node_path18.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
5315
5579
  repoPath: environment.repoPath,
5316
5580
  service: container.labels["com.docker.compose.service"] ?? "",
5317
5581
  composeProject: state.composeProject,
@@ -5440,7 +5704,7 @@ var init_controller_observation = __esm({
5440
5704
  const routeFingerprint = digest2(routes);
5441
5705
  return {
5442
5706
  environment,
5443
- identity,
5707
+ identity: identity2,
5444
5708
  journal,
5445
5709
  sampledAtMs: Math.floor(performance.now()),
5446
5710
  runtimeFingerprint: digest2({ before, processes }),
@@ -5538,11 +5802,11 @@ var init_controller_sessions = __esm({
5538
5802
  this.previousTime = { monotonic, wall };
5539
5803
  return discontinuity;
5540
5804
  }
5541
- acquire(id2, environment, requirements, monotonic, wall) {
5805
+ acquire(id3, environment, requirements, monotonic, wall) {
5542
5806
  this.tick(monotonic, wall);
5543
5807
  const next = this.read();
5544
5808
  const required = [...requirements].sort();
5545
- const existing = next.sessions.find((s) => s.id === id2);
5809
+ const existing = next.sessions.find((s) => s.id === id3);
5546
5810
  const shared = next.environments.find((env) => env.repoPath === environment.repoPath);
5547
5811
  if (shared && JSON.stringify(shared) !== JSON.stringify(environment))
5548
5812
  throw new Error("Controller environment binding conflicts.");
@@ -5554,7 +5818,7 @@ var init_controller_sessions = __esm({
5554
5818
  if (next.sessions.length >= 128 || !shared && next.environments.length >= 32)
5555
5819
  throw new Error("Controller coordination capacity exhausted.");
5556
5820
  const session = {
5557
- id: id2,
5821
+ id: id3,
5558
5822
  environmentId: environment.id,
5559
5823
  generation: (0, import_node_crypto12.randomUUID)(),
5560
5824
  requirements: required,
@@ -5564,7 +5828,7 @@ var init_controller_sessions = __esm({
5564
5828
  next.sessions.push(session);
5565
5829
  this.event(next, session, "acquired");
5566
5830
  this.commit(next);
5567
- this.leases.set(id2, monotonic + 3e4);
5831
+ this.leases.set(id3, monotonic + 3e4);
5568
5832
  return this.binding(session);
5569
5833
  }
5570
5834
  publish(bindings, projections, monotonic, wall) {
@@ -5610,8 +5874,8 @@ var init_controller_sessions = __esm({
5610
5874
  if (changed) {
5611
5875
  this.commit(next);
5612
5876
  if (bindingChanged) {
5613
- for (const id2 of this.leases.keys())
5614
- if (!next.sessions.some((s) => s.id === id2)) this.leases.delete(id2);
5877
+ for (const id3 of this.leases.keys())
5878
+ if (!next.sessions.some((s) => s.id === id3)) this.leases.delete(id3);
5615
5879
  }
5616
5880
  }
5617
5881
  }
@@ -5677,11 +5941,11 @@ function fields(value, expected) {
5677
5941
  const actual = Object.keys(value);
5678
5942
  if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) fail2();
5679
5943
  }
5680
- function text(value, max = 256) {
5944
+ function text2(value, max = 256) {
5681
5945
  return typeof value === "string" && value.length > 0 && value.length <= max && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
5682
5946
  }
5683
- function id(value) {
5684
- return text(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
5947
+ function id2(value) {
5948
+ return text2(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
5685
5949
  }
5686
5950
  function counter(value) {
5687
5951
  return Number.isSafeInteger(value) && Number(value) >= 0;
@@ -5697,7 +5961,7 @@ function validateControllerSnapshot(value) {
5697
5961
  "sessions",
5698
5962
  "events"
5699
5963
  ]);
5700
- if (value.version !== 1 || !id(value.store) || !counter(value.epoch) || !counter(value.revision) || !counter(value.nextSequence) || value.nextSequence < 1 || !Array.isArray(value.environments) || value.environments.length > 32 || !Array.isArray(value.sessions) || value.sessions.length > 128 || !Array.isArray(value.events) || value.events.length > 256)
5964
+ if (value.version !== 1 || !id2(value.store) || !counter(value.epoch) || !counter(value.revision) || !counter(value.nextSequence) || value.nextSequence < 1 || !Array.isArray(value.environments) || value.environments.length > 32 || !Array.isArray(value.sessions) || value.sessions.length > 128 || !Array.isArray(value.events) || value.events.length > 256)
5701
5965
  fail2();
5702
5966
  const environments = /* @__PURE__ */ new Set();
5703
5967
  const checkouts = /* @__PURE__ */ new Set();
@@ -5711,7 +5975,7 @@ function validateControllerSnapshot(value) {
5711
5975
  "profile",
5712
5976
  "fingerprint"
5713
5977
  ]);
5714
- if (!id(env.id) || environments.has(env.id) || !text(env.repoPath, 4096) || !import_node_path18.default.isAbsolute(env.repoPath) || import_node_path18.default.resolve(env.repoPath) !== env.repoPath || checkouts.has(env.repoPath) || !id(env.workspace) || !id(env.providerId) || !text(env.profile) || !/^[a-z0-9-]+(?:,[a-z0-9-]+)*$/.test(env.profile) || !text(env.fingerprint, 128) || !/^[a-f0-9]{64}$/.test(env.fingerprint) || !["devpod", "devsy"].includes(String(env.provider)))
5978
+ if (!id2(env.id) || environments.has(env.id) || !text2(env.repoPath, 4096) || !import_node_path19.default.isAbsolute(env.repoPath) || import_node_path19.default.resolve(env.repoPath) !== env.repoPath || checkouts.has(env.repoPath) || !id2(env.workspace) || !id2(env.providerId) || !text2(env.profile) || !/^[a-z0-9-]+(?:,[a-z0-9-]+)*$/.test(env.profile) || !text2(env.fingerprint, 128) || !/^[a-f0-9]{64}$/.test(env.fingerprint) || !["devpod", "devsy"].includes(String(env.provider)))
5715
5979
  fail2();
5716
5980
  environments.add(env.id);
5717
5981
  checkouts.add(env.repoPath);
@@ -5727,8 +5991,8 @@ function validateControllerSnapshot(value) {
5727
5991
  "renewedAtMs",
5728
5992
  ...Object.hasOwn(session, "observation") ? ["observation"] : []
5729
5993
  ]);
5730
- if (!id(session.id) || sessions.has(session.id) || !id(session.environmentId) || !environments.has(session.environmentId) || !id(session.generation) || !counter(session.renewedAtMs) || !Array.isArray(session.requirements) || session.requirements.length < 1 || session.requirements.length > 16 || new Set(session.requirements).size !== session.requirements.length || session.requirements.some(
5731
- (r) => !text(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
5994
+ if (!id2(session.id) || sessions.has(session.id) || !id2(session.environmentId) || !environments.has(session.environmentId) || !id2(session.generation) || !counter(session.renewedAtMs) || !Array.isArray(session.requirements) || session.requirements.length < 1 || session.requirements.length > 16 || new Set(session.requirements).size !== session.requirements.length || session.requirements.some(
5995
+ (r) => !text2(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
5732
5996
  ))
5733
5997
  fail2();
5734
5998
  if (session.observation !== void 0) {
@@ -5750,7 +6014,7 @@ function validateControllerSnapshot(value) {
5750
6014
  "WAITING_CAPACITY",
5751
6015
  "PARKED_CAPACITY",
5752
6016
  "UNKNOWN"
5753
- ].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !text(observation.runtimeFingerprint, 128) || !/^[a-f0-9]{64}$/.test(observation.runtimeFingerprint))
6017
+ ].includes(String(observation.status)) || !counter(observation.sampledAtMs) || !counter(observation.validUntilMs) || observation.validUntilMs < observation.sampledAtMs || !counter(observation.journalRevision) || !text2(observation.runtimeFingerprint, 128) || !/^[a-f0-9]{64}$/.test(observation.runtimeFingerprint))
5754
6018
  fail2();
5755
6019
  }
5756
6020
  sessions.add(session.id);
@@ -5760,7 +6024,7 @@ function validateControllerSnapshot(value) {
5760
6024
  let previous = 0;
5761
6025
  for (const event of value.events) {
5762
6026
  fields(event, ["sequence", "session", "generation", "kind"]);
5763
- if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !id(event.session) || !id(event.generation) || !["acquired", "renewed", "released", "expired", "invalidated", "observed"].includes(
6027
+ if (!counter(event.sequence) || event.sequence <= previous || event.sequence >= value.nextSequence || !id2(event.session) || !id2(event.generation) || !["acquired", "renewed", "released", "expired", "invalidated", "observed"].includes(
5764
6028
  String(event.kind)
5765
6029
  ))
5766
6030
  fail2();
@@ -5769,20 +6033,20 @@ function validateControllerSnapshot(value) {
5769
6033
  if (Buffer.byteLength(JSON.stringify(value.events)) > 262144 || Buffer.byteLength(JSON.stringify(value)) + 1 > CONTROLLER_SNAPSHOT_BYTES)
5770
6034
  fail2();
5771
6035
  }
5772
- var import_node_crypto13, import_node_fs14, import_node_path18, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
6036
+ var import_node_crypto13, import_node_fs14, import_node_path19, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
5773
6037
  var init_controller_store = __esm({
5774
6038
  "src/core/controller-store.ts"() {
5775
6039
  "use strict";
5776
6040
  import_node_crypto13 = require("crypto");
5777
6041
  import_node_fs14 = __toESM(require("fs"));
5778
- import_node_path18 = __toESM(require("path"));
6042
+ import_node_path19 = __toESM(require("path"));
5779
6043
  init_atomic_file();
5780
6044
  CONTROLLER_SNAPSHOT_BYTES = 1048576;
5781
6045
  ControllerStore = class {
5782
6046
  constructor(directory, write = writeFileAtomically) {
5783
6047
  this.directory = directory;
5784
6048
  this.write = write;
5785
- this.file = import_node_path18.default.join(directory, "snapshot.json");
6049
+ this.file = import_node_path19.default.join(directory, "snapshot.json");
5786
6050
  }
5787
6051
  file;
5788
6052
  read() {
@@ -5862,10 +6126,10 @@ var init_controller_store = __esm({
5862
6126
 
5863
6127
  // src/core/controller-server.ts
5864
6128
  function privateDirectory(directory) {
5865
- const absolute = import_node_path19.default.resolve(directory);
5866
- let current = import_node_path19.default.parse(absolute).root;
5867
- for (const part of absolute.slice(current.length).split(import_node_path19.default.sep)) {
5868
- current = import_node_path19.default.join(current, part);
6129
+ const absolute2 = import_node_path20.default.resolve(directory);
6130
+ let current = import_node_path20.default.parse(absolute2).root;
6131
+ for (const part of absolute2.slice(current.length).split(import_node_path20.default.sep)) {
6132
+ current = import_node_path20.default.join(current, part);
5869
6133
  try {
5870
6134
  const stat2 = import_node_fs15.default.lstatSync(current);
5871
6135
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
@@ -5875,7 +6139,7 @@ function privateDirectory(directory) {
5875
6139
  import_node_fs15.default.mkdirSync(current, { mode: 448 });
5876
6140
  }
5877
6141
  }
5878
- const stat = import_node_fs15.default.lstatSync(absolute);
6142
+ const stat = import_node_fs15.default.lstatSync(absolute2);
5879
6143
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
5880
6144
  throw new Error("Controller directory is not private.");
5881
6145
  }
@@ -5891,10 +6155,10 @@ function validateOwnedFile(file, socket) {
5891
6155
  }
5892
6156
  }
5893
6157
  async function runController(options) {
5894
- const socketPath = import_node_path19.default.join(options.directory, "control.sock");
6158
+ const socketPath = import_node_path20.default.join(options.directory, "control.sock");
5895
6159
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
5896
6160
  privateDirectory(options.directory);
5897
- const lockPath = import_node_path19.default.join(options.directory, "owner.lock");
6161
+ const lockPath = import_node_path20.default.join(options.directory, "owner.lock");
5898
6162
  try {
5899
6163
  const stat = import_node_fs15.default.lstatSync(lockPath);
5900
6164
  if (!stat.isFile() || stat.uid !== process.getuid?.())
@@ -6197,13 +6461,13 @@ async function runController(options) {
6197
6461
  }
6198
6462
  });
6199
6463
  }
6200
- var import_node_fs15, import_node_net2, import_node_path19, FRAME_BYTES;
6464
+ var import_node_fs15, import_node_net2, import_node_path20, FRAME_BYTES;
6201
6465
  var init_controller_server = __esm({
6202
6466
  "src/core/controller-server.ts"() {
6203
6467
  "use strict";
6204
6468
  import_node_fs15 = __toESM(require("fs"));
6205
6469
  import_node_net2 = __toESM(require("net"));
6206
- import_node_path19 = __toESM(require("path"));
6470
+ import_node_path20 = __toESM(require("path"));
6207
6471
  init_controller_monitor();
6208
6472
  init_controller_protocol();
6209
6473
  init_controller_sessions();
@@ -6219,7 +6483,7 @@ __export(controller_exports, {
6219
6483
  runControllerCommand: () => runControllerCommand
6220
6484
  });
6221
6485
  async function runControllerCommand(method, options, repo) {
6222
- const directory = import_node_path20.default.join(DEVROUTER_HOME, "controller");
6486
+ const directory = import_node_path21.default.join(DEVROUTER_HOME, "controller");
6223
6487
  try {
6224
6488
  if (method === "run") {
6225
6489
  const controller = new AbortController();
@@ -6244,7 +6508,7 @@ async function runControllerCommand(method, options, repo) {
6244
6508
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
6245
6509
  await controllerRequest(
6246
6510
  directory,
6247
- { method, ...fields2, ...repo ? { path: import_node_path20.default.resolve(repo) } : {} },
6511
+ { method, ...fields2, ...repo ? { path: import_node_path21.default.resolve(repo) } : {} },
6248
6512
  (value) => process.stdout.write(`${JSON.stringify(value)}
6249
6513
  `)
6250
6514
  );
@@ -6256,11 +6520,11 @@ async function runControllerCommand(method, options, repo) {
6256
6520
  process.exitCode = 1;
6257
6521
  }
6258
6522
  }
6259
- var import_node_path20;
6523
+ var import_node_path21;
6260
6524
  var init_controller = __esm({
6261
6525
  "src/commands/controller.ts"() {
6262
6526
  "use strict";
6263
- import_node_path20 = __toESM(require("path"));
6527
+ import_node_path21 = __toESM(require("path"));
6264
6528
  init_controller_binding();
6265
6529
  init_controller_client();
6266
6530
  init_controller_observation();
@@ -6308,13 +6572,13 @@ function replaceDevrouterSection(content) {
6308
6572
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
6309
6573
  }
6310
6574
  function writeRepoFile(repoPath, relPath, content) {
6311
- const absolutePath = (0, import_node_path21.join)(repoPath, relPath);
6312
- (0, import_node_fs16.mkdirSync)((0, import_node_path21.dirname)(absolutePath), { recursive: true });
6575
+ const absolutePath = (0, import_node_path22.join)(repoPath, relPath);
6576
+ (0, import_node_fs16.mkdirSync)((0, import_node_path22.dirname)(absolutePath), { recursive: true });
6313
6577
  (0, import_node_fs16.writeFileSync)(absolutePath, content, "utf-8");
6314
6578
  return absolutePath;
6315
6579
  }
6316
6580
  function ensureAgentsMdSection(repoPath) {
6317
- const filePath = (0, import_node_path21.join)(repoPath, AGENTS_MD);
6581
+ const filePath = (0, import_node_path22.join)(repoPath, AGENTS_MD);
6318
6582
  if ((0, import_node_fs16.existsSync)(filePath)) {
6319
6583
  const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
6320
6584
  if (content.includes(DEVROUTER_SENTINEL)) {
@@ -6339,12 +6603,12 @@ function ensureSkillFile(repoPath) {
6339
6603
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
6340
6604
  return { path: filePath, written: true };
6341
6605
  }
6342
- var import_node_fs16, import_node_path21, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
6606
+ var import_node_fs16, import_node_path22, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
6343
6607
  var init_agents_md = __esm({
6344
6608
  "src/core/agents-md.ts"() {
6345
6609
  "use strict";
6346
6610
  import_node_fs16 = require("fs");
6347
- import_node_path21 = require("path");
6611
+ import_node_path22 = require("path");
6348
6612
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
6349
6613
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
6350
6614
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -6877,13 +7141,14 @@ function buildOnboardingPrompt(options = {}) {
6877
7141
  `- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
6878
7142
  "- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
6879
7143
  "- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
7144
+ "- After a drained interrupted ensure, manual exec can use an exactly proven retained Devsy runtime with the plain local Docker command. Revalidate identity for each tooling command until later preparation reconciles startup; preserve configuration, data and interrupted history. This does not repair absent stop baselines or provide OOM protection.",
6880
7145
  "- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not prevent OOM or activate capacity-managed recovery.",
6881
7146
  "- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
6882
7147
  "- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
6883
7148
  "- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
6884
7149
  "- Managed adapters may use devrouter-process ensure --prepare-command <command> for synchronous dependency preparation under the process lock before app launch. Unchanged owned processes skip preparation; it participates in default fingerprints and must not daemonize or detach.",
6885
7150
  "- Host-generated Compose inputs may use managedRuntime.devcontainer.prepareCommand as literal argv. Ensure runs it once from the checkout root before Compose inspection, with a 60-second bound and no implicit shell. Keep .devrouter.yml unchanged. Diagnostics never run it; changed warm mounts require separate verification.",
6886
- "- Exact managed Devsy stop validates complete retained container membership, ownership and configuration before stopping residual services after a stopped primary. Unknown evidence blocks cleanup; original provider failures remain errors even after successful residual cleanup.",
7151
+ "- Managed Devsy startup on supported local Unix Docker endpoints captures exact stop ownership before application readiness. With that baseline, canonical stop tolerates changed or missing repository configuration and preserves containers and volumes. Older records without a baseline retain configuration-dependent stop; installing a new CLI does not establish historical ownership. Invalid or changed ownership never falls back. Do not replay destructive bootstrap hooks to obtain a baseline.",
6887
7152
  "- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
6888
7153
  "- `devrouter workspace cleanup --repo . --inactive-for 30d --check-merged --json` is report-only and never mutates the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. It reports ownership, workspace runtime registration, runtime state (`running|stopped|busy|not-found|absent|unknown`), checkout, advisory activity, route, and integration evidence. Local DevPod/Devsy list/status checks always run; `--check-merged` alone enables read-only origin and matching GitHub/GitLab checks. Treat `not-found` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit `gc`/`down` can remove exact stale registration only after expected-ID `NotFound` proof and ownership revalidation. Runtime `lastUsed` remains advisory.",
6889
7154
  '- `--measure-size` adds per-workspace storage consumption and stays read-only, but walks each worktree and runs `docker ps` / `docker inspect --size`, so leave it off for a quick report. Each row reports `worktree` and `containerWritable` bytes, which are reclaimable; `imageShared` bytes, which are not, because those image layers are shared with other containers and overlap across rows; and `reclaimable`, the sum of the reclaimable fields. Never add `imageShared` across rows. Any figure that cannot be trusted reports `{"status": "unknown", "reason": ...}` rather than zero, and a workspace with no container reports a measured `0`.',
@@ -7618,7 +7883,7 @@ function readPromptDirectory(promptDirPath) {
7618
7883
  continue;
7619
7884
  }
7620
7885
  const version = normalizeVersion(basename);
7621
- const promptPath = import_node_path22.default.join(promptDirPath, file.name);
7886
+ const promptPath = import_node_path23.default.join(promptDirPath, file.name);
7622
7887
  const prompt = import_node_fs17.default.readFileSync(promptPath, "utf-8").trim();
7623
7888
  releases.push({ version, prompt, promptPath });
7624
7889
  }
@@ -7640,17 +7905,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
7640
7905
  }
7641
7906
  function resolvePromptDirectory(explicitPath) {
7642
7907
  if (explicitPath) {
7643
- return import_node_path22.default.resolve(explicitPath);
7908
+ return import_node_path23.default.resolve(explicitPath);
7644
7909
  }
7645
- const entryFile = process.argv[1] ? import_node_path22.default.resolve(process.argv[1]) : __filename;
7646
- const entryDir = import_node_path22.default.dirname(entryFile);
7647
- const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ? import_node_path22.default.dirname(import_node_fs17.default.realpathSync(entryFile)) : entryDir;
7910
+ const entryFile = process.argv[1] ? import_node_path23.default.resolve(process.argv[1]) : __filename;
7911
+ const entryDir = import_node_path23.default.dirname(entryFile);
7912
+ const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ? import_node_path23.default.dirname(import_node_fs17.default.realpathSync(entryFile)) : entryDir;
7648
7913
  const candidates = [
7649
- import_node_path22.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
7650
- import_node_path22.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
7651
- import_node_path22.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
7652
- import_node_path22.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
7653
- import_node_path22.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
7914
+ import_node_path23.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
7915
+ import_node_path23.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
7916
+ import_node_path23.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
7917
+ import_node_path23.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
7918
+ import_node_path23.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
7654
7919
  ];
7655
7920
  const existing = candidates.find((candidate) => import_node_fs17.default.existsSync(candidate));
7656
7921
  return existing ?? candidates[0];
@@ -7668,12 +7933,12 @@ function loadUpgradeCatalog(options = {}) {
7668
7933
  releases
7669
7934
  };
7670
7935
  }
7671
- var import_node_fs17, import_node_path22, SEMVER_RE, UPGRADE_PROMPTS_DIR;
7936
+ var import_node_fs17, import_node_path23, SEMVER_RE, UPGRADE_PROMPTS_DIR;
7672
7937
  var init_upgrade = __esm({
7673
7938
  "src/core/upgrade.ts"() {
7674
7939
  "use strict";
7675
7940
  import_node_fs17 = __toESM(require("fs"));
7676
- import_node_path22 = __toESM(require("path"));
7941
+ import_node_path23 = __toESM(require("path"));
7677
7942
  init_repo_config();
7678
7943
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
7679
7944
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -7786,7 +8051,7 @@ function nativeAssetName(platform, arch) {
7786
8051
  return void 0;
7787
8052
  }
7788
8053
  function managedBinaryPath(cacheRoot, asset) {
7789
- return import_node_path23.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
8054
+ return import_node_path24.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
7790
8055
  }
7791
8056
  function hashOpenFile(file) {
7792
8057
  const digest3 = (0, import_node_crypto14.createHash)("sha256");
@@ -7828,7 +8093,7 @@ function resolvedOptions(options) {
7828
8093
  platform: options.platform ?? process.platform,
7829
8094
  arch: options.arch ?? process.arch,
7830
8095
  versionOutput: options.versionOutput ?? installedVersionOutput(),
7831
- cacheRoot: options.cacheRoot ?? import_node_path23.default.join(CACHE_DIR, "devsy", "agents"),
8096
+ cacheRoot: options.cacheRoot ?? import_node_path24.default.join(CACHE_DIR, "devsy", "agents"),
7832
8097
  lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
7833
8098
  assets: options.assets ?? DEVSY_AGENT_ASSETS,
7834
8099
  nativeAssetName: options.nativeAssetName
@@ -7953,11 +8218,11 @@ function fsyncDirectory2(directory) {
7953
8218
  }
7954
8219
  }
7955
8220
  async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
7956
- const directory = import_node_path23.default.dirname(binaryPath);
8221
+ const directory = import_node_path24.default.dirname(binaryPath);
7957
8222
  import_node_fs18.default.mkdirSync(directory, { recursive: true, mode: 448 });
7958
- const temporaryPath = import_node_path23.default.join(
8223
+ const temporaryPath = import_node_path24.default.join(
7959
8224
  directory,
7960
- `.${import_node_path23.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
8225
+ `.${import_node_path24.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
7961
8226
  );
7962
8227
  let handle;
7963
8228
  try {
@@ -8041,7 +8306,7 @@ async function prepareDevsyAgent(options = {}) {
8041
8306
  throw new DevsyAgentReadinessError(before);
8042
8307
  }
8043
8308
  const lock = options.withLock ?? ((operation) => {
8044
- import_node_fs18.default.mkdirSync(import_node_path23.default.dirname(resolved.lockPath), { recursive: true });
8309
+ import_node_fs18.default.mkdirSync(import_node_path24.default.dirname(resolved.lockPath), { recursive: true });
8045
8310
  return withFileLock(
8046
8311
  resolved.lockPath,
8047
8312
  {
@@ -8086,14 +8351,14 @@ async function prepareDevsyAgent(options = {}) {
8086
8351
  };
8087
8352
  });
8088
8353
  }
8089
- var import_node_child_process10, import_node_crypto14, import_node_fs18, import_node_path23, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
8354
+ var import_node_child_process10, import_node_crypto14, import_node_fs18, import_node_path24, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
8090
8355
  var init_devsy_agent = __esm({
8091
8356
  "src/core/devsy-agent.ts"() {
8092
8357
  "use strict";
8093
8358
  import_node_child_process10 = require("child_process");
8094
8359
  import_node_crypto14 = require("crypto");
8095
8360
  import_node_fs18 = __toESM(require("fs"));
8096
- import_node_path23 = __toESM(require("path"));
8361
+ import_node_path24 = __toESM(require("path"));
8097
8362
  init_file_lock();
8098
8363
  init_router();
8099
8364
  SUPPORTED_DEVSY_VERSION = "1.16.2";
@@ -8121,7 +8386,7 @@ var init_devsy_agent = __esm({
8121
8386
  this.name = "DevsyAgentReadinessError";
8122
8387
  }
8123
8388
  };
8124
- DEVSY_AGENT_CACHE_LOCK = import_node_path23.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
8389
+ DEVSY_AGENT_CACHE_LOCK = import_node_path24.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
8125
8390
  DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
8126
8391
  GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
8127
8392
  }
@@ -8139,11 +8404,11 @@ function adapterFingerprint(adapter) {
8139
8404
  return (0, import_node_crypto15.createHash)("sha256").update(adapter).digest("hex");
8140
8405
  }
8141
8406
  function resolveManagedPostStartPlan(repoPath) {
8142
- const adapterPath = import_node_path24.default.join(repoPath, MANAGED_ADAPTER_PATH);
8407
+ const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
8143
8408
  const adapterBytes = readRegularFileBytes(adapterPath);
8144
8409
  const adapterText = adapterBytes?.toString("utf-8");
8145
- const dockerfilePath = import_node_path24.default.join(repoPath, ".devcontainer", "Dockerfile");
8146
- const devcontainerPath = import_node_path24.default.join(repoPath, ".devcontainer", "devcontainer.json");
8410
+ const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
8411
+ const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
8147
8412
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
8148
8413
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
8149
8414
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -8225,14 +8490,14 @@ function runManagedProcessAction(options) {
8225
8490
  }
8226
8491
  return "drifted";
8227
8492
  }
8228
- var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path24, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
8493
+ var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path25, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
8229
8494
  var init_managed_post_start = __esm({
8230
8495
  "src/core/managed-post-start.ts"() {
8231
8496
  "use strict";
8232
8497
  import_node_child_process11 = require("child_process");
8233
8498
  import_node_crypto15 = require("crypto");
8234
8499
  import_node_fs19 = __toESM(require("fs"));
8235
- import_node_path24 = __toESM(require("path"));
8500
+ import_node_path25 = __toESM(require("path"));
8236
8501
  init_devcontainer_config();
8237
8502
  MANAGED_MARKER = "devrouter:managed devcontainer";
8238
8503
  MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
@@ -8272,19 +8537,19 @@ function formatPublishedPort(serviceName, value) {
8272
8537
  if (typeof value === "string") {
8273
8538
  return `${serviceName}: ${value}`;
8274
8539
  }
8275
- const record3 = asRecord(value);
8276
- if (!record3) {
8540
+ const record4 = asRecord(value);
8541
+ if (!record4) {
8277
8542
  return void 0;
8278
8543
  }
8279
- const target = record3.target;
8280
- const published = record3.published;
8544
+ const target = record4.target;
8545
+ const published = record4.published;
8281
8546
  if (published !== void 0 && target !== void 0) {
8282
8547
  return `${serviceName}: ${String(published)}:${String(target)}`;
8283
8548
  }
8284
8549
  if (target !== void 0) {
8285
8550
  return `${serviceName}: target ${String(target)}`;
8286
8551
  }
8287
- return `${serviceName}: ${JSON.stringify(record3)}`;
8552
+ return `${serviceName}: ${JSON.stringify(record4)}`;
8288
8553
  }
8289
8554
  function upstreamHost(upstream) {
8290
8555
  const separator = upstream.lastIndexOf(":");
@@ -8360,11 +8625,11 @@ function routedProxyApps(config) {
8360
8625
  );
8361
8626
  }
8362
8627
  function buildDevcontainerChecks(repoPath, config, workspace) {
8363
- const devcontainerDir = import_node_path25.default.join(repoPath, ".devcontainer");
8628
+ const devcontainerDir = import_node_path26.default.join(repoPath, ".devcontainer");
8364
8629
  if (!import_node_fs20.default.existsSync(devcontainerDir)) {
8365
8630
  return [];
8366
8631
  }
8367
- const compose = inspectCompose(import_node_path25.default.join(devcontainerDir, "docker-compose.yml"), workspace);
8632
+ const compose = inspectCompose(import_node_path26.default.join(devcontainerDir, "docker-compose.yml"), workspace);
8368
8633
  const checks = [];
8369
8634
  if (compose.parseError) {
8370
8635
  checks.push({
@@ -8391,7 +8656,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
8391
8656
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
8392
8657
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
8393
8658
  });
8394
- const dockerfilePath = import_node_path25.default.join(devcontainerDir, "Dockerfile");
8659
+ const dockerfilePath = import_node_path26.default.join(devcontainerDir, "Dockerfile");
8395
8660
  const dockerfileExists = import_node_fs20.default.existsSync(dockerfilePath);
8396
8661
  const dockerfile = dockerfileExists ? import_node_fs20.default.readFileSync(dockerfilePath, "utf-8") : "";
8397
8662
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
@@ -8448,12 +8713,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
8448
8713
  });
8449
8714
  return checks;
8450
8715
  }
8451
- var import_node_fs20, import_node_path25, import_yaml4;
8716
+ var import_node_fs20, import_node_path26, import_yaml4;
8452
8717
  var init_devcontainer_diagnostics = __esm({
8453
8718
  "src/core/devcontainer-diagnostics.ts"() {
8454
8719
  "use strict";
8455
8720
  import_node_fs20 = __toESM(require("fs"));
8456
- import_node_path25 = __toESM(require("path"));
8721
+ import_node_path26 = __toESM(require("path"));
8457
8722
  import_yaml4 = __toESM(require("yaml"));
8458
8723
  init_managed_post_start();
8459
8724
  }
@@ -9393,11 +9658,22 @@ function listDevsyWorkspaces() {
9393
9658
  if (typeof candidate.id !== "string" || !source || typeof source.localFolder !== "string") {
9394
9659
  throw new Error("devsy workspace list returned a workspace without id/source.localFolder.");
9395
9660
  }
9661
+ if (candidate.uid !== void 0 && typeof candidate.uid !== "string" || source.container !== void 0 && typeof source.container !== "string") {
9662
+ throw new Error("Devsy workspace list returned invalid provider identity.");
9663
+ }
9396
9664
  const workspace = {
9397
9665
  id: candidate.id,
9398
- source: { localFolder: source.localFolder },
9666
+ source: {
9667
+ localFolder: source.localFolder,
9668
+ ...typeof source.container === "string" ? { container: source.container } : {}
9669
+ },
9670
+ ...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
9399
9671
  ...typeof candidate.context === "string" ? { context: candidate.context } : {}
9400
9672
  };
9673
+ const provider = candidate.provider;
9674
+ if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
9675
+ if (typeof provider?.options?.DOCKER_PATH?.value === "string")
9676
+ workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
9401
9677
  if ("lastUsed" in candidate) {
9402
9678
  if (typeof candidate.lastUsed === "string") {
9403
9679
  workspace.lastUsed = candidate.lastUsed;
@@ -9455,6 +9731,16 @@ function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
9455
9731
  }
9456
9732
  return exact[0] ? { status: "owned", workspace: exact[0] } : { status: "absent" };
9457
9733
  }
9734
+ function selectDevsyWorkspace(workspaces, repoPath) {
9735
+ const matches = workspaces.filter(
9736
+ (workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
9737
+ );
9738
+ if (matches.length > 1) {
9739
+ const ids = matches.map((match) => match.id).join(", ");
9740
+ throw new Error(`Multiple Devsy workspaces reference '${repoPath}': ${ids}`);
9741
+ }
9742
+ return matches[0];
9743
+ }
9458
9744
  var import_node_child_process13;
9459
9745
  var init_devsy_workspaces = __esm({
9460
9746
  "src/core/devsy-workspaces.ts"() {
@@ -9662,20 +9948,20 @@ function resetWorkspaceRuntimeCaches() {
9662
9948
  cachedRuntime = void 0;
9663
9949
  cachedSnapshots = void 0;
9664
9950
  }
9665
- var import_node_child_process14, import_node_fs23, import_node_path26, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
9951
+ var import_node_child_process14, import_node_fs23, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
9666
9952
  var init_workspace_runtime = __esm({
9667
9953
  "src/core/workspace-runtime.ts"() {
9668
9954
  "use strict";
9669
9955
  import_node_child_process14 = require("child_process");
9670
9956
  import_node_fs23 = __toESM(require("fs"));
9671
- import_node_path26 = __toESM(require("path"));
9957
+ import_node_path27 = __toESM(require("path"));
9672
9958
  init_atomic_file();
9673
9959
  init_devpod_registry();
9674
9960
  init_devsy_workspaces();
9675
9961
  init_router();
9676
9962
  init_workspace();
9677
9963
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
9678
- RUNTIME_CONFIG_FILE = import_node_path26.default.join(DEVROUTER_HOME, "workspace-runtime.json");
9964
+ RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
9679
9965
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
9680
9966
  UnsupportedWorkspaceRuntimeError = class extends Error {
9681
9967
  };
@@ -9753,7 +10039,7 @@ function parseMinimumNodeMajor(value) {
9753
10039
  return Number(match[1]);
9754
10040
  }
9755
10041
  function readPackageJson(repoPath) {
9756
- const packagePath = import_node_path27.default.join(repoPath, "package.json");
10042
+ const packagePath = import_node_path28.default.join(repoPath, "package.json");
9757
10043
  if (!import_node_fs24.default.existsSync(packagePath)) {
9758
10044
  return void 0;
9759
10045
  }
@@ -9942,13 +10228,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
9942
10228
  checks.push(nodeToolchainCheck(repoPath));
9943
10229
  return checks;
9944
10230
  }
9945
- var import_node_child_process15, import_node_fs24, import_node_path27, DEVSY_DISABLE_TELEMETRY;
10231
+ var import_node_child_process15, import_node_fs24, import_node_path28, DEVSY_DISABLE_TELEMETRY;
9946
10232
  var init_tool_diagnostics = __esm({
9947
10233
  "src/core/tool-diagnostics.ts"() {
9948
10234
  "use strict";
9949
10235
  import_node_child_process15 = require("child_process");
9950
10236
  import_node_fs24 = __toESM(require("fs"));
9951
- import_node_path27 = __toESM(require("path"));
10237
+ import_node_path28 = __toESM(require("path"));
9952
10238
  init_devsy_agent();
9953
10239
  init_workspace_runtime();
9954
10240
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -10054,6 +10340,54 @@ var init_devpod_workspaces = __esm({
10054
10340
  }
10055
10341
  });
10056
10342
 
10343
+ // src/core/managed-compose-population.ts
10344
+ function proveManagedComposePopulation(options) {
10345
+ const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers: containers2 } = options;
10346
+ const services = /* @__PURE__ */ new Set();
10347
+ for (const container of containers2) {
10348
+ const service = container.labels["com.docker.compose.service"] ?? "";
10349
+ if (!plan.nativeRunServices.includes(service) || services.has(service) || container.labels["com.docker.compose.project"] !== composeProject || !sameWorkspacePath(
10350
+ container.labels["com.docker.compose.project.working_dir"] ?? "",
10351
+ plan.composeDirectory
10352
+ )) {
10353
+ throw new Error("Managed stop found unexpected or duplicate project membership.");
10354
+ }
10355
+ services.add(service);
10356
+ const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
10357
+ if (files.some((file) => !file || !import_node_path29.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path29.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
10358
+ throw new Error("Managed stop Compose file identity changed.");
10359
+ }
10360
+ for (const file of files.slice(plan.composeFiles.length)) {
10361
+ if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
10362
+ import_node_path29.default.basename(file)
10363
+ ) || import_node_fs25.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs25.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
10364
+ throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
10365
+ }
10366
+ }
10367
+ }
10368
+ if (plan.desiredServices.some((service) => !services.has(service))) {
10369
+ throw new Error("Managed stop cannot prove the complete retained service population.");
10370
+ }
10371
+ const primary = containers2.find(
10372
+ (c) => c.labels["com.docker.compose.service"] === plan.primaryService
10373
+ );
10374
+ if (primary?.mounts.filter(
10375
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
10376
+ ).length !== 1) {
10377
+ throw new Error("Managed stop cannot prove the exact primary workspace mount.");
10378
+ }
10379
+ return primary;
10380
+ }
10381
+ var import_node_fs25, import_node_path29;
10382
+ var init_managed_compose_population = __esm({
10383
+ "src/core/managed-compose-population.ts"() {
10384
+ "use strict";
10385
+ import_node_fs25 = __toESM(require("fs"));
10386
+ import_node_path29 = __toESM(require("path"));
10387
+ init_workspace();
10388
+ }
10389
+ });
10390
+
10057
10391
  // src/core/reliability-context.ts
10058
10392
  function claimLifecycleEffect() {
10059
10393
  claim?.();
@@ -10065,81 +10399,6 @@ var init_reliability_context = __esm({
10065
10399
  }
10066
10400
  });
10067
10401
 
10068
- // src/core/route-publication.ts
10069
- function routedAppsFromConfig(config) {
10070
- return config.apps.filter((app) => app.kind !== "dependency");
10071
- }
10072
- function configuredProxyAppsFromConfig(config) {
10073
- return routedAppsFromConfig(config).filter(
10074
- (app) => app.runtime === "proxy"
10075
- );
10076
- }
10077
- function proxyAppsFromConfig(config) {
10078
- const routedApps = routedAppsFromConfig(config);
10079
- const proxyApps = configuredProxyAppsFromConfig(config);
10080
- const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
10081
- if (unsupported.length > 0) {
10082
- throw new Error(
10083
- `Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
10084
- );
10085
- }
10086
- return proxyApps;
10087
- }
10088
- async function ensureRouteInfrastructure(apps, options = {}) {
10089
- claimLifecycleEffect();
10090
- ensureRouterFiles();
10091
- await ensureNetwork(DEVNET_NAME);
10092
- claimLifecycleEffect();
10093
- const tlsCoverage = await ensureTLSHostsCovered(
10094
- apps.map((app) => app.host),
10095
- options
10096
- );
10097
- for (const app of apps) {
10098
- if (app.protocol === "tcp") {
10099
- if (!isTLSEnabled()) {
10100
- throw new Error(
10101
- `TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
10102
- );
10103
- }
10104
- activateTcpProtocol(app.tcpProtocol);
10105
- }
10106
- }
10107
- claimLifecycleEffect();
10108
- startRouterStack();
10109
- return tlsCoverage;
10110
- }
10111
- async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
10112
- const apps = proxyAppsFromConfig(config);
10113
- const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
10114
- const routes = apps.map((app) => {
10115
- const upstream = parseUpstream(app.upstream);
10116
- return {
10117
- name: app.name,
10118
- host: app.host,
10119
- protocol: app.protocol,
10120
- tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
10121
- repoPath,
10122
- port: upstream.port,
10123
- mode: "proxy",
10124
- upstreamHost: upstream.upstreamHost,
10125
- workspace
10126
- };
10127
- });
10128
- claimLifecycleEffect();
10129
- replaceHostRoutesForRepo(repoPath, routes);
10130
- return { routes, tlsRefreshed: tlsCoverage.refreshed };
10131
- }
10132
- var init_route_publication = __esm({
10133
- "src/core/route-publication.ts"() {
10134
- "use strict";
10135
- init_docker();
10136
- init_host_routes();
10137
- init_reliability_context();
10138
- init_router();
10139
- init_tls();
10140
- }
10141
- });
10142
-
10143
10402
  // src/core/workspace-ownership.ts
10144
10403
  function commandError(command, repoPath, stderr) {
10145
10404
  return new Error(
@@ -10155,7 +10414,7 @@ function resolveGitCommonDir(repoPath) {
10155
10414
  if (result.status !== 0 || !output2) {
10156
10415
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
10157
10416
  }
10158
- return comparableWorkspacePath(import_node_path28.default.isAbsolute(output2) ? output2 : import_node_path28.default.resolve(repoPath, output2));
10417
+ return comparableWorkspacePath(import_node_path30.default.isAbsolute(output2) ? output2 : import_node_path30.default.resolve(repoPath, output2));
10159
10418
  }
10160
10419
  function resolveGitTopLevel(repoPath) {
10161
10420
  const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -10206,7 +10465,7 @@ function listGitWorktrees(repoPath) {
10206
10465
  return worktrees;
10207
10466
  }
10208
10467
  function ownershipDirectory(repoPath) {
10209
- return import_node_path28.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
10468
+ return import_node_path30.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
10210
10469
  }
10211
10470
  function validateWorkspace(value, label) {
10212
10471
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -10234,7 +10493,7 @@ function validateRecord(value, expectedWorkspace) {
10234
10493
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
10235
10494
  );
10236
10495
  }
10237
- if (typeof candidate.worktreePath !== "string" || !import_node_path28.default.isAbsolute(candidate.worktreePath)) {
10496
+ if (typeof candidate.worktreePath !== "string" || !import_node_path30.default.isAbsolute(candidate.worktreePath)) {
10238
10497
  throw new Error("invalid workspace ownership worktreePath");
10239
10498
  }
10240
10499
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -10252,7 +10511,7 @@ function validateRecord(value, expectedWorkspace) {
10252
10511
  };
10253
10512
  }
10254
10513
  function recordPath(repoPath, workspace) {
10255
- return import_node_path28.default.join(
10514
+ return import_node_path30.default.join(
10256
10515
  ownershipDirectory(repoPath),
10257
10516
  `${validateWorkspace(workspace, "workspace")}.json`
10258
10517
  );
@@ -10260,7 +10519,7 @@ function recordPath(repoPath, workspace) {
10260
10519
  function readRecordFile(filePath, expectedWorkspace) {
10261
10520
  let parsed;
10262
10521
  try {
10263
- parsed = JSON.parse(import_node_fs25.default.readFileSync(filePath, "utf-8"));
10522
+ parsed = JSON.parse(import_node_fs26.default.readFileSync(filePath, "utf-8"));
10264
10523
  } catch (error) {
10265
10524
  if (error instanceof SyntaxError) {
10266
10525
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -10285,7 +10544,7 @@ function listWorkspaceOwnership(repoPath) {
10285
10544
  function listWorkspaceOwnershipInDirectory(directory) {
10286
10545
  let entries;
10287
10546
  try {
10288
- entries = import_node_fs25.default.readdirSync(directory, { withFileTypes: true });
10547
+ entries = import_node_fs26.default.readdirSync(directory, { withFileTypes: true });
10289
10548
  } catch (error) {
10290
10549
  if (error.code === "ENOENT") return [];
10291
10550
  throw error;
@@ -10293,24 +10552,24 @@ function listWorkspaceOwnershipInDirectory(directory) {
10293
10552
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
10294
10553
  const workspace = entry.name.slice(0, -".json".length);
10295
10554
  validateWorkspace(workspace, "filename");
10296
- return readRecordFile(import_node_path28.default.join(directory, entry.name), workspace);
10555
+ return readRecordFile(import_node_path30.default.join(directory, entry.name), workspace);
10297
10556
  });
10298
10557
  }
10299
10558
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
10300
10559
  const workspace = validateWorkspace(input2.workspace, "workspace");
10301
10560
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
10302
10561
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
10303
- const filePath = import_node_path28.default.join(directory, `${workspace}.json`);
10562
+ const filePath = import_node_path30.default.join(directory, `${workspace}.json`);
10304
10563
  const now = (/* @__PURE__ */ new Date()).toISOString();
10305
10564
  const records = listWorkspaceOwnershipInDirectory(directory);
10306
- const existing = records.find((record4) => record4.workspace === workspace);
10565
+ const existing = records.find((record5) => record5.workspace === workspace);
10307
10566
  if (existing && !sameWorkspacePath(existing.worktreePath, worktreePath)) {
10308
10567
  throw new Error(
10309
10568
  `Workspace '${workspace}' already belongs to '${existing.worktreePath}', refusing '${worktreePath}'.`
10310
10569
  );
10311
10570
  }
10312
10571
  const pathOwner = records.find(
10313
- (record4) => record4.workspace !== workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
10572
+ (record5) => record5.workspace !== workspace && sameWorkspacePath(record5.worktreePath, worktreePath)
10314
10573
  );
10315
10574
  if (pathOwner) {
10316
10575
  throw new Error(
@@ -10322,7 +10581,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
10322
10581
  `Workspace '${workspace}' already owns DevPod '${existing.devpodId}', refusing '${devpodId}'.`
10323
10582
  );
10324
10583
  }
10325
- const record3 = {
10584
+ const record4 = {
10326
10585
  version: OWNERSHIP_VERSION,
10327
10586
  workspace,
10328
10587
  worktreePath,
@@ -10331,14 +10590,14 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
10331
10590
  createdAt: existing?.createdAt ?? validateTimestamp(now, "createdAt"),
10332
10591
  updatedAt: validateTimestamp(now, "updatedAt")
10333
10592
  };
10334
- writeFileAtomically(filePath, `${JSON.stringify(record3, null, 2)}
10593
+ writeFileAtomically(filePath, `${JSON.stringify(record4, null, 2)}
10335
10594
  `);
10336
- return record3;
10595
+ return record4;
10337
10596
  }
10338
10597
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
10339
- const filePath = import_node_path28.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
10598
+ const filePath = import_node_path30.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
10340
10599
  try {
10341
- import_node_fs25.default.rmSync(filePath);
10600
+ import_node_fs26.default.rmSync(filePath);
10342
10601
  return true;
10343
10602
  } catch (error) {
10344
10603
  if (error.code === "ENOENT") return false;
@@ -10349,7 +10608,7 @@ function sameOwnershipRecord(left, right) {
10349
10608
  return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
10350
10609
  }
10351
10610
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
10352
- const filePath = import_node_path28.default.join(
10611
+ const filePath = import_node_path30.default.join(
10353
10612
  directory,
10354
10613
  `${validateWorkspace(expected.workspace, "workspace")}.json`
10355
10614
  );
@@ -10361,14 +10620,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
10361
10620
  throw error;
10362
10621
  }
10363
10622
  if (!sameOwnershipRecord(current, expected)) return "changed";
10364
- import_node_fs25.default.rmSync(filePath);
10623
+ import_node_fs26.default.rmSync(filePath);
10365
10624
  return "removed";
10366
10625
  }
10367
10626
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
10368
10627
  const directory = ownershipDirectory(repoPath);
10369
- import_node_fs25.default.mkdirSync(directory, { recursive: true });
10628
+ import_node_fs26.default.mkdirSync(directory, { recursive: true });
10370
10629
  return withFileLockSync(
10371
- import_node_path28.default.join(directory, ".lock"),
10630
+ import_node_path30.default.join(directory, ".lock"),
10372
10631
  {
10373
10632
  activity: "workspace ownership transaction",
10374
10633
  target: `'${repoPath}'`,
@@ -10401,7 +10660,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
10401
10660
  try {
10402
10661
  workspace = readPersistedWorkspace(worktree.path);
10403
10662
  } catch (error) {
10404
- if (!import_node_fs25.default.existsSync(worktree.path)) continue;
10663
+ if (!import_node_fs26.default.existsSync(worktree.path)) continue;
10405
10664
  throw error;
10406
10665
  }
10407
10666
  if (!workspace) continue;
@@ -10415,7 +10674,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
10415
10674
  }
10416
10675
  function claimConflict(workspace, devpodId, worktreePath, records, providerWorkspaces, persistedOwners, exactRecord) {
10417
10676
  const recordOwner = records.find(
10418
- (record3) => record3 !== exactRecord && (record3.workspace === workspace || record3.devpodId === devpodId)
10677
+ (record4) => record4 !== exactRecord && (record4.workspace === workspace || record4.devpodId === devpodId)
10419
10678
  );
10420
10679
  if (recordOwner) {
10421
10680
  return `workspace owner record '${recordOwner.workspace}' for '${recordOwner.worktreePath}'`;
@@ -10438,7 +10697,7 @@ function claimWorkspaceIdentity(repoPath, input2) {
10438
10697
  return withWorkspaceOwnershipTransaction(repoPath, (transaction) => {
10439
10698
  const records = transaction.list();
10440
10699
  const exactRecords = records.filter(
10441
- (record3) => sameWorkspacePath(record3.worktreePath, worktreePath)
10700
+ (record4) => sameWorkspacePath(record4.worktreePath, worktreePath)
10442
10701
  );
10443
10702
  if (exactRecords.length > 1) {
10444
10703
  throw new Error(
@@ -10552,11 +10811,11 @@ function removeWorkspaceOwnership(repoPath, workspace) {
10552
10811
  (transaction) => transaction.remove(workspace)
10553
10812
  );
10554
10813
  }
10555
- function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10814
+ function inspectWorkspaceOwnership(record4, worktrees, devpods) {
10556
10815
  const worktree = worktrees.find(
10557
- (candidate) => sameWorkspacePath(candidate.path, record3.worktreePath)
10816
+ (candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
10558
10817
  );
10559
- const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record3.devpodId, record3.worktreePath) : void 0;
10818
+ const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record4.devpodId, record4.worktreePath) : void 0;
10560
10819
  const devpodStatus = devpodOwnership?.status ?? "unknown";
10561
10820
  if (devpodStatus === "conflict") {
10562
10821
  return { ownerStatus: "conflict", devpodStatus, worktree };
@@ -10564,7 +10823,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10564
10823
  if (worktree?.locked) {
10565
10824
  return { ownerStatus: "locked", devpodStatus, worktree };
10566
10825
  }
10567
- if ((!worktree || worktree.prunable) && import_node_fs25.default.existsSync(record3.worktreePath)) {
10826
+ if ((!worktree || worktree.prunable) && import_node_fs26.default.existsSync(record4.worktreePath)) {
10568
10827
  return { ownerStatus: "conflict", devpodStatus, worktree };
10569
10828
  }
10570
10829
  if (!worktree || worktree.prunable) {
@@ -10577,7 +10836,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10577
10836
  return { ownerStatus: "conflict", devpodStatus, worktree };
10578
10837
  }
10579
10838
  return {
10580
- ownerStatus: persisted === record3.workspace ? "present" : "conflict",
10839
+ ownerStatus: persisted === record4.workspace ? "present" : "conflict",
10581
10840
  devpodStatus,
10582
10841
  worktree
10583
10842
  };
@@ -10585,23 +10844,231 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10585
10844
  function listMissingWorkspaceOwnership(repoPath) {
10586
10845
  const worktrees = listGitWorktrees(repoPath);
10587
10846
  return listWorkspaceOwnership(repoPath).filter(
10588
- (record3) => inspectWorkspaceOwnership(record3, worktrees, void 0).ownerStatus === "missing"
10847
+ (record4) => inspectWorkspaceOwnership(record4, worktrees, void 0).ownerStatus === "missing"
10589
10848
  );
10590
10849
  }
10591
- var import_node_child_process17, import_node_fs25, import_node_path28, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
10850
+ var import_node_child_process17, import_node_fs26, import_node_path30, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
10592
10851
  var init_workspace_ownership = __esm({
10593
10852
  "src/core/workspace-ownership.ts"() {
10594
10853
  "use strict";
10595
10854
  import_node_child_process17 = require("child_process");
10596
- import_node_fs25 = __toESM(require("fs"));
10597
- import_node_path28 = __toESM(require("path"));
10855
+ import_node_fs26 = __toESM(require("fs"));
10856
+ import_node_path30 = __toESM(require("path"));
10598
10857
  init_atomic_file();
10599
10858
  init_devpod_workspaces();
10600
10859
  init_file_lock();
10601
10860
  init_workspace();
10602
10861
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
10603
10862
  OWNERSHIP_VERSION = 1;
10604
- OWNERSHIP_DIR = import_node_path28.default.join("devrouter", "workspaces");
10863
+ OWNERSHIP_DIR = import_node_path30.default.join("devrouter", "workspaces");
10864
+ }
10865
+ });
10866
+
10867
+ // src/core/managed-stop-recovery.ts
10868
+ function registration(state) {
10869
+ resetWorkspaceRuntimeCaches();
10870
+ if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
10871
+ throw new Error("Stop provider changed.");
10872
+ const owner = inspectDevsyWorkspaceOwnership(
10873
+ listDevsyWorkspaces(),
10874
+ state.devpodId,
10875
+ state.repoPath
10876
+ );
10877
+ if (owner.status !== "owned") throw new Error("Stop requires one exact provider registration.");
10878
+ if (isLinkedWorktree(state.repoPath)) {
10879
+ const workspace = resolveWorktreeWorkspace(state.repoPath);
10880
+ const record4 = workspace ? readWorkspaceOwnership(state.repoPath, workspace) : void 0;
10881
+ if (!workspace || workspace !== state.workspace || !record4 || record4.devpodId !== state.devpodId || !sameWorkspacePath(record4.worktreePath, state.repoPath))
10882
+ throw new Error("Stop workspace identity changed.");
10883
+ resolveGitCommonDir(state.repoPath);
10884
+ } else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
10885
+ const value = owner.workspace;
10886
+ return {
10887
+ context: value.context ?? "",
10888
+ providerId: value.id,
10889
+ uid: value.uid ?? "",
10890
+ sourcePath: value.source.localFolder,
10891
+ sourceContainer: value.source.container ?? ""
10892
+ };
10893
+ }
10894
+ function compareText(left, right) {
10895
+ return left < right ? -1 : left > right ? 1 : 0;
10896
+ }
10897
+ function orderedMounts(mounts) {
10898
+ return mounts.map((mount) => ({ ...mount })).sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
10899
+ }
10900
+ function identity(container) {
10901
+ return {
10902
+ id: container.id,
10903
+ service: container.labels["com.docker.compose.service"] ?? "",
10904
+ configFiles: (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()),
10905
+ mounts: orderedMounts(container.mounts)
10906
+ };
10907
+ }
10908
+ function prove(state, baseline) {
10909
+ const owner = registration(state);
10910
+ if (Object.entries(owner).some(
10911
+ ([key, value]) => baseline[key] !== value
10912
+ )) {
10913
+ throw new Error("Retained provider identity changed.");
10914
+ }
10915
+ if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
10916
+ throw new Error("Stop daemon changed.");
10917
+ const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
10918
+ const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
10919
+ const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
10920
+ if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers2.some(
10921
+ (container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
10922
+ )) {
10923
+ throw new Error("Retained container population or immutable identity changed.");
10924
+ }
10925
+ const workspaceIds = inspectManagedStopWorkspaceIds(
10926
+ baseline.endpoint,
10927
+ baseline.composeDirectory
10928
+ ).sort();
10929
+ if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
10930
+ throw new Error("Workspace contains an unrecorded container population.");
10931
+ }
10932
+ const primary = baseline.containers.find(
10933
+ (container) => container.service === baseline.primaryService
10934
+ );
10935
+ if (!primary) throw new Error("Retained primary is missing.");
10936
+ const uidBytes = Buffer.byteLength(baseline.uid);
10937
+ const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
10938
+ if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
10939
+ throw new Error("Provider-to-container binding changed.");
10940
+ }
10941
+ if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
10942
+ throw new Error("Stop authority changed during inspection.");
10943
+ }
10944
+ return containers2;
10945
+ }
10946
+ function proveRetainedManagedStop(state) {
10947
+ const baseline = validateManagedStopBaseline(state.stopBaseline, state);
10948
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
10949
+ throw new Error("Retained stop generation changed.");
10950
+ }
10951
+ const result = prove(state, baseline);
10952
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
10953
+ throw new Error("Retained stop generation changed during inspection.");
10954
+ }
10955
+ return result;
10956
+ }
10957
+ function stopFromManagedBaseline(state) {
10958
+ const baseline = validateManagedStopBaseline(state.stopBaseline, state);
10959
+ const stopped = /* @__PURE__ */ new Set();
10960
+ for (const initial of proveRetainedManagedStop(state))
10961
+ if (!initial.state.Running) stopped.add(initial.id);
10962
+ const inspect = () => {
10963
+ const current = proveRetainedManagedStop(state);
10964
+ if (current.some((container) => stopped.has(container.id) && container.state.Running)) {
10965
+ throw new Error("Retained container restarted during stop.");
10966
+ }
10967
+ for (const container of current) if (!container.state.Running) stopped.add(container.id);
10968
+ return current;
10969
+ };
10970
+ for (const retained of baseline.containers) {
10971
+ if (inspect().find((container) => container.id === retained.id)?.state.Running) {
10972
+ claimLifecycleEffect();
10973
+ stopPinnedManagedContainer(baseline.endpoint, retained.id);
10974
+ if (inspect().find((container) => container.id === retained.id)?.state.Running)
10975
+ throw new Error("Container cessation is not proven.");
10976
+ }
10977
+ }
10978
+ if (inspect().some((container) => container.state.Running))
10979
+ throw new Error("Retained workloads remain running.");
10980
+ }
10981
+ var import_node_os2, import_node_path31, import_node_util2;
10982
+ var init_managed_stop_recovery = __esm({
10983
+ "src/core/managed-stop-recovery.ts"() {
10984
+ "use strict";
10985
+ import_node_os2 = __toESM(require("os"));
10986
+ import_node_path31 = __toESM(require("path"));
10987
+ import_node_util2 = require("util");
10988
+ init_devpod_environment();
10989
+ init_devsy_workspaces();
10990
+ init_managed_compose_population();
10991
+ init_managed_runtime_state();
10992
+ init_managed_stop_baseline();
10993
+ init_reliability_context();
10994
+ init_workspace();
10995
+ init_workspace_ownership();
10996
+ init_workspace_runtime();
10997
+ }
10998
+ });
10999
+
11000
+ // src/core/route-publication.ts
11001
+ function routedAppsFromConfig(config) {
11002
+ return config.apps.filter((app) => app.kind !== "dependency");
11003
+ }
11004
+ function configuredProxyAppsFromConfig(config) {
11005
+ return routedAppsFromConfig(config).filter(
11006
+ (app) => app.runtime === "proxy"
11007
+ );
11008
+ }
11009
+ function proxyAppsFromConfig(config) {
11010
+ const routedApps = routedAppsFromConfig(config);
11011
+ const proxyApps = configuredProxyAppsFromConfig(config);
11012
+ const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
11013
+ if (unsupported.length > 0) {
11014
+ throw new Error(
11015
+ `Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
11016
+ );
11017
+ }
11018
+ return proxyApps;
11019
+ }
11020
+ async function ensureRouteInfrastructure(apps, options = {}) {
11021
+ claimLifecycleEffect();
11022
+ ensureRouterFiles();
11023
+ await ensureNetwork(DEVNET_NAME);
11024
+ claimLifecycleEffect();
11025
+ const tlsCoverage = await ensureTLSHostsCovered(
11026
+ apps.map((app) => app.host),
11027
+ options
11028
+ );
11029
+ for (const app of apps) {
11030
+ if (app.protocol === "tcp") {
11031
+ if (!isTLSEnabled()) {
11032
+ throw new Error(
11033
+ `TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
11034
+ );
11035
+ }
11036
+ activateTcpProtocol(app.tcpProtocol);
11037
+ }
11038
+ }
11039
+ claimLifecycleEffect();
11040
+ startRouterStack();
11041
+ return tlsCoverage;
11042
+ }
11043
+ async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
11044
+ const apps = proxyAppsFromConfig(config);
11045
+ const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
11046
+ const routes = apps.map((app) => {
11047
+ const upstream = parseUpstream(app.upstream);
11048
+ return {
11049
+ name: app.name,
11050
+ host: app.host,
11051
+ protocol: app.protocol,
11052
+ tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
11053
+ repoPath,
11054
+ port: upstream.port,
11055
+ mode: "proxy",
11056
+ upstreamHost: upstream.upstreamHost,
11057
+ workspace
11058
+ };
11059
+ });
11060
+ claimLifecycleEffect();
11061
+ replaceHostRoutesForRepo(repoPath, routes);
11062
+ return { routes, tlsRefreshed: tlsCoverage.refreshed };
11063
+ }
11064
+ var init_route_publication = __esm({
11065
+ "src/core/route-publication.ts"() {
11066
+ "use strict";
11067
+ init_docker();
11068
+ init_host_routes();
11069
+ init_reliability_context();
11070
+ init_router();
11071
+ init_tls();
10605
11072
  }
10606
11073
  });
10607
11074
 
@@ -10627,8 +11094,12 @@ function stopRetainedManagedDevsyWorkspace(options) {
10627
11094
  if (state.devpodId !== devsyId || linked && !workspace) {
10628
11095
  throw new Error("Managed stop requires the exact retained workspace identity.");
10629
11096
  }
11097
+ if (state.stopBaseline) {
11098
+ stopFromManagedBaseline(state);
11099
+ return true;
11100
+ }
10630
11101
  const workspaceEnv = workspace ? { token: workspace, gitCommonDir: resolveGitCommonDir(repoPath) } : void 0;
10631
- function registration() {
11102
+ function registration2() {
10632
11103
  resetWorkspaceRuntimeCaches();
10633
11104
  if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
10634
11105
  throw new Error("Managed stop provider selection changed.");
@@ -10638,19 +11109,19 @@ function stopRetainedManagedDevsyWorkspace(options) {
10638
11109
  throw new Error("Managed stop requires one exact retained Devsy registration.");
10639
11110
  }
10640
11111
  if (workspace) {
10641
- const record3 = readWorkspaceOwnership(repoPath, workspace);
10642
- if (!record3 || record3.devpodId !== devsyId || !sameWorkspacePath(record3.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir) {
11112
+ const record4 = readWorkspaceOwnership(repoPath, workspace);
11113
+ if (!record4 || record4.devpodId !== devsyId || !sameWorkspacePath(record4.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir) {
10643
11114
  throw new Error("Managed stop workspace ownership changed.");
10644
11115
  }
10645
11116
  }
10646
11117
  return owner.workspace;
10647
11118
  }
10648
- const context = registration().context;
11119
+ const context = registration2().context;
10649
11120
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
10650
11121
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
10651
11122
  }
10652
- const devsyRoot = import_node_path29.default.resolve(process.env.DEVSY_HOME || import_node_path29.default.join(import_node_os2.default.homedir(), ".devsy"));
10653
- const featureDirectory = import_node_path29.default.join(
11123
+ const devsyRoot = import_node_path32.default.resolve(process.env.DEVSY_HOME || import_node_path32.default.join(import_node_os3.default.homedir(), ".devsy"));
11124
+ const featureDirectory = import_node_path32.default.join(
10654
11125
  devsyRoot,
10655
11126
  "contexts",
10656
11127
  context,
@@ -10659,8 +11130,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
10659
11130
  "agent",
10660
11131
  ".docker-compose"
10661
11132
  );
10662
- function prove(previous) {
10663
- if (registration().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
11133
+ function prove2(previous) {
11134
+ if (registration2().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
10664
11135
  throw new Error("Managed stop retained context or runtime record changed.");
10665
11136
  }
10666
11137
  const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
@@ -10683,49 +11154,24 @@ function stopRetainedManagedDevsyWorkspace(options) {
10683
11154
  throw new Error("Managed stop requires unchanged recorded resources and configuration.");
10684
11155
  }
10685
11156
  const containers2 = inspectManagedStopContainers(state.composeProject);
10686
- const services = /* @__PURE__ */ new Set();
10687
- for (const container of containers2) {
10688
- const service = container.labels["com.docker.compose.service"] ?? "";
10689
- if (!plan.nativeRunServices.includes(service) || services.has(service) || container.labels["com.docker.compose.project"] !== state.composeProject || !sameWorkspacePath(
10690
- container.labels["com.docker.compose.project.working_dir"] ?? "",
10691
- plan.composeDirectory
10692
- )) {
10693
- throw new Error("Managed stop found unexpected or duplicate project membership.");
10694
- }
10695
- services.add(service);
10696
- const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
10697
- if (files.some((file) => !file || !import_node_path29.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path29.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
10698
- throw new Error("Managed stop Compose file identity changed.");
10699
- }
10700
- for (const file of files.slice(plan.composeFiles.length)) {
10701
- if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
10702
- import_node_path29.default.basename(file)
10703
- ) || import_node_fs26.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs26.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
10704
- throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
10705
- }
10706
- }
10707
- if (previous) {
10708
- const retained = previous.find((entry) => entry.id === container.id);
10709
- if (!retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running) {
10710
- throw new Error("Managed stop container identity or quiescent state changed.");
10711
- }
10712
- }
10713
- }
10714
- if (plan.desiredServices.some((service) => !services.has(service)) || previous && !sameSet(
11157
+ const primary = proveManagedComposePopulation({
11158
+ plan,
11159
+ repoPath,
11160
+ composeProject: state.composeProject,
11161
+ featureDirectory,
11162
+ devsyRoot,
11163
+ containers: containers2
11164
+ });
11165
+ if (previous && (!sameSet(
10715
11166
  previous.map((c) => c.id),
10716
11167
  containers2.map((c) => c.id)
10717
- )) {
10718
- throw new Error("Managed stop cannot prove the complete retained service population.");
10719
- }
10720
- const primary = containers2.find(
10721
- (c) => c.labels["com.docker.compose.service"] === plan.primaryService
10722
- );
10723
- if (primary?.mounts.filter(
10724
- (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
10725
- ).length !== 1) {
10726
- throw new Error("Managed stop cannot prove the exact primary workspace mount.");
11168
+ ) || containers2.some((container) => {
11169
+ const retained = previous.find((entry) => entry.id === container.id);
11170
+ return !retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running;
11171
+ }))) {
11172
+ throw new Error("Managed stop container identity or quiescent state changed.");
10727
11173
  }
10728
- if (registration().context !== context) {
11174
+ if (registration2().context !== context) {
10729
11175
  throw new Error("Managed stop provider context changed during inspection.");
10730
11176
  }
10731
11177
  const status = inspectDevsyRuntimeStatus(devsyId);
@@ -10734,7 +11180,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10734
11180
  }
10735
11181
  return { containers: containers2, primary, status };
10736
11182
  }
10737
- const initial = prove();
11183
+ const initial = prove2();
10738
11184
  let providerError;
10739
11185
  let providerFailed = false;
10740
11186
  if (initial.status === "running") {
@@ -10747,7 +11193,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10747
11193
  }
10748
11194
  try {
10749
11195
  const stopped = () => {
10750
- const current = prove(initial.containers);
11196
+ const current = prove2(initial.containers);
10751
11197
  if (current.status !== "stopped" || current.primary.state.Running) {
10752
11198
  throw new Error("Managed stop has not stopped the exact primary container.");
10753
11199
  }
@@ -10779,17 +11225,18 @@ function stopRetainedManagedDevsyWorkspace(options) {
10779
11225
  if (providerFailed) throw providerError;
10780
11226
  return true;
10781
11227
  }
10782
- var import_node_fs26, import_node_os2, import_node_path29;
11228
+ var import_node_os3, import_node_path32;
10783
11229
  var init_managed_devsy_stop = __esm({
10784
11230
  "src/core/managed-devsy-stop.ts"() {
10785
11231
  "use strict";
10786
- import_node_fs26 = __toESM(require("fs"));
10787
- import_node_os2 = __toESM(require("os"));
10788
- import_node_path29 = __toESM(require("path"));
11232
+ import_node_os3 = __toESM(require("os"));
11233
+ import_node_path32 = __toESM(require("path"));
10789
11234
  init_devcontainer_profile();
10790
11235
  init_devpod_environment();
10791
11236
  init_devsy_workspaces();
11237
+ init_managed_compose_population();
10792
11238
  init_managed_runtime_state();
11239
+ init_managed_stop_recovery();
10793
11240
  init_repo_config();
10794
11241
  init_route_publication();
10795
11242
  init_workspace();
@@ -10875,20 +11322,20 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
10875
11322
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
10876
11323
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
10877
11324
  }
10878
- var import_node_child_process18, import_node_fs27, import_node_path30, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11325
+ var import_node_child_process18, import_node_fs27, import_node_path33, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
10879
11326
  var init_devsy_mutation = __esm({
10880
11327
  "src/core/devsy-mutation.ts"() {
10881
11328
  "use strict";
10882
11329
  import_node_child_process18 = require("child_process");
10883
11330
  import_node_fs27 = __toESM(require("fs"));
10884
- import_node_path30 = __toESM(require("path"));
11331
+ import_node_path33 = __toESM(require("path"));
10885
11332
  init_devsy_agent();
10886
11333
  init_devsy_workspaces();
10887
11334
  init_file_lock();
10888
11335
  init_managed_devsy_stop();
10889
11336
  init_reliability_context();
10890
11337
  init_router();
10891
- DEVSY_MUTATION_LOCK_FILE = import_node_path30.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
11338
+ DEVSY_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
10892
11339
  DEVSY_MUTATION_WAIT_MS = 18e5;
10893
11340
  }
10894
11341
  });
@@ -10981,20 +11428,20 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
10981
11428
  resetWorkspaceRuntimeCaches();
10982
11429
  return result;
10983
11430
  }
10984
- var import_node_child_process19, import_node_fs28, import_node_path31, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
11431
+ var import_node_child_process19, import_node_fs28, import_node_path34, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
10985
11432
  var init_devpod_mutation = __esm({
10986
11433
  "src/core/devpod-mutation.ts"() {
10987
11434
  "use strict";
10988
11435
  import_node_child_process19 = require("child_process");
10989
11436
  import_node_fs28 = __toESM(require("fs"));
10990
- import_node_path31 = __toESM(require("path"));
11437
+ import_node_path34 = __toESM(require("path"));
10991
11438
  init_devpod_workspaces();
10992
11439
  init_devsy_mutation();
10993
11440
  init_file_lock();
10994
11441
  init_reliability_context();
10995
11442
  init_router();
10996
11443
  init_workspace_runtime();
10997
- DEVPOD_MUTATION_LOCK_FILE = import_node_path31.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
11444
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path34.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
10998
11445
  DEVPOD_MUTATION_WAIT_MS = 18e5;
10999
11446
  }
11000
11447
  });
@@ -11009,10 +11456,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
11009
11456
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
11010
11457
  const comparableRepo = comparableWorkspacePath(repoPath);
11011
11458
  const comparableWorktree = comparableWorkspacePath(worktreePath);
11012
- const localRoot = import_node_path32.default.join(comparableRepo, "trees") + import_node_path32.default.sep;
11459
+ const localRoot = import_node_path35.default.join(comparableRepo, "trees") + import_node_path35.default.sep;
11013
11460
  if (comparableWorktree.startsWith(localRoot)) return true;
11014
- const legacyPrefix = `${import_node_path32.default.basename(comparableRepo)}-`;
11015
- return import_node_path32.default.dirname(comparableWorktree) === import_node_path32.default.dirname(comparableRepo) && import_node_path32.default.basename(comparableWorktree).startsWith(legacyPrefix);
11461
+ const legacyPrefix = `${import_node_path35.default.basename(comparableRepo)}-`;
11462
+ return import_node_path35.default.dirname(comparableWorktree) === import_node_path35.default.dirname(comparableRepo) && import_node_path35.default.basename(comparableWorktree).startsWith(legacyPrefix);
11016
11463
  }
11017
11464
  function previewActions(devpodStatus, routeCount, includeRecord) {
11018
11465
  const actions = [
@@ -11041,15 +11488,15 @@ function ownedReason(ownerStatus) {
11041
11488
  return "Ownership conflicts with live Git, persisted token, or DevPod source evidence.";
11042
11489
  }
11043
11490
  }
11044
- function ownedCandidate(record3, worktrees, devpods, routes) {
11045
- const status = inspectWorkspaceOwnership(record3, worktrees, devpods);
11046
- const routeCount = routesForOwner(routes, record3.workspace, record3.worktreePath).length;
11491
+ function ownedCandidate(record4, worktrees, devpods, routes) {
11492
+ const status = inspectWorkspaceOwnership(record4, worktrees, devpods);
11493
+ const routeCount = routesForOwner(routes, record4.workspace, record4.worktreePath).length;
11047
11494
  const eligible = status.ownerStatus === "missing" && status.devpodStatus !== "conflict";
11048
11495
  return {
11049
11496
  kind: "owned",
11050
- workspace: record3.workspace,
11051
- devpodId: record3.devpodId,
11052
- worktreePath: record3.worktreePath,
11497
+ workspace: record4.workspace,
11498
+ devpodId: record4.devpodId,
11499
+ worktreePath: record4.worktreePath,
11053
11500
  ownerStatus: status.ownerStatus,
11054
11501
  devpodStatus: status.devpodStatus,
11055
11502
  routeCount,
@@ -11067,10 +11514,10 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
11067
11514
  const primaryPath = worktrees[0]?.path;
11068
11515
  const evidence = /* @__PURE__ */ new Map();
11069
11516
  const isRecordedRoute = (workspace, worktreePath) => records.some(
11070
- (record3) => record3.workspace === workspace && sameWorkspacePath(record3.worktreePath, worktreePath)
11517
+ (record4) => record4.workspace === workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
11071
11518
  );
11072
11519
  const isRecordedDevpod = (devpodId, worktreePath) => records.some(
11073
- (record3) => record3.devpodId === devpodId && sameWorkspacePath(record3.worktreePath, worktreePath)
11520
+ (record4) => record4.devpodId === devpodId && sameWorkspacePath(record4.worktreePath, worktreePath)
11074
11521
  );
11075
11522
  const key = (workspace, worktreePath) => `${workspace}\0${comparableWorkspacePath(worktreePath)}`;
11076
11523
  for (const devpod of devpods) {
@@ -11109,8 +11556,8 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
11109
11556
  }));
11110
11557
  }
11111
11558
  function revalidateCandidate(candidate, repoPath, transaction) {
11112
- const record3 = transaction.list().find((entry) => entry.workspace === candidate.workspace);
11113
- if (!record3 || !sameWorkspacePath(record3.worktreePath, candidate.worktreePath) || record3.devpodId !== candidate.devpodId) {
11559
+ const record4 = transaction.list().find((entry) => entry.workspace === candidate.workspace);
11560
+ if (!record4 || !sameWorkspacePath(record4.worktreePath, candidate.worktreePath) || record4.devpodId !== candidate.devpodId) {
11114
11561
  return {
11115
11562
  status: "blocked",
11116
11563
  candidate: {
@@ -11126,12 +11573,12 @@ function revalidateCandidate(candidate, repoPath, transaction) {
11126
11573
  };
11127
11574
  }
11128
11575
  const fresh2 = ownedCandidate(
11129
- record3,
11576
+ record4,
11130
11577
  listGitWorktrees(repoPath),
11131
- listDevpodWorkspaces(record3.worktreePath),
11578
+ listDevpodWorkspaces(record4.worktreePath),
11132
11579
  listHostRouteState()
11133
11580
  );
11134
- if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record3 };
11581
+ if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record4 };
11135
11582
  return {
11136
11583
  status: "blocked",
11137
11584
  candidate: {
@@ -11165,11 +11612,11 @@ function applyCandidate(candidate, repoPath) {
11165
11612
  };
11166
11613
  }
11167
11614
  if (revalidated.status === "blocked") return revalidated.candidate;
11168
- const { candidate: fresh2, record: record3 } = revalidated;
11615
+ const { candidate: fresh2, record: record4 } = revalidated;
11169
11616
  const actions = [];
11170
11617
  let devpodStatus;
11171
11618
  try {
11172
- const mutation = deleteOwnedDevpodWorkspace(record3.devpodId, record3.worktreePath);
11619
+ const mutation = deleteOwnedDevpodWorkspace(record4.devpodId, record4.worktreePath);
11173
11620
  devpodStatus = mutation.status === "changed" ? "owned" : "absent";
11174
11621
  } catch (error) {
11175
11622
  return { ...fresh2, actions: [failedAction("devpod", error)] };
@@ -11189,7 +11636,7 @@ function applyCandidate(candidate, repoPath) {
11189
11636
  return { ...fresh2, actions: [...actions, failedAction("routes", error)] };
11190
11637
  }
11191
11638
  try {
11192
- const removal = transaction.removeIfMatches(record3);
11639
+ const removal = transaction.removeIfMatches(record4);
11193
11640
  if (removal === "changed") {
11194
11641
  throw new Error("Ownership record changed during cleanup; record was retained.");
11195
11642
  }
@@ -11235,10 +11682,10 @@ function inspectWorkspaceGc(repo) {
11235
11682
  // that owns each record (snapshot reads: the apply path revalidates
11236
11683
  // live); legacy detection scans both registries.
11237
11684
  ...records.map(
11238
- (record3) => ownedCandidate(
11239
- record3,
11685
+ (record4) => ownedCandidate(
11686
+ record4,
11240
11687
  worktrees,
11241
- listDevpodWorkspacesFromSnapshots(record3.worktreePath),
11688
+ listDevpodWorkspacesFromSnapshots(record4.worktreePath),
11242
11689
  routes
11243
11690
  )
11244
11691
  ),
@@ -11264,11 +11711,11 @@ function applyWorkspaceGc(plan) {
11264
11711
  candidates
11265
11712
  };
11266
11713
  }
11267
- var import_node_path32;
11714
+ var import_node_path35;
11268
11715
  var init_workspace_gc = __esm({
11269
11716
  "src/core/workspace-gc.ts"() {
11270
11717
  "use strict";
11271
- import_node_path32 = __toESM(require("path"));
11718
+ import_node_path35 = __toESM(require("path"));
11272
11719
  init_devpod_mutation();
11273
11720
  init_devpod_workspaces();
11274
11721
  init_host_routes();
@@ -11621,7 +12068,7 @@ async function buildDoctorReport(options = {}) {
11621
12068
  const config = runtimeConfig.config;
11622
12069
  loadedConfig = config;
11623
12070
  loadedWorkspace = runtimeConfig.workspace;
11624
- const cliVersion = true ? "0.0.60" : "0.0.0-dev";
12071
+ const cliVersion = true ? "0.0.62" : "0.0.0-dev";
11625
12072
  const configVersion = config.devrouter?.version;
11626
12073
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
11627
12074
  addCheck(checks, {
@@ -11651,7 +12098,7 @@ async function buildDoctorReport(options = {}) {
11651
12098
  (app) => app.docker.composeFiles.map((filePath) => ({
11652
12099
  app: app.name,
11653
12100
  filePath,
11654
- absolutePath: import_node_path33.default.resolve(repo.path, filePath)
12101
+ absolutePath: import_node_path36.default.resolve(repo.path, filePath)
11655
12102
  }))
11656
12103
  ).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
11657
12104
  addCheck(checks, {
@@ -11696,7 +12143,7 @@ async function buildDoctorReport(options = {}) {
11696
12143
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
11697
12144
  app: app.name,
11698
12145
  cwd: app.hostRun.cwd,
11699
- absolutePath: import_node_path33.default.resolve(repo.path, app.hostRun.cwd)
12146
+ absolutePath: import_node_path36.default.resolve(repo.path, app.hostRun.cwd)
11700
12147
  })).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
11701
12148
  addCheck(checks, {
11702
12149
  id: "repo.host-cwd",
@@ -11841,12 +12288,12 @@ async function buildDoctorReport(options = {}) {
11841
12288
  nextSteps
11842
12289
  };
11843
12290
  }
11844
- var import_node_fs29, import_node_path33, import_yaml5, POSTGRES_DEFAULTS;
12291
+ var import_node_fs29, import_node_path36, import_yaml5, POSTGRES_DEFAULTS;
11845
12292
  var init_doctor = __esm({
11846
12293
  "src/core/doctor.ts"() {
11847
12294
  "use strict";
11848
12295
  import_node_fs29 = __toESM(require("fs"));
11849
- import_node_path33 = __toESM(require("path"));
12296
+ import_node_path36 = __toESM(require("path"));
11850
12297
  import_yaml5 = __toESM(require("yaml"));
11851
12298
  init_docker();
11852
12299
  init_host_routes();
@@ -12254,8 +12701,98 @@ var init_up = __esm({
12254
12701
  }
12255
12702
  });
12256
12703
 
12704
+ // src/core/devsy-exec-proof.ts
12705
+ function proveLocalDockerSelection(workspace) {
12706
+ const name = workspace.providerName;
12707
+ const context = workspace.context;
12708
+ if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
12709
+ throw new Error("Retained exec provider configuration identity is unavailable.");
12710
+ const file = import_node_path37.default.join(
12711
+ process.env.DEVSY_HOME || import_node_path37.default.join(import_node_os4.default.homedir(), ".devsy"),
12712
+ "contexts",
12713
+ context,
12714
+ "providers",
12715
+ name,
12716
+ "provider.json"
12717
+ );
12718
+ try {
12719
+ if (import_node_fs30.default.statSync(file).size > 1024 * 1024) throw new Error();
12720
+ const config = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
12721
+ const driver = config.agent?.driver ?? "docker";
12722
+ const configuredPath = config.agent?.docker?.path ?? "";
12723
+ if (typeof configuredPath !== "string") throw new Error();
12724
+ const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
12725
+ if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
12726
+ } catch {
12727
+ throw new Error("Retained exec requires a provider configured for the local Docker command.");
12728
+ }
12729
+ return name;
12730
+ }
12731
+ function captureDevsyExecProof(repoPath) {
12732
+ repoPath = comparableWorkspacePath(repoPath);
12733
+ const registry = listDevsyWorkspaces();
12734
+ const workspace = selectDevsyWorkspace(registry, repoPath);
12735
+ if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
12736
+ throw new Error("Retained exec requires one exact running Devsy registration.");
12737
+ const providerName = proveLocalDockerSelection(workspace);
12738
+ const endpoint = resolveManagedStopEndpoint();
12739
+ if (!supportsManagedStopBaseline(endpoint))
12740
+ throw new Error("Retained exec requires an exact local Docker endpoint.");
12741
+ const daemon = inspectManagedStopDaemon(endpoint);
12742
+ const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
12743
+ (container) => container.state.Running
12744
+ );
12745
+ if (candidates.length !== 1)
12746
+ throw new Error("Retained exec requires one exact running primary container.");
12747
+ const candidate = candidates[0];
12748
+ const project = candidate.labels["com.docker.compose.project"];
12749
+ if (!project) throw new Error("Retained exec primary has no Compose identity.");
12750
+ const pinned = workspaceAppContainers(
12751
+ inspectManagedStopContainers(project, endpoint),
12752
+ repoPath
12753
+ ).filter((container) => container.state.Running);
12754
+ if (pinned.length !== 1 || pinned[0].id !== candidate.id)
12755
+ throw new Error("Retained exec primary changed during inspection.");
12756
+ const mounts = pinned[0].mounts.filter(
12757
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
12758
+ );
12759
+ if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
12760
+ throw new Error("Retained exec requires one exact source mount.");
12761
+ const uidBytes = Buffer.byteLength(workspace.uid);
12762
+ const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
12763
+ const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
12764
+ if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
12765
+ throw new Error("Retained exec runner does not select the exact primary container.");
12766
+ if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
12767
+ throw new Error("Retained exec provider or Docker identity changed.");
12768
+ return {
12769
+ repoPath,
12770
+ id: workspace.id,
12771
+ uid: workspace.uid,
12772
+ context: workspace.context,
12773
+ providerName,
12774
+ endpoint,
12775
+ daemon,
12776
+ containerId: candidate.id,
12777
+ workspacePath: mounts[0].Destination
12778
+ };
12779
+ }
12780
+ var import_node_fs30, import_node_os4, import_node_path37, import_node_util3;
12781
+ var init_devsy_exec_proof = __esm({
12782
+ "src/core/devsy-exec-proof.ts"() {
12783
+ "use strict";
12784
+ import_node_fs30 = __toESM(require("fs"));
12785
+ import_node_os4 = __toESM(require("os"));
12786
+ import_node_path37 = __toESM(require("path"));
12787
+ import_node_util3 = require("util");
12788
+ init_devpod_environment();
12789
+ init_devsy_workspaces();
12790
+ init_workspace();
12791
+ }
12792
+ });
12793
+
12257
12794
  // src/core/reliability-model.ts
12258
- function record2(value) {
12795
+ function record3(value) {
12259
12796
  return typeof value === "object" && value !== null && !Array.isArray(value);
12260
12797
  }
12261
12798
  function oneOf2(value, values) {
@@ -12265,16 +12802,16 @@ function uniqueIds2(values) {
12265
12802
  return new Set(values).size === values.length && values.every(isReliabilityId);
12266
12803
  }
12267
12804
  function isFence(value) {
12268
- return record2(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
12805
+ return record3(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
12269
12806
  }
12270
12807
  function isConsumer(value) {
12271
- return record2(value) && isReliabilityId(value.id) && typeof value.pinned === "boolean" && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.length <= 128 && uniqueIds2(value.requiredCapabilities);
12808
+ return record3(value) && isReliabilityId(value.id) && typeof value.pinned === "boolean" && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.length <= 128 && uniqueIds2(value.requiredCapabilities);
12272
12809
  }
12273
12810
  function isObservation(value) {
12274
- return record2(value) && isReliabilityId(value.capability) && oneOf2(value.infrastructure, INFRASTRUCTURE_VALUES) && oneOf2(value.application, APPLICATION_VALUES) && isReliabilityCounter(value.observedAtMs) && isReliabilityCounter(value.validForMs);
12811
+ return record3(value) && isReliabilityId(value.capability) && oneOf2(value.infrastructure, INFRASTRUCTURE_VALUES) && oneOf2(value.application, APPLICATION_VALUES) && isReliabilityCounter(value.observedAtMs) && isReliabilityCounter(value.validForMs);
12275
12812
  }
12276
12813
  function assertReliabilityEvent(value) {
12277
- if (!record2(value) || !isFence(value) || typeof value.type !== "string") {
12814
+ if (!record3(value) || !isFence(value) || typeof value.type !== "string") {
12278
12815
  throw new Error("Invalid reliability event.");
12279
12816
  }
12280
12817
  let valid = false;
@@ -12283,7 +12820,7 @@ function assertReliabilityEvent(value) {
12283
12820
  valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
12284
12821
  break;
12285
12822
  case "operation-request":
12286
- valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
12823
+ valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean" && (value.recoverInterruptedEnsure === void 0 || typeof value.recoverInterruptedEnsure === "boolean");
12287
12824
  break;
12288
12825
  case "drained":
12289
12826
  valid = isReliabilityId(value.operationId);
@@ -12466,6 +13003,10 @@ function handleRequest(state, event) {
12466
13003
  });
12467
13004
  return transition(state, "accepted");
12468
13005
  }
13006
+ function canExecAfterInterruptedEnsure(state) {
13007
+ const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
13008
+ return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
13009
+ }
12469
13010
  function handleOperationRequest(state, event) {
12470
13011
  if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
12471
13012
  const previous = state.operationHistory.find((entry) => entry.key === event.key);
@@ -12476,20 +13017,33 @@ function handleOperationRequest(state, event) {
12476
13017
  );
12477
13018
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
12478
13019
  return unchanged(state, "conflict");
12479
- if (state.operationHistory.length >= RELIABILITY_MAX_ITEMS) return unchanged(state, "blocked");
13020
+ if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
13021
+ return unchanged(state, "blocked");
12480
13022
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
12481
13023
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
12482
- if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
13024
+ if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
12483
13025
  return unchanged(state, "blocked");
12484
13026
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
12485
13027
  return unchanged(state, "blocked");
12486
13028
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
12487
13029
  return unchanged(state, "blocked");
12488
- if (state.desired !== "running") {
13030
+ const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
13031
+ let retired = -1;
13032
+ if (rollover) {
13033
+ if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
13034
+ return unchanged(state, "blocked");
13035
+ const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
13036
+ retired = state.operationHistory.findIndex(
13037
+ (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
13038
+ );
13039
+ if (retired === -1) return unchanged(state, "blocked");
13040
+ }
13041
+ if (state.desired !== "running" || rollover) {
12489
13042
  const revision = advance(state.intentRevision);
12490
13043
  if (revision === null) return unchanged(state, "blocked");
12491
13044
  state.intentRevision = revision;
12492
13045
  }
13046
+ if (rollover) state.operationHistory.splice(retired, 1);
12493
13047
  state.desired = "running";
12494
13048
  state.phase = "queued";
12495
13049
  state.profile = event.profile;
@@ -12858,8 +13412,8 @@ var init_reliability_model = __esm({
12858
13412
  });
12859
13413
 
12860
13414
  // src/core/reliability-worker.ts
12861
- function sameFence2(record3, fence) {
12862
- return Object.entries(reliabilityFence(record3.state)).every(
13415
+ function sameFence2(record4, fence) {
13416
+ return Object.entries(reliabilityFence(record4.state)).every(
12863
13417
  ([key, value]) => value === fence[key]
12864
13418
  );
12865
13419
  }
@@ -12882,8 +13436,8 @@ function signalOwnedGroup(child, signal) {
12882
13436
  async function runLifecycleWorker(request) {
12883
13437
  if (process.platform === "win32")
12884
13438
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
12885
- const workerPath = import_node_path34.default.join(__dirname, "devrouter-lifecycle-worker.js");
12886
- if (!import_node_fs30.default.existsSync(workerPath))
13439
+ const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
13440
+ if (!import_node_fs31.default.existsSync(workerPath))
12887
13441
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
12888
13442
  const child = (0, import_node_child_process21.fork)(workerPath, [], {
12889
13443
  detached: true,
@@ -12930,28 +13484,28 @@ async function runLifecycleWorker(request) {
12930
13484
  if (request.kind !== "stop") {
12931
13485
  const birth = processBirthIdentity(child.pid);
12932
13486
  if (!birth) throw new Error("Could not prove lifecycle worker incarnation.");
12933
- updateReliabilityOperation(request.identity, (record3) => {
12934
- if (!sameFence2(record3, request.fence) || record3.worker || record3.state.operation?.id !== request.operationId) {
13487
+ updateReliabilityOperation(request.identity, (record4) => {
13488
+ if (!sameFence2(record4, request.fence) || record4.worker || record4.state.operation?.id !== request.operationId) {
12935
13489
  throw new Error("Lifecycle intent changed before dispatch.");
12936
13490
  }
12937
- record3.worker = {
13491
+ record4.worker = {
12938
13492
  id: request.workerId,
12939
13493
  operationId: request.operationId,
12940
13494
  pid: child.pid,
12941
13495
  birth
12942
13496
  };
12943
13497
  const transition2 = stepReliability(
12944
- record3.state,
13498
+ record4.state,
12945
13499
  { ...request.fence, type: "dispatch" },
12946
13500
  Date.now()
12947
13501
  );
12948
13502
  if (transition2.outcome !== "accepted")
12949
13503
  throw new Error("Lifecycle dispatch is blocked.");
12950
- record3.state = transition2.state;
13504
+ record4.state = transition2.state;
12951
13505
  });
12952
- updateReliabilityOperation(request.identity, (record3) => {
13506
+ updateReliabilityOperation(request.identity, (record4) => {
12953
13507
  const transition2 = stepReliability(
12954
- record3.state,
13508
+ record4.state,
12955
13509
  {
12956
13510
  ...request.fence,
12957
13511
  type: "dispatch-persisted",
@@ -12959,7 +13513,7 @@ async function runLifecycleWorker(request) {
12959
13513
  },
12960
13514
  Date.now()
12961
13515
  );
12962
- record3.state = transition2.state;
13516
+ record4.state = transition2.state;
12963
13517
  if (!transition2.effects.some((effect2) => effect2.kind === "launch")) {
12964
13518
  throw new Error("Lifecycle dispatch persistence was not acknowledged.");
12965
13519
  }
@@ -12973,8 +13527,8 @@ async function runLifecycleWorker(request) {
12973
13527
  });
12974
13528
  monitor = setInterval(() => {
12975
13529
  try {
12976
- const record3 = readReliabilityOperation(request.identity);
12977
- if (!record3 || !sameFence2(record3, request.fence)) cancel();
13530
+ const record4 = readReliabilityOperation(request.identity);
13531
+ if (!record4 || !sameFence2(record4, request.fence)) cancel();
12978
13532
  } catch (error) {
12979
13533
  failure = error instanceof Error ? error : new Error(String(error));
12980
13534
  cancel();
@@ -12991,13 +13545,13 @@ async function runLifecycleWorker(request) {
12991
13545
  child.once("close", () => {
12992
13546
  try {
12993
13547
  if (request.kind !== "stop") {
12994
- updateReliabilityOperation(request.identity, (record3) => {
12995
- if (record3.worker?.id !== request.workerId || record3.worker.pid !== child.pid) return;
13548
+ updateReliabilityOperation(request.identity, (record4) => {
13549
+ if (record4.worker?.id !== request.workerId || record4.worker.pid !== child.pid) return;
12996
13550
  if (!result) {
12997
- record3.state = stepReliability(
12998
- record3.state,
13551
+ record4.state = stepReliability(
13552
+ record4.state,
12999
13553
  {
13000
- ...reliabilityFence(record3.state),
13554
+ ...reliabilityFence(record4.state),
13001
13555
  type: "interrupted",
13002
13556
  operationId: request.operationId
13003
13557
  },
@@ -13005,16 +13559,16 @@ async function runLifecycleWorker(request) {
13005
13559
  ).state;
13006
13560
  }
13007
13561
  if (child.pid && workerGroupAbsent(child.pid)) {
13008
- record3.state = stepReliability(
13009
- record3.state,
13562
+ record4.state = stepReliability(
13563
+ record4.state,
13010
13564
  {
13011
- ...reliabilityFence(record3.state),
13565
+ ...reliabilityFence(record4.state),
13012
13566
  type: "drained",
13013
13567
  operationId: request.operationId
13014
13568
  },
13015
13569
  Date.now()
13016
13570
  ).state;
13017
- record3.worker = null;
13571
+ record4.worker = null;
13018
13572
  }
13019
13573
  });
13020
13574
  }
@@ -13041,14 +13595,14 @@ async function runLifecycleWorker(request) {
13041
13595
  function newLifecycleIds() {
13042
13596
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
13043
13597
  }
13044
- var import_node_child_process21, import_node_crypto16, import_node_fs30, import_node_path34;
13598
+ var import_node_child_process21, import_node_crypto16, import_node_fs31, import_node_path38;
13045
13599
  var init_reliability_worker = __esm({
13046
13600
  "src/core/reliability-worker.ts"() {
13047
13601
  "use strict";
13048
13602
  import_node_child_process21 = require("child_process");
13049
13603
  import_node_crypto16 = require("crypto");
13050
- import_node_fs30 = __toESM(require("fs"));
13051
- import_node_path34 = __toESM(require("path"));
13604
+ import_node_fs31 = __toESM(require("fs"));
13605
+ import_node_path38 = __toESM(require("path"));
13052
13606
  init_file_lock();
13053
13607
  init_reliability_contract();
13054
13608
  init_reliability_model();
@@ -13057,12 +13611,12 @@ var init_reliability_worker = __esm({
13057
13611
  });
13058
13612
 
13059
13613
  // src/core/managed-host-preparation.ts
13060
- var import_node_child_process22, import_node_fs31;
13614
+ var import_node_child_process22, import_node_fs32;
13061
13615
  var init_managed_host_preparation = __esm({
13062
13616
  "src/core/managed-host-preparation.ts"() {
13063
13617
  "use strict";
13064
13618
  import_node_child_process22 = require("child_process");
13065
- import_node_fs31 = __toESM(require("fs"));
13619
+ import_node_fs32 = __toESM(require("fs"));
13066
13620
  init_repo_config();
13067
13621
  }
13068
13622
  });
@@ -13171,7 +13725,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
13171
13725
  throw new Error(
13172
13726
  "Traefik routes did not reload; shared router restart is disabled for this operation."
13173
13727
  );
13174
- import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13728
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13175
13729
  return withFileLock(
13176
13730
  ROUTER_RELOAD_LOCK_FILE,
13177
13731
  {
@@ -13202,19 +13756,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
13202
13756
  async function ensureTraefikRoutesRemoved(routes, options = {}) {
13203
13757
  return ensureTraefikRouteExpectation(routes, "removed", options);
13204
13758
  }
13205
- var import_node_child_process23, import_node_fs32, import_node_path35, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
13759
+ var import_node_child_process23, import_node_fs33, import_node_path39, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
13206
13760
  var init_traefik_route_health = __esm({
13207
13761
  "src/core/traefik-route-health.ts"() {
13208
13762
  "use strict";
13209
13763
  import_node_child_process23 = require("child_process");
13210
- import_node_fs32 = __toESM(require("fs"));
13211
- import_node_path35 = __toESM(require("path"));
13764
+ import_node_fs33 = __toESM(require("fs"));
13765
+ import_node_path39 = __toESM(require("path"));
13212
13766
  init_file_lock();
13213
13767
  init_host_routes();
13214
13768
  init_reliability_context();
13215
13769
  init_router();
13216
13770
  ROUTER_API_BASE = "http://127.0.0.1:8080/api";
13217
- ROUTER_RELOAD_LOCK_FILE = import_node_path35.default.join(DEVROUTER_HOME, "router-reload.lock");
13771
+ ROUTER_RELOAD_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "router-reload.lock");
13218
13772
  ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
13219
13773
  DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
13220
13774
  DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -13253,17 +13807,18 @@ function resolveLinkedTarget(repoPath) {
13253
13807
  gitCommonDir: resolveGitCommonDir(repoPath)
13254
13808
  };
13255
13809
  }
13256
- var import_node_child_process24, import_node_fs33, import_node_path36;
13810
+ var import_node_child_process24, import_node_fs34, import_node_path40;
13257
13811
  var init_workspace_ensure = __esm({
13258
13812
  "src/core/workspace-ensure.ts"() {
13259
13813
  "use strict";
13260
13814
  import_node_child_process24 = require("child_process");
13261
- import_node_fs33 = __toESM(require("fs"));
13262
- import_node_path36 = __toESM(require("path"));
13815
+ import_node_fs34 = __toESM(require("fs"));
13816
+ import_node_path40 = __toESM(require("path"));
13263
13817
  init_devcontainer_profile();
13264
13818
  init_devpod_environment();
13265
13819
  init_devpod_mutation();
13266
13820
  init_devpod_workspaces();
13821
+ init_devsy_mutation();
13267
13822
  init_file_lock();
13268
13823
  init_host_routes();
13269
13824
  init_http_route_probe();
@@ -13271,6 +13826,7 @@ var init_workspace_ensure = __esm({
13271
13826
  init_managed_post_start();
13272
13827
  init_managed_runtime_state();
13273
13828
  init_managed_runtime_status();
13829
+ init_managed_stop_recovery();
13274
13830
  init_reliability_lifecycle();
13275
13831
  init_repo_config();
13276
13832
  init_route_publication();
@@ -13292,50 +13848,51 @@ async function withLifecycleOperationLock(repoPath, operation) {
13292
13848
  }
13293
13849
  return withWorkspaceLifecycleLock(repoPath, operation);
13294
13850
  }
13295
- function stepRecord(record3, event) {
13296
- const transition2 = stepReliability(record3.state, event, Date.now());
13851
+ function stepRecord(record4, event) {
13852
+ const transition2 = stepReliability(record4.state, event, Date.now());
13297
13853
  if (!["accepted", "joined"].includes(transition2.outcome))
13298
13854
  throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
13299
- record3.state = transition2.state;
13855
+ record4.state = transition2.state;
13300
13856
  }
13301
- function reconcileDrained(record3) {
13302
- const worker = record3.worker;
13857
+ function reconcileDrained(record4) {
13858
+ const worker = record4.worker;
13303
13859
  if (!worker) {
13304
- if (record3.state.operation?.status === "NOT_STARTED" && !record3.state.operation.drained) {
13305
- stepRecord(record3, {
13306
- ...reliabilityFence(record3.state),
13860
+ if (record4.state.operation?.status === "NOT_STARTED" && !record4.state.operation.drained) {
13861
+ stepRecord(record4, {
13862
+ ...reliabilityFence(record4.state),
13307
13863
  type: "drained",
13308
- operationId: record3.state.operation.id
13864
+ operationId: record4.state.operation.id
13309
13865
  });
13310
13866
  }
13311
13867
  return;
13312
13868
  }
13313
13869
  if (processBirthIdentity(worker.pid) === worker.birth || !workerGroupAbsent(worker.pid)) return;
13314
- if (record3.state.operation) {
13870
+ if (record4.state.operation) {
13315
13871
  const interrupted = stepReliability(
13316
- record3.state,
13872
+ record4.state,
13317
13873
  {
13318
- ...reliabilityFence(record3.state),
13874
+ ...reliabilityFence(record4.state),
13319
13875
  type: "interrupted",
13320
13876
  operationId: worker.operationId
13321
13877
  },
13322
13878
  Date.now()
13323
13879
  );
13324
- record3.state = interrupted.state;
13325
- stepRecord(record3, {
13326
- ...reliabilityFence(record3.state),
13880
+ record4.state = interrupted.state;
13881
+ stepRecord(record4, {
13882
+ ...reliabilityFence(record4.state),
13327
13883
  type: "drained",
13328
13884
  operationId: worker.operationId
13329
13885
  });
13330
- record3.worker = null;
13886
+ record4.worker = null;
13331
13887
  }
13332
13888
  }
13333
13889
  async function superviseLifecycle(kind, repoPath, options = {}, command) {
13334
13890
  repoPath = comparableWorkspacePath(repoPath);
13335
13891
  if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
13892
+ if (kind === "stop") throw new Error("Stop requires the existing linked workspace identity.");
13336
13893
  resolveLinkedTarget(repoPath);
13337
13894
  }
13338
- const identity = {
13895
+ const identity2 = {
13339
13896
  repoPath,
13340
13897
  workspace: resolveWorktreeWorkspace(repoPath) ?? null,
13341
13898
  provider: resolveWorkspaceRuntimeOrDefault(repoPath)
@@ -13343,48 +13900,67 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
13343
13900
  const ids = newLifecycleIds();
13344
13901
  let fence;
13345
13902
  const runtimeRunning = kind === "exec" ? Boolean(resolveRunningWorkspaceContainer(repoPath)) : false;
13346
- updateReliabilityOperation(identity, (record3) => {
13347
- reconcileDrained(record3);
13903
+ const previous = kind === "exec" && identity2.provider === "devsy" ? updateReliabilityOperation(identity2, (record4) => {
13904
+ reconcileDrained(record4);
13905
+ return record4;
13906
+ }) : void 0;
13907
+ let retainedExecProof;
13908
+ if (previous && !previous.worker && canExecAfterInterruptedEnsure(previous.state)) {
13909
+ retainedExecProof = withMutationLock(
13910
+ "Prove retained exec",
13911
+ repoPath,
13912
+ () => captureDevsyExecProof(repoPath)
13913
+ );
13914
+ }
13915
+ updateReliabilityOperation(identity2, (record4) => {
13916
+ reconcileDrained(record4);
13917
+ if (retainedExecProof && record4.state.operation?.id !== previous?.state.operation?.id)
13918
+ throw new Error("Lifecycle operation changed during retained exec proof.");
13348
13919
  if (kind === "stop") {
13349
- stepRecord(record3, { ...reliabilityFence(record3.state), type: "stop" });
13920
+ stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
13350
13921
  } else {
13351
- if (record3.worker)
13922
+ if (record4.worker)
13352
13923
  throw new Error(
13353
13924
  "An earlier lifecycle worker may still be active; use explicit stop to reconcile it."
13354
13925
  );
13355
- record3.outcome = null;
13356
- stepRecord(record3, {
13357
- ...reliabilityFence(record3.state),
13926
+ record4.outcome = null;
13927
+ stepRecord(record4, {
13928
+ ...reliabilityFence(record4.state),
13358
13929
  type: "operation-request",
13359
13930
  kind,
13360
13931
  key: ids.requestId,
13361
13932
  operationId: ids.operationId,
13362
- profile: options.profile ?? record3.state.profile ?? "full",
13933
+ profile: options.profile ?? record4.state.profile ?? "full",
13363
13934
  consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
13364
- runtimeRunning
13935
+ runtimeRunning,
13936
+ ...retainedExecProof ? { recoverInterruptedEnsure: true } : {}
13365
13937
  });
13366
13938
  }
13367
- fence = reliabilityFence(record3.state);
13939
+ fence = reliabilityFence(record4.state);
13368
13940
  });
13369
13941
  return runLifecycleWorker({
13370
13942
  kind,
13371
13943
  repoPath,
13372
- identity,
13944
+ identity: identity2,
13373
13945
  ...ids,
13374
13946
  fence,
13375
13947
  options,
13376
- ...command ? { command } : {}
13948
+ ...command ? { command } : {},
13949
+ ...retainedExecProof ? { retainedExecProof } : {}
13377
13950
  });
13378
13951
  }
13379
- var import_node_path37, activeWorker, lockHeld;
13952
+ var import_node_path41, activeWorker, lockHeld;
13380
13953
  var init_reliability_lifecycle = __esm({
13381
13954
  "src/core/reliability-lifecycle.ts"() {
13382
13955
  "use strict";
13383
- import_node_path37 = __toESM(require("path"));
13956
+ import_node_path41 = __toESM(require("path"));
13384
13957
  init_devpod_environment();
13958
+ init_devsy_exec_proof();
13959
+ init_devsy_mutation();
13385
13960
  init_file_lock();
13386
13961
  init_host_routes();
13387
13962
  init_managed_runtime_state();
13963
+ init_managed_stop_recovery();
13388
13964
  init_reliability_context();
13389
13965
  init_reliability_contract();
13390
13966
  init_reliability_model();
@@ -13788,17 +14364,17 @@ function asRecord3(value) {
13788
14364
  return value;
13789
14365
  }
13790
14366
  function readJson(filePath) {
13791
- if (!import_node_fs34.default.existsSync(filePath)) {
14367
+ if (!import_node_fs35.default.existsSync(filePath)) {
13792
14368
  return void 0;
13793
14369
  }
13794
14370
  try {
13795
- return JSON.parse(import_node_fs34.default.readFileSync(filePath, "utf-8"));
14371
+ return JSON.parse(import_node_fs35.default.readFileSync(filePath, "utf-8"));
13796
14372
  } catch {
13797
14373
  return void 0;
13798
14374
  }
13799
14375
  }
13800
14376
  function relative(repoPath, filePath) {
13801
- return import_node_path38.default.relative(repoPath, filePath) || ".";
14377
+ return import_node_path42.default.relative(repoPath, filePath) || ".";
13802
14378
  }
13803
14379
  function redactEnvAssignments(value) {
13804
14380
  return value.replace(
@@ -13839,7 +14415,7 @@ function inspectPackageManager(repoPath, pkg) {
13839
14415
  ["bun.lock", "bun"]
13840
14416
  ];
13841
14417
  for (const [fileName, name] of lockfiles) {
13842
- if (import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))) {
14418
+ if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
13843
14419
  return { name, source: fileName };
13844
14420
  }
13845
14421
  }
@@ -13854,9 +14430,9 @@ function inspectNode(repoPath, pkg) {
13854
14430
  if (typeof engines?.node === "string") {
13855
14431
  return { version: engines.node, source: "package.json:engines.node" };
13856
14432
  }
13857
- const nvmrc = import_node_path38.default.join(repoPath, ".nvmrc");
13858
- if (import_node_fs34.default.existsSync(nvmrc)) {
13859
- const version = import_node_fs34.default.readFileSync(nvmrc, "utf-8").trim();
14433
+ const nvmrc = import_node_path42.default.join(repoPath, ".nvmrc");
14434
+ if (import_node_fs35.default.existsSync(nvmrc)) {
14435
+ const version = import_node_fs35.default.readFileSync(nvmrc, "utf-8").trim();
13860
14436
  return { version, source: ".nvmrc" };
13861
14437
  }
13862
14438
  return void 0;
@@ -13917,7 +14493,7 @@ function configuredComposeFiles(repoPath) {
13917
14493
  const files = config.apps.filter(
13918
14494
  (app) => app.runtime === "docker"
13919
14495
  ).flatMap((app) => app.docker.composeFiles).filter(
13920
- (fileName) => !import_node_path38.default.isAbsolute(fileName) && !import_node_path38.default.normalize(fileName).startsWith("..")
14496
+ (fileName) => !import_node_path42.default.isAbsolute(fileName) && !import_node_path42.default.normalize(fileName).startsWith("..")
13921
14497
  );
13922
14498
  return Array.from(new Set(files));
13923
14499
  } catch {
@@ -13935,7 +14511,7 @@ function composeFiles(repoPath) {
13935
14511
  ...configuredComposeFiles(repoPath)
13936
14512
  ];
13937
14513
  return Array.from(new Set(candidates)).filter(
13938
- (fileName) => import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))
14514
+ (fileName) => import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))
13939
14515
  );
13940
14516
  }
13941
14517
  function stringArray2(value) {
@@ -13948,8 +14524,8 @@ function envNames(value) {
13948
14524
  if (Array.isArray(value)) {
13949
14525
  return value.filter((entry) => typeof entry === "string").map((entry) => entry.split("=")[0]?.trim()).filter((entry) => Boolean(entry));
13950
14526
  }
13951
- const record3 = asRecord3(value);
13952
- return record3 ? Object.keys(record3).sort() : [];
14527
+ const record4 = asRecord3(value);
14528
+ return record4 ? Object.keys(record4).sort() : [];
13953
14529
  }
13954
14530
  function inferServiceKind(name, image) {
13955
14531
  const source = `${name} ${image ?? ""}`.toLowerCase();
@@ -13973,7 +14549,7 @@ function inspectServices(repoPath) {
13973
14549
  const services = [];
13974
14550
  for (const fileName of composeFiles(repoPath)) {
13975
14551
  try {
13976
- const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(import_node_path38.default.join(repoPath, fileName), "utf-8"));
14552
+ const parsed = import_yaml6.default.parse(import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8"));
13977
14553
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
13978
14554
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
13979
14555
  const service = asRecord3(value);
@@ -14008,8 +14584,8 @@ function inspectServices(repoPath) {
14008
14584
  return services;
14009
14585
  }
14010
14586
  function inspectEnvFiles(repoPath) {
14011
- const files = import_node_fs34.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
14012
- const content = import_node_fs34.default.readFileSync(import_node_path38.default.join(repoPath, fileName), "utf-8");
14587
+ const files = import_node_fs35.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
14588
+ const content = import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8");
14013
14589
  const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
14014
14590
  return { path: fileName, names };
14015
14591
  });
@@ -14023,18 +14599,18 @@ function inspectEnvFiles(repoPath) {
14023
14599
  };
14024
14600
  }
14025
14601
  function inspectDevcontainer(repoPath) {
14026
- const dir = import_node_path38.default.join(repoPath, ".devcontainer");
14027
- if (!import_node_fs34.default.existsSync(dir)) {
14602
+ const dir = import_node_path42.default.join(repoPath, ".devcontainer");
14603
+ if (!import_node_fs35.default.existsSync(dir)) {
14028
14604
  return { exists: false, files: [] };
14029
14605
  }
14030
14606
  return {
14031
14607
  exists: true,
14032
- files: import_node_fs34.default.readdirSync(dir).filter((fileName) => import_node_fs34.default.statSync(import_node_path38.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
14608
+ files: import_node_fs35.default.readdirSync(dir).filter((fileName) => import_node_fs35.default.statSync(import_node_path42.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
14033
14609
  };
14034
14610
  }
14035
14611
  function inspectDevrouter(repoPath) {
14036
14612
  const configPath = getRepoConfigPath(repoPath);
14037
- if (!import_node_fs34.default.existsSync(configPath)) {
14613
+ if (!import_node_fs35.default.existsSync(configPath)) {
14038
14614
  return {
14039
14615
  exists: false,
14040
14616
  configPath,
@@ -14078,15 +14654,15 @@ function inspectAgentGuidance(repoPath) {
14078
14654
  ["AGENTS.md", "agents"],
14079
14655
  ["CLAUDE.md", "claude"]
14080
14656
  ]) {
14081
- if (import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))) {
14657
+ if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
14082
14658
  results.push({ path: fileName, kind });
14083
14659
  }
14084
14660
  }
14085
- const skillsDir = import_node_path38.default.join(repoPath, ".agents", "skills");
14086
- if (import_node_fs34.default.existsSync(skillsDir)) {
14087
- for (const name of import_node_fs34.default.readdirSync(skillsDir).sort()) {
14088
- const skillPath = import_node_path38.default.join(skillsDir, name, "SKILL.md");
14089
- if (import_node_fs34.default.existsSync(skillPath)) {
14661
+ const skillsDir = import_node_path42.default.join(repoPath, ".agents", "skills");
14662
+ if (import_node_fs35.default.existsSync(skillsDir)) {
14663
+ for (const name of import_node_fs35.default.readdirSync(skillsDir).sort()) {
14664
+ const skillPath = import_node_path42.default.join(skillsDir, name, "SKILL.md");
14665
+ if (import_node_fs35.default.existsSync(skillPath)) {
14090
14666
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
14091
14667
  }
14092
14668
  }
@@ -14129,7 +14705,7 @@ function buildIssues(report) {
14129
14705
  }
14130
14706
  function inspectRepo(options = {}) {
14131
14707
  const repoPath = resolveRepoPath(options.repo);
14132
- const pkg = readJson(import_node_path38.default.join(repoPath, "package.json"));
14708
+ const pkg = readJson(import_node_path42.default.join(repoPath, "package.json"));
14133
14709
  const scripts = inspectScripts(pkg);
14134
14710
  const reportWithoutIssues = {
14135
14711
  repoPath,
@@ -14148,12 +14724,12 @@ function inspectRepo(options = {}) {
14148
14724
  issues: buildIssues(reportWithoutIssues)
14149
14725
  };
14150
14726
  }
14151
- var import_node_fs34, import_node_path38, import_yaml6;
14727
+ var import_node_fs35, import_node_path42, import_yaml6;
14152
14728
  var init_repo_inspect = __esm({
14153
14729
  "src/core/repo-inspect.ts"() {
14154
14730
  "use strict";
14155
- import_node_fs34 = __toESM(require("fs"));
14156
- import_node_path38 = __toESM(require("path"));
14731
+ import_node_fs35 = __toESM(require("fs"));
14732
+ import_node_path42 = __toESM(require("path"));
14157
14733
  import_yaml6 = __toESM(require("yaml"));
14158
14734
  init_repo_config();
14159
14735
  }
@@ -14258,7 +14834,7 @@ function requiredFileChecks(repoPath) {
14258
14834
  ".devcontainer/docker-compose.yml",
14259
14835
  ".devrouter.yml"
14260
14836
  ];
14261
- const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(import_node_path39.default.join(repoPath, fileName)));
14837
+ const missing = required.filter((fileName) => !import_node_fs36.default.existsSync(import_node_path43.default.join(repoPath, fileName)));
14262
14838
  return {
14263
14839
  id: "repo.devcontainer.verify-files",
14264
14840
  level: missing.length === 0 ? "ok" : "error",
@@ -14483,12 +15059,12 @@ async function verifyDevcontainer(options = {}) {
14483
15059
  nextSteps: collectNextSteps3(checks)
14484
15060
  };
14485
15061
  }
14486
- var import_node_fs35, import_node_path39;
15062
+ var import_node_fs36, import_node_path43;
14487
15063
  var init_devcontainer_verify = __esm({
14488
15064
  "src/core/devcontainer-verify.ts"() {
14489
15065
  "use strict";
14490
- import_node_fs35 = __toESM(require("fs"));
14491
- import_node_path39 = __toESM(require("path"));
15066
+ import_node_fs36 = __toESM(require("fs"));
15067
+ import_node_path43 = __toESM(require("path"));
14492
15068
  init_capabilities();
14493
15069
  init_doctor();
14494
15070
  init_host_routes();
@@ -14785,7 +15361,7 @@ function packageManagerIssues(repo) {
14785
15361
  }
14786
15362
  function plannedFiles(repoPath, version) {
14787
15363
  const repo = inspectRepo({ repo: repoPath });
14788
- const projectName = sanitizeProjectName(import_node_path40.default.basename(repo.repoPath));
15364
+ const projectName = sanitizeProjectName(import_node_path44.default.basename(repo.repoPath));
14789
15365
  const nodeMajor = majorVersion(repo.node?.version, "24");
14790
15366
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
14791
15367
  const port = inferPort2(repo);
@@ -14830,8 +15406,8 @@ function plannedFiles(repoPath, version) {
14830
15406
  };
14831
15407
  }
14832
15408
  function classifyFile(repoPath, file) {
14833
- const absolutePath = import_node_path40.default.join(repoPath, file.relativePath);
14834
- if (!import_node_fs36.default.existsSync(absolutePath)) {
15409
+ const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
15410
+ if (!import_node_fs37.default.existsSync(absolutePath)) {
14835
15411
  return {
14836
15412
  path: file.relativePath,
14837
15413
  action: "create",
@@ -14839,7 +15415,7 @@ function classifyFile(repoPath, file) {
14839
15415
  bytes: Buffer.byteLength(file.content)
14840
15416
  };
14841
15417
  }
14842
- const current = import_node_fs36.default.readFileSync(absolutePath, "utf-8");
15418
+ const current = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
14843
15419
  if (!current.includes(MANAGED_MARKER2)) {
14844
15420
  return {
14845
15421
  path: file.relativePath,
@@ -14896,11 +15472,11 @@ function buildPlan(repoPath, dryRun, version) {
14896
15472
  };
14897
15473
  }
14898
15474
  function writeFile(repoPath, file) {
14899
- const absolutePath = import_node_path40.default.join(repoPath, file.relativePath);
14900
- import_node_fs36.default.mkdirSync(import_node_path40.default.dirname(absolutePath), { recursive: true });
14901
- import_node_fs36.default.writeFileSync(absolutePath, file.content, "utf-8");
15475
+ const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
15476
+ import_node_fs37.default.mkdirSync(import_node_path44.default.dirname(absolutePath), { recursive: true });
15477
+ import_node_fs37.default.writeFileSync(absolutePath, file.content, "utf-8");
14902
15478
  if (file.executable) {
14903
- import_node_fs36.default.chmodSync(absolutePath, 493);
15479
+ import_node_fs37.default.chmodSync(absolutePath, 493);
14904
15480
  }
14905
15481
  }
14906
15482
  function writeDevcontainer(options = {}) {
@@ -14938,12 +15514,12 @@ function writeDevcontainer(options = {}) {
14938
15514
  nextSteps: postWriteNextSteps(repoPath)
14939
15515
  };
14940
15516
  }
14941
- var import_node_fs36, import_node_path40, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
15517
+ var import_node_fs37, import_node_path44, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
14942
15518
  var init_devcontainer_write = __esm({
14943
15519
  "src/core/devcontainer-write.ts"() {
14944
15520
  "use strict";
14945
- import_node_fs36 = __toESM(require("fs"));
14946
- import_node_path40 = __toESM(require("path"));
15521
+ import_node_fs37 = __toESM(require("fs"));
15522
+ import_node_path44 = __toESM(require("path"));
14947
15523
  init_repo_config();
14948
15524
  init_repo_inspect();
14949
15525
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -15277,13 +15853,13 @@ function buildProfilePlanReport(options) {
15277
15853
  return { ...report, contractPath, bindings };
15278
15854
  }
15279
15855
  function loadContract(repoPath, contractPath) {
15280
- if (import_node_path41.default.isAbsolute(contractPath)) {
15856
+ if (import_node_path45.default.isAbsolute(contractPath)) {
15281
15857
  fail3("profile plan contract path must be relative to the repository.");
15282
15858
  }
15283
15859
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
15284
15860
  let metadata;
15285
15861
  try {
15286
- metadata = import_node_fs37.default.lstatSync(absolutePath);
15862
+ metadata = import_node_fs38.default.lstatSync(absolutePath);
15287
15863
  } catch (error) {
15288
15864
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
15289
15865
  }
@@ -15293,16 +15869,16 @@ function loadContract(repoPath, contractPath) {
15293
15869
  if (metadata.size > MAX_CONTRACT_BYTES) {
15294
15870
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
15295
15871
  }
15296
- const realRepo = import_node_fs37.default.realpathSync(repoPath);
15297
- const realContract = import_node_fs37.default.realpathSync(absolutePath);
15872
+ const realRepo = import_node_fs38.default.realpathSync(repoPath);
15873
+ const realContract = import_node_fs38.default.realpathSync(absolutePath);
15298
15874
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
15299
- const source = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
15875
+ const source = import_node_fs38.default.readFileSync(absolutePath, "utf-8");
15300
15876
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
15301
15877
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
15302
15878
  }
15303
15879
  return {
15304
15880
  contract: parseProfilePlanContract(source),
15305
- contractPath: import_node_path41.default.relative(repoPath, absolutePath).split(import_node_path41.default.sep).join("/")
15881
+ contractPath: import_node_path45.default.relative(repoPath, absolutePath).split(import_node_path45.default.sep).join("/")
15306
15882
  };
15307
15883
  }
15308
15884
  function resolveProfilePlan(options) {
@@ -15316,12 +15892,12 @@ function resolveProfilePlan(options) {
15316
15892
  contractPath: loaded.contractPath
15317
15893
  });
15318
15894
  }
15319
- var import_node_fs37, import_node_path41, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
15895
+ var import_node_fs38, import_node_path45, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
15320
15896
  var init_profile_plan = __esm({
15321
15897
  "src/core/profile-plan.ts"() {
15322
15898
  "use strict";
15323
- import_node_fs37 = __toESM(require("fs"));
15324
- import_node_path41 = __toESM(require("path"));
15899
+ import_node_fs38 = __toESM(require("fs"));
15900
+ import_node_path45 = __toESM(require("path"));
15325
15901
  import_yaml7 = require("yaml");
15326
15902
  init_paths();
15327
15903
  init_profile_resolution();
@@ -15353,7 +15929,7 @@ async function runProfileResolveCommand(options) {
15353
15929
  async function runProfilePlanCommand(options) {
15354
15930
  const report = resolveProfilePlan(options);
15355
15931
  if (options.output) {
15356
- writeFileAtomically(import_node_path42.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15932
+ writeFileAtomically(import_node_path46.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15357
15933
  `);
15358
15934
  }
15359
15935
  if (options.json) {
@@ -15389,14 +15965,14 @@ function printProfilePlanSummary(report, output2) {
15389
15965
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
15390
15966
  `);
15391
15967
  }
15392
- if (output2) process.stdout.write(`Output: ${import_node_path42.default.resolve(output2)}
15968
+ if (output2) process.stdout.write(`Output: ${import_node_path46.default.resolve(output2)}
15393
15969
  `);
15394
15970
  }
15395
- var import_node_path42;
15971
+ var import_node_path46;
15396
15972
  var init_profile = __esm({
15397
15973
  "src/commands/profile.ts"() {
15398
15974
  "use strict";
15399
- import_node_path42 = __toESM(require("path"));
15975
+ import_node_path46 = __toESM(require("path"));
15400
15976
  init_atomic_file();
15401
15977
  init_output();
15402
15978
  init_profile_plan();
@@ -15686,7 +16262,7 @@ function sanitizeRouterId(value) {
15686
16262
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
15687
16263
  }
15688
16264
  function repoHash(repoPath) {
15689
- return (0, import_node_crypto17.createHash)("sha1").update(import_node_path43.default.resolve(repoPath)).digest("hex").slice(0, 12);
16265
+ return (0, import_node_crypto17.createHash)("sha1").update(import_node_path47.default.resolve(repoPath)).digest("hex").slice(0, 12);
15690
16266
  }
15691
16267
  function asDockerApp(app) {
15692
16268
  return app.runtime === "docker";
@@ -15765,11 +16341,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
15765
16341
  if (dockerApps.length === 0) {
15766
16342
  throw new Error("No docker apps selected to prepare compose overlay.");
15767
16343
  }
15768
- const cachePath = import_node_path43.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
15769
- import_node_fs38.default.mkdirSync(cachePath, { recursive: true });
15770
- const overlayPath = import_node_path43.default.join(cachePath, "compose.devrouter.yml");
16344
+ const cachePath = import_node_path47.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
16345
+ import_node_fs39.default.mkdirSync(cachePath, { recursive: true });
16346
+ const overlayPath = import_node_path47.default.join(cachePath, "compose.devrouter.yml");
15771
16347
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
15772
- import_node_fs38.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
16348
+ import_node_fs39.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
15773
16349
  return {
15774
16350
  overlayPath,
15775
16351
  composeFiles: ensureComposeFiles(dockerApps),
@@ -15889,14 +16465,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
15889
16465
  const port = Number(match[1]);
15890
16466
  return Number.isInteger(port) && port > 0 ? port : void 0;
15891
16467
  }
15892
- var import_node_child_process27, import_node_crypto17, import_node_fs38, import_node_path43, import_yaml8;
16468
+ var import_node_child_process27, import_node_crypto17, import_node_fs39, import_node_path47, import_yaml8;
15893
16469
  var init_docker_run = __esm({
15894
16470
  "src/core/docker-run.ts"() {
15895
16471
  "use strict";
15896
16472
  import_node_child_process27 = require("child_process");
15897
16473
  import_node_crypto17 = require("crypto");
15898
- import_node_fs38 = __toESM(require("fs"));
15899
- import_node_path43 = __toESM(require("path"));
16474
+ import_node_fs39 = __toESM(require("fs"));
16475
+ import_node_path47 = __toESM(require("path"));
15900
16476
  import_yaml8 = __toESM(require("yaml"));
15901
16477
  init_docker_error_guidance();
15902
16478
  init_paths();
@@ -16718,13 +17294,13 @@ function measureWorktreeConsumption(worktreePath, options) {
16718
17294
  const startedAt = Date.now();
16719
17295
  let rootStat;
16720
17296
  try {
16721
- rootStat = import_node_fs39.default.lstatSync(worktreePath);
17297
+ rootStat = import_node_fs40.default.lstatSync(worktreePath);
16722
17298
  } catch (error) {
16723
17299
  return { status: "unknown", reason: describeError(error, worktreePath) };
16724
17300
  }
16725
17301
  let rootEntries;
16726
17302
  try {
16727
- rootEntries = import_node_fs39.default.readdirSync(worktreePath, { withFileTypes: true });
17303
+ rootEntries = import_node_fs40.default.readdirSync(worktreePath, { withFileTypes: true });
16728
17304
  } catch (error) {
16729
17305
  return { status: "unknown", reason: describeError(error, worktreePath) };
16730
17306
  }
@@ -16750,10 +17326,10 @@ function measureWorktreeConsumption(worktreePath, options) {
16750
17326
  timedOut = true;
16751
17327
  break;
16752
17328
  }
16753
- const entryPath = import_node_path44.default.join(dirPath, entry.name);
17329
+ const entryPath = import_node_path48.default.join(dirPath, entry.name);
16754
17330
  let entryStat;
16755
17331
  try {
16756
- entryStat = import_node_fs39.default.lstatSync(entryPath);
17332
+ entryStat = import_node_fs40.default.lstatSync(entryPath);
16757
17333
  } catch (error) {
16758
17334
  if (error?.code === "ENOENT") continue;
16759
17335
  unreadableReason = describeIncompleteWalk(error);
@@ -16763,7 +17339,7 @@ function measureWorktreeConsumption(worktreePath, options) {
16763
17339
  if (!entryStat.isDirectory()) continue;
16764
17340
  let childEntries;
16765
17341
  try {
16766
- childEntries = import_node_fs39.default.readdirSync(entryPath, { withFileTypes: true });
17342
+ childEntries = import_node_fs40.default.readdirSync(entryPath, { withFileTypes: true });
16767
17343
  } catch (error) {
16768
17344
  unreadableReason = describeIncompleteWalk(error);
16769
17345
  break;
@@ -16827,12 +17403,12 @@ function describeError(error, worktreePath) {
16827
17403
  }
16828
17404
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
16829
17405
  }
16830
- var import_node_fs39, import_node_path44, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
17406
+ var import_node_fs40, import_node_path48, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
16831
17407
  var init_workspace_consumption = __esm({
16832
17408
  "src/core/workspace-consumption.ts"() {
16833
17409
  "use strict";
16834
- import_node_fs39 = __toESM(require("fs"));
16835
- import_node_path44 = __toESM(require("path"));
17410
+ import_node_fs40 = __toESM(require("fs"));
17411
+ import_node_path48 = __toESM(require("path"));
16836
17412
  init_devpod_environment();
16837
17413
  DEFAULT_DEADLINE_MS = 1e4;
16838
17414
  BLOCK_SIZE_BYTES = 512;
@@ -16933,7 +17509,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
16933
17509
  }
16934
17510
  function readGitSnapshot(worktree, commandRunner) {
16935
17511
  const comparablePath = comparableWorkspacePath(worktree.path);
16936
- if (worktree.prunable || !import_node_fs40.default.existsSync(comparablePath)) {
17512
+ if (worktree.prunable || !import_node_fs41.default.existsSync(comparablePath)) {
16937
17513
  return { worktree, checkout: "missing", head: null, committerDate: null };
16938
17514
  }
16939
17515
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -17082,8 +17658,8 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17082
17658
  return { status: "unknown", reason: "Current HEAD or branch evidence is unavailable." };
17083
17659
  }
17084
17660
  const originUrl = gitOutput(repoPath, ["config", "--get", "remote.origin.url"], commandRunner);
17085
- const identity = originUrl ? parseRemoteIdentity(originUrl) : void 0;
17086
- if (!identity) {
17661
+ const identity2 = originUrl ? parseRemoteIdentity(originUrl) : void 0;
17662
+ if (!identity2) {
17087
17663
  return { status: "unknown", reason: "The origin is missing or uses an unsupported forge." };
17088
17664
  }
17089
17665
  const localTargetBranch = parseTargetBranch(
@@ -17128,11 +17704,11 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17128
17704
  };
17129
17705
  }
17130
17706
  const forgeChanges = [];
17131
- const forgeCommand = identity.provider === "github" ? [
17707
+ const forgeCommand = identity2.provider === "github" ? [
17132
17708
  "pr",
17133
17709
  "list",
17134
17710
  "--repo",
17135
- identity.project,
17711
+ identity2.project,
17136
17712
  "--state",
17137
17713
  "all",
17138
17714
  "--head",
@@ -17143,20 +17719,20 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17143
17719
  "mr",
17144
17720
  "list",
17145
17721
  "--repo",
17146
- identity.project,
17722
+ identity2.project,
17147
17723
  "--all",
17148
17724
  "--source-branch",
17149
17725
  branch,
17150
17726
  "--output",
17151
17727
  "json"
17152
17728
  ];
17153
- const forge = commandRunner(identity.provider === "github" ? "gh" : "glab", forgeCommand);
17729
+ const forge = commandRunner(identity2.provider === "github" ? "gh" : "glab", forgeCommand);
17154
17730
  if (forge.status !== 0 || forge.error) {
17155
17731
  return { status: "unknown", reason: "The forge query was unavailable or unauthenticated." };
17156
17732
  }
17157
17733
  try {
17158
17734
  const parsed = JSON.parse(forge.stdout);
17159
- const changes = identity.provider === "github" ? parseGitHubChanges(parsed, identity.project, branch) : parseGitLabChanges(parsed, identity.project, branch);
17735
+ const changes = identity2.provider === "github" ? parseGitHubChanges(parsed, identity2.project, branch) : parseGitLabChanges(parsed, identity2.project, branch);
17160
17736
  if (!changes) {
17161
17737
  return { status: "unknown", reason: "The forge response was malformed." };
17162
17738
  }
@@ -17222,31 +17798,31 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17222
17798
  reason: "Fresh target and forge evidence did not prove integration."
17223
17799
  };
17224
17800
  }
17225
- function routeStatus(record3, routes) {
17801
+ function routeStatus(record4, routes) {
17226
17802
  if (!routes) return "unknown";
17227
17803
  const matchingPath = routes.filter(
17228
- (route) => sameWorkspacePath(route.repoPath, record3.worktreePath)
17804
+ (route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
17229
17805
  );
17230
17806
  if (matchingPath.length === 0) return "absent";
17231
- return matchingPath.every((route) => route.workspace === record3.workspace) ? "owned" : "conflict";
17807
+ return matchingPath.every((route) => route.workspace === record4.workspace) ? "owned" : "conflict";
17232
17808
  }
17233
- function buildActivityEvidence(record3, providerStatus, provider, routeEntries, git) {
17809
+ function buildActivityEvidence(record4, providerStatus, provider, routeEntries, git) {
17234
17810
  const providerEvidence = providerStatus === "unknown" || providerStatus === "conflict" ? { source: "devpod.lastUsed", status: "unavailable" } : providerStatus === "absent" ? { source: "devpod.lastUsed", status: "not-applicable" } : provider?.lastUsedMalformed ? { source: "devpod.lastUsed", status: "malformed" } : provider?.lastUsed ? validTimestamp(provider.lastUsed) ? { source: "devpod.lastUsed", status: "valid", timestamp: provider.lastUsed } : { source: "devpod.lastUsed", status: "malformed" } : { source: "devpod.lastUsed", status: "missing" };
17235
17811
  const routeTimestamps = routeEntries?.map((route) => route.updatedAt) ?? [];
17236
17812
  const malformedRoute = routeTimestamps.some((timestamp) => !validTimestamp(timestamp));
17237
17813
  const newestRoute = routeTimestamps.filter(validTimestamp).sort((left, right) => (parseTimestamp(right) ?? 0) - (parseTimestamp(left) ?? 0))[0];
17238
17814
  const routeEvidence = !routeEntries ? { source: "route.updatedAt", status: "unavailable" } : routeEntries.length === 0 ? { source: "route.updatedAt", status: "not-applicable" } : malformedRoute ? { source: "route.updatedAt", status: "malformed" } : newestRoute ? { source: "route.updatedAt", status: "valid", timestamp: newestRoute } : { source: "route.updatedAt", status: "malformed" };
17239
- const ownerEvidence = validTimestamp(record3.updatedAt) ? { source: "ownership.updatedAt", status: "valid", timestamp: record3.updatedAt } : { source: "ownership.updatedAt", status: "malformed" };
17815
+ const ownerEvidence = validTimestamp(record4.updatedAt) ? { source: "ownership.updatedAt", status: "valid", timestamp: record4.updatedAt } : { source: "ownership.updatedAt", status: "malformed" };
17240
17816
  const gitEvidence = git.checkout === "missing" ? { source: "git.headCommitterDate", status: "not-applicable" } : git.committerDate && validTimestamp(git.committerDate) ? { source: "git.headCommitterDate", status: "valid", timestamp: git.committerDate } : git.checkout === "unknown" ? { source: "git.headCommitterDate", status: "unavailable" } : { source: "git.headCommitterDate", status: "malformed" };
17241
17817
  return [providerEvidence, routeEvidence, ownerEvidence, gitEvidence];
17242
17818
  }
17243
- function buildSuggestions(repoPath, record3, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
17819
+ function buildSuggestions(repoPath, record4, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
17244
17820
  const reasons = [];
17245
17821
  const eligibleActions = [];
17246
17822
  const suggestions = [];
17247
17823
  const gcCommand = workspaceCommand(repoPath, ["gc"], [], ["--yes"]);
17248
- const keepCommand = workspaceCommand(repoPath, ["down", record3.workspace], ["--keep-worktree"]);
17249
- const downCommand = workspaceCommand(repoPath, ["down", record3.workspace]);
17824
+ const keepCommand = workspaceCommand(repoPath, ["down", record4.workspace], ["--keep-worktree"]);
17825
+ const downCommand = workspaceCommand(repoPath, ["down", record4.workspace]);
17250
17826
  const routeSafe = route === "owned" || route === "absent";
17251
17827
  const checkoutSafe = checkout === "clean" && !worktree?.locked;
17252
17828
  const providerSafe = provider === "owned";
@@ -17324,17 +17900,17 @@ function buildSuggestions(repoPath, record3, ownership, provider, runtime, check
17324
17900
  }
17325
17901
  return { eligibleActions, suggestions, reasons };
17326
17902
  }
17327
- function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
17328
- const ownershipEvidence = inspectOwnershipFn(record3, worktrees, devpods);
17903
+ function buildRow(repoPath, record4, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
17904
+ const ownershipEvidence = inspectOwnershipFn(record4, worktrees, devpods);
17329
17905
  const providerOwnership = devpods?.find(
17330
- (devpod) => devpod.id === record3.devpodId && sameWorkspacePath(devpod.source.localFolder, record3.worktreePath)
17906
+ (devpod) => devpod.id === record4.devpodId && sameWorkspacePath(devpod.source.localFolder, record4.worktreePath)
17331
17907
  );
17332
17908
  const matchingRoutes = routes?.filter(
17333
- (route) => sameWorkspacePath(route.repoPath, record3.worktreePath)
17909
+ (route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
17334
17910
  );
17335
17911
  const activity = evaluateWorkspaceActivity(
17336
17912
  buildActivityEvidence(
17337
- record3,
17913
+ record4,
17338
17914
  ownershipEvidence.devpodStatus,
17339
17915
  providerOwnership,
17340
17916
  matchingRoutes,
@@ -17342,23 +17918,23 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17342
17918
  ),
17343
17919
  cutoff
17344
17920
  );
17345
- const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record3.branch;
17921
+ const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record4.branch;
17346
17922
  const integration = inspectIntegrationFn?.(repoPath, snapshot, branch, checkMerged) ?? inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, commandRunner);
17347
17923
  const runtime = ownershipEvidence.devpodStatus === "absent" ? "absent" : ownershipEvidence.devpodStatus === "owned" ? (() => {
17348
17924
  try {
17349
- return inspectRuntimeFn(record3.devpodId);
17925
+ return inspectRuntimeFn(record4.devpodId);
17350
17926
  } catch {
17351
17927
  return "unknown";
17352
17928
  }
17353
17929
  })() : "unknown";
17354
17930
  const suggestions = buildSuggestions(
17355
17931
  repoPath,
17356
- record3,
17932
+ record4,
17357
17933
  ownershipEvidence.ownerStatus,
17358
17934
  ownershipEvidence.devpodStatus,
17359
17935
  runtime,
17360
17936
  snapshot.checkout,
17361
- routeStatus(record3, routes),
17937
+ routeStatus(record4, routes),
17362
17938
  activity.status,
17363
17939
  integration,
17364
17940
  snapshot.worktree,
@@ -17369,7 +17945,7 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17369
17945
  `provider=${ownershipEvidence.devpodStatus}`,
17370
17946
  `runtime=${runtime}`,
17371
17947
  `checkout=${snapshot.checkout}`,
17372
- `route=${routeStatus(record3, routes)}`,
17948
+ `route=${routeStatus(record4, routes)}`,
17373
17949
  `activity=${activity.status}`,
17374
17950
  `integration=${integration.status}`,
17375
17951
  ...integration.reason ? [integration.reason] : [],
@@ -17377,16 +17953,16 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17377
17953
  ];
17378
17954
  return {
17379
17955
  schemaVersion: 2,
17380
- workspace: record3.workspace,
17956
+ workspace: record4.workspace,
17381
17957
  branch,
17382
17958
  repo: repoPath,
17383
- worktreePath: record3.worktreePath,
17384
- devpodId: record3.devpodId,
17959
+ worktreePath: record4.worktreePath,
17960
+ devpodId: record4.devpodId,
17385
17961
  ownership: ownershipEvidence.ownerStatus,
17386
17962
  provider: ownershipEvidence.devpodStatus,
17387
17963
  runtime,
17388
17964
  checkout: snapshot.checkout,
17389
- route: routeStatus(record3, routes),
17965
+ route: routeStatus(record4, routes),
17390
17966
  activity: activity.status,
17391
17967
  cutoff,
17392
17968
  latestTimestamp: activity.latestTimestamp,
@@ -17453,30 +18029,30 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17453
18029
  const measureContainersFn = dependencies.measureContainers ?? measureContainerConsumption;
17454
18030
  let containers2 = /* @__PURE__ */ new Map();
17455
18031
  if (measureSize) {
17456
- const worktreePaths = records.map((record3) => record3.worktreePath);
18032
+ const worktreePaths = records.map((record4) => record4.worktreePath);
17457
18033
  try {
17458
18034
  containers2 = measureContainersFn(worktreePaths);
17459
18035
  } catch (error) {
17460
18036
  const reason = `container measurement failed: ${describeCause(error)}`;
17461
- containers2 = new Map(worktreePaths.map((path45) => [path45, unknownContainers(reason)]));
18037
+ containers2 = new Map(worktreePaths.map((path49) => [path49, unknownContainers(reason)]));
17462
18038
  }
17463
18039
  }
17464
- const rows = records.map((record3) => {
18040
+ const rows = records.map((record4) => {
17465
18041
  const worktree = worktrees.find(
17466
- (candidate) => sameWorkspacePath(candidate.path, record3.worktreePath)
18042
+ (candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
17467
18043
  );
17468
18044
  const snapshot = (dependencies.readGitSnapshot ?? ((candidate) => readGitSnapshot(candidate, commandRunner)))(
17469
18045
  worktree ?? {
17470
- path: record3.worktreePath,
17471
- branch: record3.branch ?? void 0,
18046
+ path: record4.worktreePath,
18047
+ branch: record4.branch ?? void 0,
17472
18048
  locked: false,
17473
18049
  prunable: true
17474
18050
  }
17475
18051
  );
17476
- const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record3.worktreePath);
18052
+ const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record4.worktreePath);
17477
18053
  const row = buildRow(
17478
18054
  repoPath,
17479
- record3,
18055
+ record4,
17480
18056
  worktrees,
17481
18057
  devpods,
17482
18058
  routes,
@@ -17489,11 +18065,11 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17489
18065
  return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
17490
18066
  }),
17491
18067
  dependencies.inspectIntegration,
17492
- dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record3.worktreePath))
18068
+ dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record4.worktreePath))
17493
18069
  );
17494
18070
  return measureSize ? {
17495
18071
  ...row,
17496
- consumption: collectConsumption(record3.worktreePath, measureWorktreeFn, containers2)
18072
+ consumption: collectConsumption(record4.worktreePath, measureWorktreeFn, containers2)
17497
18073
  } : row;
17498
18074
  }).sort((left, right) => left.workspace.localeCompare(right.workspace));
17499
18075
  return {
@@ -17507,12 +18083,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17507
18083
  workspaces: rows
17508
18084
  };
17509
18085
  }
17510
- var import_node_child_process29, import_node_fs40, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
18086
+ var import_node_child_process29, import_node_fs41, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
17511
18087
  var init_workspace_cleanup = __esm({
17512
18088
  "src/core/workspace-cleanup.ts"() {
17513
18089
  "use strict";
17514
18090
  import_node_child_process29 = require("child_process");
17515
- import_node_fs40 = __toESM(require("fs"));
18091
+ import_node_fs41 = __toESM(require("fs"));
17516
18092
  init_devpod_workspaces();
17517
18093
  init_host_routes();
17518
18094
  init_repo_config();
@@ -17559,7 +18135,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
17559
18135
  );
17560
18136
  }
17561
18137
  function defaultWorktreePath(mainRepo, ws) {
17562
- return import_node_path45.default.join(mainRepo, "trees", ws);
18138
+ return import_node_path49.default.join(mainRepo, "trees", ws);
17563
18139
  }
17564
18140
  function assertDefaultWorktreeRootIgnored(mainRepo) {
17565
18141
  const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -17567,12 +18143,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
17567
18143
  });
17568
18144
  if (ignored.status !== 0) {
17569
18145
  throw new Error(
17570
- `Default worktree root '${import_node_path45.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path45.default.join(mainRepo, ".gitignore")}' or use --path.`
18146
+ `Default worktree root '${import_node_path49.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path49.default.join(mainRepo, ".gitignore")}' or use --path.`
17571
18147
  );
17572
18148
  }
17573
18149
  }
17574
18150
  function legacyDefaultWorktreePath(mainRepo, ws) {
17575
- return import_node_path45.default.join(import_node_path45.default.dirname(mainRepo), `${import_node_path45.default.basename(mainRepo)}-${ws}`);
18151
+ return import_node_path49.default.join(import_node_path49.default.dirname(mainRepo), `${import_node_path49.default.basename(mainRepo)}-${ws}`);
17576
18152
  }
17577
18153
  function teardownFallbackPath(mainRepo, workspace) {
17578
18154
  const candidates = [
@@ -17625,7 +18201,7 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17625
18201
  }
17626
18202
  const liveRecord = oneRecordMatch(
17627
18203
  target,
17628
- records.filter((record4) => sameWorkspacePath(record4.worktreePath, liveBranchWorktree.path))
18204
+ records.filter((record5) => sameWorkspacePath(record5.worktreePath, liveBranchWorktree.path))
17629
18205
  );
17630
18206
  return {
17631
18207
  workspace: liveRecord?.workspace ?? liveWorkspace,
@@ -17634,13 +18210,13 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17634
18210
  record: liveRecord
17635
18211
  };
17636
18212
  }
17637
- const record3 = records.find((candidate) => candidate.workspace === requestedWorkspace);
17638
- if (record3) {
18213
+ const record4 = records.find((candidate) => candidate.workspace === requestedWorkspace);
18214
+ if (record4) {
17639
18215
  return {
17640
- workspace: record3.workspace,
17641
- worktreePath: record3.worktreePath,
17642
- worktree: worktreeForRecord(worktrees, record3),
17643
- record: record3
18216
+ workspace: record4.workspace,
18217
+ worktreePath: record4.worktreePath,
18218
+ worktree: worktreeForRecord(worktrees, record4),
18219
+ record: record4
17644
18220
  };
17645
18221
  }
17646
18222
  const linked = worktrees.filter((worktree2) => worktree2.workspace !== void 0);
@@ -17661,8 +18237,8 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17661
18237
  record: void 0
17662
18238
  };
17663
18239
  }
17664
- function worktreeForRecord(worktrees, record3) {
17665
- return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record3.worktreePath));
18240
+ function worktreeForRecord(worktrees, record4) {
18241
+ return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record4.worktreePath));
17666
18242
  }
17667
18243
  function assertDevpodTargetSafe(target, worktrees, devpods) {
17668
18244
  if (target.record) {
@@ -17684,7 +18260,7 @@ function assertFullDownPreflight(mainRepo, target) {
17684
18260
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
17685
18261
  throw new Error("Refusing to remove the primary Git checkout.");
17686
18262
  }
17687
- if (!target.worktree || target.worktree.prunable || !import_node_fs41.default.existsSync(target.worktreePath)) return;
18263
+ if (!target.worktree || target.worktree.prunable || !import_node_fs42.default.existsSync(target.worktreePath)) return;
17688
18264
  if (target.worktree.locked) {
17689
18265
  throw new Error(
17690
18266
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
@@ -17724,9 +18300,9 @@ async function workspaceUp(branch, opts = {}) {
17724
18300
  (candidate) => defaultWorktreePath(mainRepo, candidate)
17725
18301
  );
17726
18302
  const generatedPath = candidatePaths.find(
17727
- (candidate) => !import_node_fs41.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
18303
+ (candidate) => !import_node_fs42.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
17728
18304
  );
17729
- const selectedPath = opts.path ? import_node_path45.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
18305
+ const selectedPath = opts.path ? import_node_path49.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
17730
18306
  if (!selectedPath) {
17731
18307
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
17732
18308
  }
@@ -17735,7 +18311,7 @@ async function workspaceUp(branch, opts = {}) {
17735
18311
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
17736
18312
  );
17737
18313
  }
17738
- if (import_node_fs41.default.existsSync(selectedPath)) {
18314
+ if (import_node_fs42.default.existsSync(selectedPath)) {
17739
18315
  const registered = worktrees.find(
17740
18316
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
17741
18317
  );
@@ -17799,26 +18375,26 @@ function workspaceLs(repoPath) {
17799
18375
  const paths = Array.from(
17800
18376
  /* @__PURE__ */ new Set([
17801
18377
  ...worktrees.map((worktree) => worktree.path),
17802
- ...records.map((record3) => record3.worktreePath)
18378
+ ...records.map((record4) => record4.worktreePath)
17803
18379
  ])
17804
18380
  );
17805
18381
  const routesByWorktreePath = listRoutesForWorktreePaths(paths);
17806
18382
  const worktreesByPath = new Map(worktrees.map((worktree) => [worktree.path, worktree]));
17807
- const recordsByPath = new Map(records.map((record3) => [record3.worktreePath, record3]));
18383
+ const recordsByPath = new Map(records.map((record4) => [record4.worktreePath, record4]));
17808
18384
  return paths.map((worktreePath) => {
17809
18385
  const worktree = worktreesByPath.get(worktreePath);
17810
- const record3 = recordsByPath.get(worktreePath);
18386
+ const record4 = recordsByPath.get(worktreePath);
17811
18387
  const devpods = devpodsForPath(worktreePath);
17812
- const status = record3 ? inspectWorkspaceOwnership(record3, worktrees, devpods) : void 0;
18388
+ const status = record4 ? inspectWorkspaceOwnership(record4, worktrees, devpods) : void 0;
17813
18389
  const matchingDevpods = devpods?.filter(
17814
18390
  (devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
17815
18391
  );
17816
18392
  const wsRoutes = routesByWorktreePath.get(worktreePath) ?? [];
17817
18393
  return {
17818
- workspace: record3?.workspace ?? worktree?.workspace,
17819
- branch: worktree?.branch ?? record3?.branch ?? void 0,
18394
+ workspace: record4?.workspace ?? worktree?.workspace,
18395
+ branch: worktree?.branch ?? record4?.branch ?? void 0,
17820
18396
  worktreePath,
17821
- legacy: !record3 && worktree?.workspace !== void 0,
18397
+ legacy: !record4 && worktree?.workspace !== void 0,
17822
18398
  ownerStatus: status?.ownerStatus,
17823
18399
  devpodStatus: status?.devpodStatus ?? (devpods === void 0 ? "unknown" : matchingDevpods?.length === 0 ? "absent" : matchingDevpods?.length === 1 ? "owned" : "conflict"),
17824
18400
  routeCount: wsRoutes.length,
@@ -17875,7 +18451,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
17875
18451
  }
17876
18452
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
17877
18453
  if (removeWorktree) {
17878
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs41.default.existsSync(resolved.worktreePath)) {
18454
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath)) {
17879
18455
  const rm = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
17880
18456
  encoding: "utf-8"
17881
18457
  });
@@ -17894,7 +18470,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
17894
18470
  }
17895
18471
  return result;
17896
18472
  };
17897
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs41.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
18473
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
17898
18474
  }
17899
18475
  async function workspaceStop(target, opts = {}) {
17900
18476
  return runWorkspaceLifecycle("stop", target, opts);
@@ -17902,13 +18478,13 @@ async function workspaceStop(target, opts = {}) {
17902
18478
  async function workspaceDown(target, opts = {}) {
17903
18479
  return runWorkspaceLifecycle("down", target, opts);
17904
18480
  }
17905
- var import_node_child_process30, import_node_fs41, import_node_path45, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
18481
+ var import_node_child_process30, import_node_fs42, import_node_path49, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
17906
18482
  var init_workspace_lifecycle = __esm({
17907
18483
  "src/core/workspace-lifecycle.ts"() {
17908
18484
  "use strict";
17909
18485
  import_node_child_process30 = require("child_process");
17910
- import_node_fs41 = __toESM(require("fs"));
17911
- import_node_path45 = __toESM(require("path"));
18486
+ import_node_fs42 = __toESM(require("fs"));
18487
+ import_node_path49 = __toESM(require("path"));
17912
18488
  init_devpod_mutation();
17913
18489
  init_devpod_workspaces();
17914
18490
  init_managed_runtime_state();
@@ -18067,7 +18643,7 @@ var init_version = __esm({
18067
18643
 
18068
18644
  // src/cli.ts
18069
18645
  var import_commander = require("commander");
18070
- var CLI_VERSION = true ? "0.0.60" : "0.0.0-dev";
18646
+ var CLI_VERSION = true ? "0.0.62" : "0.0.0-dev";
18071
18647
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
18072
18648
  function withErrorHandling(action2) {
18073
18649
  return async (...args) => {