@devrouter/cli 0.0.60 → 0.0.61

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.61" : "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,86 @@ 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 inspectManagedStopDaemon(endpoint) {
4562
+ const value = parseManagedStopJson(
4563
+ runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
4564
+ );
4565
+ if (typeof value !== "string" || !value.length || value.length > 256 || /\s/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
4566
+ throw new Error("Docker daemon identity is unavailable.");
4567
+ }
4568
+ return value;
4569
+ }
4570
+ function inspectManagedStopRunnerId(endpoint, containerId) {
4571
+ assertFullContainerId(containerId);
4572
+ const value = parseManagedStopJson(
4573
+ runManagedStopDocker(
4574
+ ["inspect", "--format", '{{json (index .Config.Labels "dev.containers.id")}}', containerId],
4575
+ endpoint
4576
+ )
4577
+ );
4578
+ if (typeof value !== "string" || value.length > 4096)
4579
+ throw new Error("Provider container binding is unavailable.");
4580
+ return value;
4581
+ }
4582
+ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
4583
+ const ids = parseDockerLines(
4584
+ runManagedStopDocker(
4585
+ [
4586
+ "ps",
4587
+ "-a",
4588
+ "--no-trunc",
4589
+ "--filter",
4590
+ `label=com.docker.compose.project.working_dir=${composeDirectory}`,
4591
+ "--format",
4592
+ "{{.ID}}"
4593
+ ],
4594
+ endpoint
4595
+ )
4596
+ );
4597
+ if (ids.length > 256) throw new Error("Managed stop population exceeds its bound.");
4598
+ ids.forEach(assertFullContainerId);
4599
+ assertUniqueContainerIds(ids);
4600
+ return ids;
4601
+ }
4602
+ function stopPinnedManagedContainer(endpoint, containerId) {
4603
+ assertFullContainerId(containerId);
4604
+ runManagedStopDocker(["stop", containerId], endpoint, 3e4);
4605
+ }
4517
4606
  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
4607
  var init_devpod_environment = __esm({
4519
4608
  "src/core/devpod-environment.ts"() {
@@ -5084,12 +5173,147 @@ var init_http_route_probe = __esm({
5084
5173
  }
5085
5174
  });
5086
5175
 
5176
+ // src/core/managed-stop-baseline.ts
5177
+ function record2(value, keys2) {
5178
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== keys2.length || Object.keys(value).some((key) => !keys2.includes(key))) {
5179
+ throw new Error("Invalid managed stop baseline fields.");
5180
+ }
5181
+ return value;
5182
+ }
5183
+ function text(value, empty = false) {
5184
+ if (typeof value !== "string" || value.length > 4096 || !empty && !value.length || value.trim() !== value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
5185
+ throw new Error("Invalid managed stop baseline identity.");
5186
+ }
5187
+ return value;
5188
+ }
5189
+ function absolute(value) {
5190
+ const result = text(value);
5191
+ if (!import_node_path16.default.isAbsolute(result) || import_node_path16.default.normalize(result) !== result) {
5192
+ throw new Error("Invalid managed stop baseline path.");
5193
+ }
5194
+ return result;
5195
+ }
5196
+ function list(value, parse2 = text) {
5197
+ if (!Array.isArray(value) || !value.length || value.length > 256) {
5198
+ throw new Error("Invalid managed stop baseline population.");
5199
+ }
5200
+ const result = value.map((item) => parse2(item));
5201
+ if (new Set(result).size !== result.length) throw new Error("Duplicate stop baseline identity.");
5202
+ return result;
5203
+ }
5204
+ function id(value) {
5205
+ const result = text(value);
5206
+ if (!/^[a-f0-9]{64}$/.test(result)) throw new Error("Invalid stop baseline container ID.");
5207
+ return result;
5208
+ }
5209
+ function validateManagedStopBaseline(value, state) {
5210
+ const v = record2(value, [
5211
+ "version",
5212
+ "provider",
5213
+ "context",
5214
+ "providerId",
5215
+ "uid",
5216
+ "sourcePath",
5217
+ "sourceContainer",
5218
+ "endpoint",
5219
+ "daemonId",
5220
+ "sourceConfigSha256",
5221
+ "effectiveConfigSha256",
5222
+ "project",
5223
+ "primaryService",
5224
+ "requiredServices",
5225
+ "allowedServices",
5226
+ "composeDirectory",
5227
+ "composeFiles",
5228
+ "featureDirectory",
5229
+ "containers"
5230
+ ]);
5231
+ if (v.version !== 1 || v.provider !== "devsy" || Buffer.byteLength(JSON.stringify(value)) > 1024 * 1024) {
5232
+ throw new Error("Unsupported or oversized managed stop baseline.");
5233
+ }
5234
+ const endpoint = text(v.endpoint);
5235
+ if (!endpoint.startsWith("unix://"))
5236
+ throw new Error("Stop baseline requires a pinned local Docker endpoint.");
5237
+ absolute(endpoint.slice(7));
5238
+ const result = {
5239
+ version: 1,
5240
+ provider: "devsy",
5241
+ context: text(v.context),
5242
+ providerId: text(v.providerId),
5243
+ uid: text(v.uid, true),
5244
+ sourcePath: absolute(v.sourcePath),
5245
+ sourceContainer: text(v.sourceContainer, true),
5246
+ endpoint,
5247
+ daemonId: text(v.daemonId),
5248
+ sourceConfigSha256: id(v.sourceConfigSha256),
5249
+ effectiveConfigSha256: id(v.effectiveConfigSha256),
5250
+ project: text(v.project),
5251
+ primaryService: text(v.primaryService),
5252
+ requiredServices: list(v.requiredServices),
5253
+ allowedServices: list(v.allowedServices),
5254
+ composeDirectory: absolute(v.composeDirectory),
5255
+ composeFiles: list(v.composeFiles, absolute),
5256
+ featureDirectory: absolute(v.featureDirectory),
5257
+ containers: []
5258
+ };
5259
+ 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))) {
5260
+ throw new Error("Managed stop baseline does not bind the retained generation.");
5261
+ }
5262
+ if (!Array.isArray(v.containers) || !v.containers.length || v.containers.length > 256) {
5263
+ throw new Error("Invalid managed stop baseline population.");
5264
+ }
5265
+ result.containers = v.containers.map((entry) => {
5266
+ const c = record2(entry, ["id", "service", "configFiles", "mounts"]);
5267
+ const configFiles = list(c.configFiles, absolute);
5268
+ if (configFiles.length < result.composeFiles.length || result.composeFiles.some((file, index) => configFiles[index] !== file) || configFiles.slice(result.composeFiles.length).some(
5269
+ (file) => import_node_path16.default.dirname(file) !== result.featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
5270
+ import_node_path16.default.basename(file)
5271
+ )
5272
+ )) {
5273
+ throw new Error("Invalid stop baseline Compose provenance.");
5274
+ }
5275
+ if (!Array.isArray(c.mounts) || c.mounts.length > 256)
5276
+ throw new Error("Invalid baseline mounts.");
5277
+ return {
5278
+ id: id(c.id),
5279
+ service: text(c.service),
5280
+ configFiles,
5281
+ mounts: c.mounts.map((item) => {
5282
+ const mount = record2(item, ["Type", "Source", "Destination"]);
5283
+ return {
5284
+ Type: text(mount.Type),
5285
+ Source: mount.Type === "tmpfs" && mount.Source === "" ? "" : absolute(mount.Source),
5286
+ Destination: absolute(mount.Destination)
5287
+ };
5288
+ })
5289
+ };
5290
+ });
5291
+ const services = result.containers.map((container) => container.service);
5292
+ 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))) {
5293
+ throw new Error("Incomplete or duplicate stop baseline population.");
5294
+ }
5295
+ const primary = result.containers.find(
5296
+ (container) => container.service === result.primaryService
5297
+ );
5298
+ if (primary?.mounts.filter((mount) => mount.Type === "bind" && mount.Source === state.repoPath).length !== 1 || result.sourceContainer && result.sourceContainer !== primary.id) {
5299
+ throw new Error("Stop baseline has no exact primary source binding.");
5300
+ }
5301
+ return result;
5302
+ }
5303
+ var import_node_path16;
5304
+ var init_managed_stop_baseline = __esm({
5305
+ "src/core/managed-stop-baseline.ts"() {
5306
+ "use strict";
5307
+ import_node_path16 = __toESM(require("path"));
5308
+ }
5309
+ });
5310
+
5087
5311
  // src/core/managed-runtime-state.ts
5088
5312
  function stateKey(repoPath, workspace) {
5089
5313
  return (0, import_node_crypto10.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
5090
5314
  }
5091
5315
  function managedRuntimeStatePath(repoPath, workspace) {
5092
- return import_node_path16.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
5316
+ return import_node_path17.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
5093
5317
  }
5094
5318
  function isStringArray(value) {
5095
5319
  return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
@@ -5131,7 +5355,7 @@ function validateState(value, repoPath, workspace) {
5131
5355
  if (typeof candidate.updatedAt !== "string" || !Number.isFinite(Date.parse(candidate.updatedAt))) {
5132
5356
  throw new Error("Managed runtime state has an invalid timestamp.");
5133
5357
  }
5134
- return {
5358
+ const result = {
5135
5359
  version: 1,
5136
5360
  repoPath,
5137
5361
  ...workspace !== void 0 ? { workspace } : {},
@@ -5149,6 +5373,10 @@ function validateState(value, repoPath, workspace) {
5149
5373
  ...candidate.transitionPhase ? { transitionPhase: candidate.transitionPhase } : {},
5150
5374
  updatedAt: candidate.updatedAt
5151
5375
  };
5376
+ if (Object.hasOwn(candidate, "stopBaseline")) {
5377
+ result.stopBaseline = validateManagedStopBaseline(candidate.stopBaseline, result);
5378
+ }
5379
+ return result;
5152
5380
  }
5153
5381
  function readManagedRuntimeState(repoPath, workspace, read = (file) => import_node_fs13.default.readFileSync(file, "utf-8")) {
5154
5382
  const statePath = managedRuntimeStatePath(repoPath, workspace);
@@ -5161,14 +5389,15 @@ function readManagedRuntimeState(repoPath, workspace, read = (file) => import_no
5161
5389
  }
5162
5390
  return validateState(parsed, repoPath, workspace);
5163
5391
  }
5164
- var import_node_crypto10, import_node_fs13, import_node_path16;
5392
+ var import_node_crypto10, import_node_fs13, import_node_path17;
5165
5393
  var init_managed_runtime_state = __esm({
5166
5394
  "src/core/managed-runtime-state.ts"() {
5167
5395
  "use strict";
5168
5396
  import_node_crypto10 = require("crypto");
5169
5397
  import_node_fs13 = __toESM(require("fs"));
5170
- import_node_path16 = __toESM(require("path"));
5398
+ import_node_path17 = __toESM(require("path"));
5171
5399
  init_atomic_file();
5400
+ init_managed_stop_baseline();
5172
5401
  init_router();
5173
5402
  }
5174
5403
  });
@@ -5197,7 +5426,7 @@ async function containers(project, signal) {
5197
5426
  signal
5198
5427
  );
5199
5428
  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)))
5429
+ if (ids.length > 128 || new Set(ids).size !== ids.length || ids.some((id3) => !/^[a-f0-9]{64}$/.test(id3)))
5201
5430
  throw new Error("Observation container population is invalid.");
5202
5431
  if (!ids.length) return [];
5203
5432
  const output2 = await runControllerProbe(
@@ -5214,12 +5443,12 @@ async function containers(project, signal) {
5214
5443
  }
5215
5444
  return values.sort((a, b) => a.id.localeCompare(b.id));
5216
5445
  }
5217
- var import_node_crypto11, import_node_path17, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
5446
+ var import_node_crypto11, import_node_path18, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
5218
5447
  var init_controller_observation = __esm({
5219
5448
  "src/core/controller-observation.ts"() {
5220
5449
  "use strict";
5221
5450
  import_node_crypto11 = require("crypto");
5222
- import_node_path17 = __toESM(require("path"));
5451
+ import_node_path18 = __toESM(require("path"));
5223
5452
  init_controller_binding();
5224
5453
  init_controller_monitor();
5225
5454
  init_controller_probe();
@@ -5262,14 +5491,14 @@ var init_controller_observation = __esm({
5262
5491
  )).trim().split("\n");
5263
5492
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
5264
5493
  const [gitDir, commonDir] = metadata;
5265
- const sourcePath = import_node_path17.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
5494
+ const sourcePath = import_node_path18.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
5266
5495
  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"),
5496
+ import_node_path18.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
5497
+ import_node_path18.default.join(environment.repoPath, ".devrouter.yml"),
5498
+ import_node_path18.default.join(gitDir, "devrouter-workspace"),
5270
5499
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
5271
5500
  sourcePath,
5272
- import_node_path17.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
5501
+ import_node_path18.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
5273
5502
  ];
5274
5503
  const persisted = captureControllerEvidence(files);
5275
5504
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -5285,16 +5514,16 @@ var init_controller_observation = __esm({
5285
5514
  const source = parseDevcontainerConfig(persisted.contents[4], sourcePath);
5286
5515
  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
5516
  throw new ControllerObservationBindingChanged("Observation retained configuration changed.");
5288
- const identity = {
5517
+ const identity2 = {
5289
5518
  repoPath: environment.repoPath,
5290
5519
  workspace: environment.workspace,
5291
5520
  provider: environment.provider
5292
5521
  };
5293
- const journal = readReliabilityOperation(identity);
5522
+ const journal = readReliabilityOperation(identity2);
5294
5523
  if (!journal) throw new Error("Observation journal is unavailable.");
5295
5524
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
5296
5525
  const before = await containers(state.composeProject, signal);
5297
- const composeDirectory = import_node_path17.default.join(environment.repoPath, ".devcontainer");
5526
+ const composeDirectory = import_node_path18.default.join(environment.repoPath, ".devcontainer");
5298
5527
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
5299
5528
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
5300
5529
  throw new Error("Observation Compose source is unavailable.");
@@ -5311,7 +5540,7 @@ var init_controller_observation = __esm({
5311
5540
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
5312
5541
  throw new Error("Observation Compose ownership is unavailable.");
5313
5542
  const files2 = recorded.split(",").map((file) => file.trim());
5314
- if (files2.some((file) => !import_node_path17.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
5543
+ if (files2.some((file) => !import_node_path18.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
5315
5544
  repoPath: environment.repoPath,
5316
5545
  service: container.labels["com.docker.compose.service"] ?? "",
5317
5546
  composeProject: state.composeProject,
@@ -5440,7 +5669,7 @@ var init_controller_observation = __esm({
5440
5669
  const routeFingerprint = digest2(routes);
5441
5670
  return {
5442
5671
  environment,
5443
- identity,
5672
+ identity: identity2,
5444
5673
  journal,
5445
5674
  sampledAtMs: Math.floor(performance.now()),
5446
5675
  runtimeFingerprint: digest2({ before, processes }),
@@ -5538,11 +5767,11 @@ var init_controller_sessions = __esm({
5538
5767
  this.previousTime = { monotonic, wall };
5539
5768
  return discontinuity;
5540
5769
  }
5541
- acquire(id2, environment, requirements, monotonic, wall) {
5770
+ acquire(id3, environment, requirements, monotonic, wall) {
5542
5771
  this.tick(monotonic, wall);
5543
5772
  const next = this.read();
5544
5773
  const required = [...requirements].sort();
5545
- const existing = next.sessions.find((s) => s.id === id2);
5774
+ const existing = next.sessions.find((s) => s.id === id3);
5546
5775
  const shared = next.environments.find((env) => env.repoPath === environment.repoPath);
5547
5776
  if (shared && JSON.stringify(shared) !== JSON.stringify(environment))
5548
5777
  throw new Error("Controller environment binding conflicts.");
@@ -5554,7 +5783,7 @@ var init_controller_sessions = __esm({
5554
5783
  if (next.sessions.length >= 128 || !shared && next.environments.length >= 32)
5555
5784
  throw new Error("Controller coordination capacity exhausted.");
5556
5785
  const session = {
5557
- id: id2,
5786
+ id: id3,
5558
5787
  environmentId: environment.id,
5559
5788
  generation: (0, import_node_crypto12.randomUUID)(),
5560
5789
  requirements: required,
@@ -5564,7 +5793,7 @@ var init_controller_sessions = __esm({
5564
5793
  next.sessions.push(session);
5565
5794
  this.event(next, session, "acquired");
5566
5795
  this.commit(next);
5567
- this.leases.set(id2, monotonic + 3e4);
5796
+ this.leases.set(id3, monotonic + 3e4);
5568
5797
  return this.binding(session);
5569
5798
  }
5570
5799
  publish(bindings, projections, monotonic, wall) {
@@ -5610,8 +5839,8 @@ var init_controller_sessions = __esm({
5610
5839
  if (changed) {
5611
5840
  this.commit(next);
5612
5841
  if (bindingChanged) {
5613
- for (const id2 of this.leases.keys())
5614
- if (!next.sessions.some((s) => s.id === id2)) this.leases.delete(id2);
5842
+ for (const id3 of this.leases.keys())
5843
+ if (!next.sessions.some((s) => s.id === id3)) this.leases.delete(id3);
5615
5844
  }
5616
5845
  }
5617
5846
  }
@@ -5677,11 +5906,11 @@ function fields(value, expected) {
5677
5906
  const actual = Object.keys(value);
5678
5907
  if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) fail2();
5679
5908
  }
5680
- function text(value, max = 256) {
5909
+ function text2(value, max = 256) {
5681
5910
  return typeof value === "string" && value.length > 0 && value.length <= max && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
5682
5911
  }
5683
- function id(value) {
5684
- return text(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
5912
+ function id2(value) {
5913
+ return text2(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value);
5685
5914
  }
5686
5915
  function counter(value) {
5687
5916
  return Number.isSafeInteger(value) && Number(value) >= 0;
@@ -5697,7 +5926,7 @@ function validateControllerSnapshot(value) {
5697
5926
  "sessions",
5698
5927
  "events"
5699
5928
  ]);
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)
5929
+ 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
5930
  fail2();
5702
5931
  const environments = /* @__PURE__ */ new Set();
5703
5932
  const checkouts = /* @__PURE__ */ new Set();
@@ -5711,7 +5940,7 @@ function validateControllerSnapshot(value) {
5711
5940
  "profile",
5712
5941
  "fingerprint"
5713
5942
  ]);
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)))
5943
+ 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
5944
  fail2();
5716
5945
  environments.add(env.id);
5717
5946
  checkouts.add(env.repoPath);
@@ -5727,8 +5956,8 @@ function validateControllerSnapshot(value) {
5727
5956
  "renewedAtMs",
5728
5957
  ...Object.hasOwn(session, "observation") ? ["observation"] : []
5729
5958
  ]);
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)
5959
+ 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(
5960
+ (r) => !text2(r, 132) || !/^(runtime|app:[a-z0-9][a-z0-9-]*)$/.test(r)
5732
5961
  ))
5733
5962
  fail2();
5734
5963
  if (session.observation !== void 0) {
@@ -5750,7 +5979,7 @@ function validateControllerSnapshot(value) {
5750
5979
  "WAITING_CAPACITY",
5751
5980
  "PARKED_CAPACITY",
5752
5981
  "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))
5982
+ ].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
5983
  fail2();
5755
5984
  }
5756
5985
  sessions.add(session.id);
@@ -5760,7 +5989,7 @@ function validateControllerSnapshot(value) {
5760
5989
  let previous = 0;
5761
5990
  for (const event of value.events) {
5762
5991
  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(
5992
+ 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
5993
  String(event.kind)
5765
5994
  ))
5766
5995
  fail2();
@@ -5769,20 +5998,20 @@ function validateControllerSnapshot(value) {
5769
5998
  if (Buffer.byteLength(JSON.stringify(value.events)) > 262144 || Buffer.byteLength(JSON.stringify(value)) + 1 > CONTROLLER_SNAPSHOT_BYTES)
5770
5999
  fail2();
5771
6000
  }
5772
- var import_node_crypto13, import_node_fs14, import_node_path18, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
6001
+ var import_node_crypto13, import_node_fs14, import_node_path19, CONTROLLER_SNAPSHOT_BYTES, ControllerStore;
5773
6002
  var init_controller_store = __esm({
5774
6003
  "src/core/controller-store.ts"() {
5775
6004
  "use strict";
5776
6005
  import_node_crypto13 = require("crypto");
5777
6006
  import_node_fs14 = __toESM(require("fs"));
5778
- import_node_path18 = __toESM(require("path"));
6007
+ import_node_path19 = __toESM(require("path"));
5779
6008
  init_atomic_file();
5780
6009
  CONTROLLER_SNAPSHOT_BYTES = 1048576;
5781
6010
  ControllerStore = class {
5782
6011
  constructor(directory, write = writeFileAtomically) {
5783
6012
  this.directory = directory;
5784
6013
  this.write = write;
5785
- this.file = import_node_path18.default.join(directory, "snapshot.json");
6014
+ this.file = import_node_path19.default.join(directory, "snapshot.json");
5786
6015
  }
5787
6016
  file;
5788
6017
  read() {
@@ -5862,10 +6091,10 @@ var init_controller_store = __esm({
5862
6091
 
5863
6092
  // src/core/controller-server.ts
5864
6093
  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);
6094
+ const absolute2 = import_node_path20.default.resolve(directory);
6095
+ let current = import_node_path20.default.parse(absolute2).root;
6096
+ for (const part of absolute2.slice(current.length).split(import_node_path20.default.sep)) {
6097
+ current = import_node_path20.default.join(current, part);
5869
6098
  try {
5870
6099
  const stat2 = import_node_fs15.default.lstatSync(current);
5871
6100
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
@@ -5875,7 +6104,7 @@ function privateDirectory(directory) {
5875
6104
  import_node_fs15.default.mkdirSync(current, { mode: 448 });
5876
6105
  }
5877
6106
  }
5878
- const stat = import_node_fs15.default.lstatSync(absolute);
6107
+ const stat = import_node_fs15.default.lstatSync(absolute2);
5879
6108
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
5880
6109
  throw new Error("Controller directory is not private.");
5881
6110
  }
@@ -5891,10 +6120,10 @@ function validateOwnedFile(file, socket) {
5891
6120
  }
5892
6121
  }
5893
6122
  async function runController(options) {
5894
- const socketPath = import_node_path19.default.join(options.directory, "control.sock");
6123
+ const socketPath = import_node_path20.default.join(options.directory, "control.sock");
5895
6124
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
5896
6125
  privateDirectory(options.directory);
5897
- const lockPath = import_node_path19.default.join(options.directory, "owner.lock");
6126
+ const lockPath = import_node_path20.default.join(options.directory, "owner.lock");
5898
6127
  try {
5899
6128
  const stat = import_node_fs15.default.lstatSync(lockPath);
5900
6129
  if (!stat.isFile() || stat.uid !== process.getuid?.())
@@ -6197,13 +6426,13 @@ async function runController(options) {
6197
6426
  }
6198
6427
  });
6199
6428
  }
6200
- var import_node_fs15, import_node_net2, import_node_path19, FRAME_BYTES;
6429
+ var import_node_fs15, import_node_net2, import_node_path20, FRAME_BYTES;
6201
6430
  var init_controller_server = __esm({
6202
6431
  "src/core/controller-server.ts"() {
6203
6432
  "use strict";
6204
6433
  import_node_fs15 = __toESM(require("fs"));
6205
6434
  import_node_net2 = __toESM(require("net"));
6206
- import_node_path19 = __toESM(require("path"));
6435
+ import_node_path20 = __toESM(require("path"));
6207
6436
  init_controller_monitor();
6208
6437
  init_controller_protocol();
6209
6438
  init_controller_sessions();
@@ -6219,7 +6448,7 @@ __export(controller_exports, {
6219
6448
  runControllerCommand: () => runControllerCommand
6220
6449
  });
6221
6450
  async function runControllerCommand(method, options, repo) {
6222
- const directory = import_node_path20.default.join(DEVROUTER_HOME, "controller");
6451
+ const directory = import_node_path21.default.join(DEVROUTER_HOME, "controller");
6223
6452
  try {
6224
6453
  if (method === "run") {
6225
6454
  const controller = new AbortController();
@@ -6244,7 +6473,7 @@ async function runControllerCommand(method, options, repo) {
6244
6473
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
6245
6474
  await controllerRequest(
6246
6475
  directory,
6247
- { method, ...fields2, ...repo ? { path: import_node_path20.default.resolve(repo) } : {} },
6476
+ { method, ...fields2, ...repo ? { path: import_node_path21.default.resolve(repo) } : {} },
6248
6477
  (value) => process.stdout.write(`${JSON.stringify(value)}
6249
6478
  `)
6250
6479
  );
@@ -6256,11 +6485,11 @@ async function runControllerCommand(method, options, repo) {
6256
6485
  process.exitCode = 1;
6257
6486
  }
6258
6487
  }
6259
- var import_node_path20;
6488
+ var import_node_path21;
6260
6489
  var init_controller = __esm({
6261
6490
  "src/commands/controller.ts"() {
6262
6491
  "use strict";
6263
- import_node_path20 = __toESM(require("path"));
6492
+ import_node_path21 = __toESM(require("path"));
6264
6493
  init_controller_binding();
6265
6494
  init_controller_client();
6266
6495
  init_controller_observation();
@@ -6308,13 +6537,13 @@ function replaceDevrouterSection(content) {
6308
6537
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
6309
6538
  }
6310
6539
  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 });
6540
+ const absolutePath = (0, import_node_path22.join)(repoPath, relPath);
6541
+ (0, import_node_fs16.mkdirSync)((0, import_node_path22.dirname)(absolutePath), { recursive: true });
6313
6542
  (0, import_node_fs16.writeFileSync)(absolutePath, content, "utf-8");
6314
6543
  return absolutePath;
6315
6544
  }
6316
6545
  function ensureAgentsMdSection(repoPath) {
6317
- const filePath = (0, import_node_path21.join)(repoPath, AGENTS_MD);
6546
+ const filePath = (0, import_node_path22.join)(repoPath, AGENTS_MD);
6318
6547
  if ((0, import_node_fs16.existsSync)(filePath)) {
6319
6548
  const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
6320
6549
  if (content.includes(DEVROUTER_SENTINEL)) {
@@ -6339,12 +6568,12 @@ function ensureSkillFile(repoPath) {
6339
6568
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
6340
6569
  return { path: filePath, written: true };
6341
6570
  }
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;
6571
+ 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
6572
  var init_agents_md = __esm({
6344
6573
  "src/core/agents-md.ts"() {
6345
6574
  "use strict";
6346
6575
  import_node_fs16 = require("fs");
6347
- import_node_path21 = require("path");
6576
+ import_node_path22 = require("path");
6348
6577
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
6349
6578
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
6350
6579
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -6883,7 +7112,7 @@ function buildOnboardingPrompt(options = {}) {
6883
7112
  "- 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
7113
  "- 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
7114
  "- 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.",
7115
+ "- 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
7116
  "- 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
7117
  "- `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
7118
  '- `--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 +7847,7 @@ function readPromptDirectory(promptDirPath) {
7618
7847
  continue;
7619
7848
  }
7620
7849
  const version = normalizeVersion(basename);
7621
- const promptPath = import_node_path22.default.join(promptDirPath, file.name);
7850
+ const promptPath = import_node_path23.default.join(promptDirPath, file.name);
7622
7851
  const prompt = import_node_fs17.default.readFileSync(promptPath, "utf-8").trim();
7623
7852
  releases.push({ version, prompt, promptPath });
7624
7853
  }
@@ -7640,17 +7869,17 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
7640
7869
  }
7641
7870
  function resolvePromptDirectory(explicitPath) {
7642
7871
  if (explicitPath) {
7643
- return import_node_path22.default.resolve(explicitPath);
7872
+ return import_node_path23.default.resolve(explicitPath);
7644
7873
  }
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;
7874
+ const entryFile = process.argv[1] ? import_node_path23.default.resolve(process.argv[1]) : __filename;
7875
+ const entryDir = import_node_path23.default.dirname(entryFile);
7876
+ const resolvedEntryDir = import_node_fs17.default.existsSync(entryFile) ? import_node_path23.default.dirname(import_node_fs17.default.realpathSync(entryFile)) : entryDir;
7648
7877
  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)
7878
+ import_node_path23.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
7879
+ import_node_path23.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
7880
+ import_node_path23.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
7881
+ import_node_path23.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
7882
+ import_node_path23.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
7654
7883
  ];
7655
7884
  const existing = candidates.find((candidate) => import_node_fs17.default.existsSync(candidate));
7656
7885
  return existing ?? candidates[0];
@@ -7668,12 +7897,12 @@ function loadUpgradeCatalog(options = {}) {
7668
7897
  releases
7669
7898
  };
7670
7899
  }
7671
- var import_node_fs17, import_node_path22, SEMVER_RE, UPGRADE_PROMPTS_DIR;
7900
+ var import_node_fs17, import_node_path23, SEMVER_RE, UPGRADE_PROMPTS_DIR;
7672
7901
  var init_upgrade = __esm({
7673
7902
  "src/core/upgrade.ts"() {
7674
7903
  "use strict";
7675
7904
  import_node_fs17 = __toESM(require("fs"));
7676
- import_node_path22 = __toESM(require("path"));
7905
+ import_node_path23 = __toESM(require("path"));
7677
7906
  init_repo_config();
7678
7907
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
7679
7908
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -7786,7 +8015,7 @@ function nativeAssetName(platform, arch) {
7786
8015
  return void 0;
7787
8016
  }
7788
8017
  function managedBinaryPath(cacheRoot, asset) {
7789
- return import_node_path23.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
8018
+ return import_node_path24.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
7790
8019
  }
7791
8020
  function hashOpenFile(file) {
7792
8021
  const digest3 = (0, import_node_crypto14.createHash)("sha256");
@@ -7828,7 +8057,7 @@ function resolvedOptions(options) {
7828
8057
  platform: options.platform ?? process.platform,
7829
8058
  arch: options.arch ?? process.arch,
7830
8059
  versionOutput: options.versionOutput ?? installedVersionOutput(),
7831
- cacheRoot: options.cacheRoot ?? import_node_path23.default.join(CACHE_DIR, "devsy", "agents"),
8060
+ cacheRoot: options.cacheRoot ?? import_node_path24.default.join(CACHE_DIR, "devsy", "agents"),
7832
8061
  lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
7833
8062
  assets: options.assets ?? DEVSY_AGENT_ASSETS,
7834
8063
  nativeAssetName: options.nativeAssetName
@@ -7953,11 +8182,11 @@ function fsyncDirectory2(directory) {
7953
8182
  }
7954
8183
  }
7955
8184
  async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
7956
- const directory = import_node_path23.default.dirname(binaryPath);
8185
+ const directory = import_node_path24.default.dirname(binaryPath);
7957
8186
  import_node_fs18.default.mkdirSync(directory, { recursive: true, mode: 448 });
7958
- const temporaryPath = import_node_path23.default.join(
8187
+ const temporaryPath = import_node_path24.default.join(
7959
8188
  directory,
7960
- `.${import_node_path23.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
8189
+ `.${import_node_path24.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto14.randomUUID)()}.tmp`
7961
8190
  );
7962
8191
  let handle;
7963
8192
  try {
@@ -8041,7 +8270,7 @@ async function prepareDevsyAgent(options = {}) {
8041
8270
  throw new DevsyAgentReadinessError(before);
8042
8271
  }
8043
8272
  const lock = options.withLock ?? ((operation) => {
8044
- import_node_fs18.default.mkdirSync(import_node_path23.default.dirname(resolved.lockPath), { recursive: true });
8273
+ import_node_fs18.default.mkdirSync(import_node_path24.default.dirname(resolved.lockPath), { recursive: true });
8045
8274
  return withFileLock(
8046
8275
  resolved.lockPath,
8047
8276
  {
@@ -8086,14 +8315,14 @@ async function prepareDevsyAgent(options = {}) {
8086
8315
  };
8087
8316
  });
8088
8317
  }
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;
8318
+ 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
8319
  var init_devsy_agent = __esm({
8091
8320
  "src/core/devsy-agent.ts"() {
8092
8321
  "use strict";
8093
8322
  import_node_child_process10 = require("child_process");
8094
8323
  import_node_crypto14 = require("crypto");
8095
8324
  import_node_fs18 = __toESM(require("fs"));
8096
- import_node_path23 = __toESM(require("path"));
8325
+ import_node_path24 = __toESM(require("path"));
8097
8326
  init_file_lock();
8098
8327
  init_router();
8099
8328
  SUPPORTED_DEVSY_VERSION = "1.16.2";
@@ -8121,7 +8350,7 @@ var init_devsy_agent = __esm({
8121
8350
  this.name = "DevsyAgentReadinessError";
8122
8351
  }
8123
8352
  };
8124
- DEVSY_AGENT_CACHE_LOCK = import_node_path23.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
8353
+ DEVSY_AGENT_CACHE_LOCK = import_node_path24.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
8125
8354
  DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
8126
8355
  GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
8127
8356
  }
@@ -8139,11 +8368,11 @@ function adapterFingerprint(adapter) {
8139
8368
  return (0, import_node_crypto15.createHash)("sha256").update(adapter).digest("hex");
8140
8369
  }
8141
8370
  function resolveManagedPostStartPlan(repoPath) {
8142
- const adapterPath = import_node_path24.default.join(repoPath, MANAGED_ADAPTER_PATH);
8371
+ const adapterPath = import_node_path25.default.join(repoPath, MANAGED_ADAPTER_PATH);
8143
8372
  const adapterBytes = readRegularFileBytes(adapterPath);
8144
8373
  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");
8374
+ const dockerfilePath = import_node_path25.default.join(repoPath, ".devcontainer", "Dockerfile");
8375
+ const devcontainerPath = import_node_path25.default.join(repoPath, ".devcontainer", "devcontainer.json");
8147
8376
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
8148
8377
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
8149
8378
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -8225,14 +8454,14 @@ function runManagedProcessAction(options) {
8225
8454
  }
8226
8455
  return "drifted";
8227
8456
  }
8228
- var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path24, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
8457
+ var import_node_child_process11, import_node_crypto15, import_node_fs19, import_node_path25, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
8229
8458
  var init_managed_post_start = __esm({
8230
8459
  "src/core/managed-post-start.ts"() {
8231
8460
  "use strict";
8232
8461
  import_node_child_process11 = require("child_process");
8233
8462
  import_node_crypto15 = require("crypto");
8234
8463
  import_node_fs19 = __toESM(require("fs"));
8235
- import_node_path24 = __toESM(require("path"));
8464
+ import_node_path25 = __toESM(require("path"));
8236
8465
  init_devcontainer_config();
8237
8466
  MANAGED_MARKER = "devrouter:managed devcontainer";
8238
8467
  MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
@@ -8272,19 +8501,19 @@ function formatPublishedPort(serviceName, value) {
8272
8501
  if (typeof value === "string") {
8273
8502
  return `${serviceName}: ${value}`;
8274
8503
  }
8275
- const record3 = asRecord(value);
8276
- if (!record3) {
8504
+ const record4 = asRecord(value);
8505
+ if (!record4) {
8277
8506
  return void 0;
8278
8507
  }
8279
- const target = record3.target;
8280
- const published = record3.published;
8508
+ const target = record4.target;
8509
+ const published = record4.published;
8281
8510
  if (published !== void 0 && target !== void 0) {
8282
8511
  return `${serviceName}: ${String(published)}:${String(target)}`;
8283
8512
  }
8284
8513
  if (target !== void 0) {
8285
8514
  return `${serviceName}: target ${String(target)}`;
8286
8515
  }
8287
- return `${serviceName}: ${JSON.stringify(record3)}`;
8516
+ return `${serviceName}: ${JSON.stringify(record4)}`;
8288
8517
  }
8289
8518
  function upstreamHost(upstream) {
8290
8519
  const separator = upstream.lastIndexOf(":");
@@ -8360,11 +8589,11 @@ function routedProxyApps(config) {
8360
8589
  );
8361
8590
  }
8362
8591
  function buildDevcontainerChecks(repoPath, config, workspace) {
8363
- const devcontainerDir = import_node_path25.default.join(repoPath, ".devcontainer");
8592
+ const devcontainerDir = import_node_path26.default.join(repoPath, ".devcontainer");
8364
8593
  if (!import_node_fs20.default.existsSync(devcontainerDir)) {
8365
8594
  return [];
8366
8595
  }
8367
- const compose = inspectCompose(import_node_path25.default.join(devcontainerDir, "docker-compose.yml"), workspace);
8596
+ const compose = inspectCompose(import_node_path26.default.join(devcontainerDir, "docker-compose.yml"), workspace);
8368
8597
  const checks = [];
8369
8598
  if (compose.parseError) {
8370
8599
  checks.push({
@@ -8391,7 +8620,7 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
8391
8620
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
8392
8621
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
8393
8622
  });
8394
- const dockerfilePath = import_node_path25.default.join(devcontainerDir, "Dockerfile");
8623
+ const dockerfilePath = import_node_path26.default.join(devcontainerDir, "Dockerfile");
8395
8624
  const dockerfileExists = import_node_fs20.default.existsSync(dockerfilePath);
8396
8625
  const dockerfile = dockerfileExists ? import_node_fs20.default.readFileSync(dockerfilePath, "utf-8") : "";
8397
8626
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
@@ -8448,12 +8677,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
8448
8677
  });
8449
8678
  return checks;
8450
8679
  }
8451
- var import_node_fs20, import_node_path25, import_yaml4;
8680
+ var import_node_fs20, import_node_path26, import_yaml4;
8452
8681
  var init_devcontainer_diagnostics = __esm({
8453
8682
  "src/core/devcontainer-diagnostics.ts"() {
8454
8683
  "use strict";
8455
8684
  import_node_fs20 = __toESM(require("fs"));
8456
- import_node_path25 = __toESM(require("path"));
8685
+ import_node_path26 = __toESM(require("path"));
8457
8686
  import_yaml4 = __toESM(require("yaml"));
8458
8687
  init_managed_post_start();
8459
8688
  }
@@ -9393,9 +9622,16 @@ function listDevsyWorkspaces() {
9393
9622
  if (typeof candidate.id !== "string" || !source || typeof source.localFolder !== "string") {
9394
9623
  throw new Error("devsy workspace list returned a workspace without id/source.localFolder.");
9395
9624
  }
9625
+ if (candidate.uid !== void 0 && typeof candidate.uid !== "string" || source.container !== void 0 && typeof source.container !== "string") {
9626
+ throw new Error("Devsy workspace list returned invalid provider identity.");
9627
+ }
9396
9628
  const workspace = {
9397
9629
  id: candidate.id,
9398
- source: { localFolder: source.localFolder },
9630
+ source: {
9631
+ localFolder: source.localFolder,
9632
+ ...typeof source.container === "string" ? { container: source.container } : {}
9633
+ },
9634
+ ...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
9399
9635
  ...typeof candidate.context === "string" ? { context: candidate.context } : {}
9400
9636
  };
9401
9637
  if ("lastUsed" in candidate) {
@@ -9662,20 +9898,20 @@ function resetWorkspaceRuntimeCaches() {
9662
9898
  cachedRuntime = void 0;
9663
9899
  cachedSnapshots = void 0;
9664
9900
  }
9665
- var import_node_child_process14, import_node_fs23, import_node_path26, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
9901
+ var import_node_child_process14, import_node_fs23, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
9666
9902
  var init_workspace_runtime = __esm({
9667
9903
  "src/core/workspace-runtime.ts"() {
9668
9904
  "use strict";
9669
9905
  import_node_child_process14 = require("child_process");
9670
9906
  import_node_fs23 = __toESM(require("fs"));
9671
- import_node_path26 = __toESM(require("path"));
9907
+ import_node_path27 = __toESM(require("path"));
9672
9908
  init_atomic_file();
9673
9909
  init_devpod_registry();
9674
9910
  init_devsy_workspaces();
9675
9911
  init_router();
9676
9912
  init_workspace();
9677
9913
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
9678
- RUNTIME_CONFIG_FILE = import_node_path26.default.join(DEVROUTER_HOME, "workspace-runtime.json");
9914
+ RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
9679
9915
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
9680
9916
  UnsupportedWorkspaceRuntimeError = class extends Error {
9681
9917
  };
@@ -9753,7 +9989,7 @@ function parseMinimumNodeMajor(value) {
9753
9989
  return Number(match[1]);
9754
9990
  }
9755
9991
  function readPackageJson(repoPath) {
9756
- const packagePath = import_node_path27.default.join(repoPath, "package.json");
9992
+ const packagePath = import_node_path28.default.join(repoPath, "package.json");
9757
9993
  if (!import_node_fs24.default.existsSync(packagePath)) {
9758
9994
  return void 0;
9759
9995
  }
@@ -9942,13 +10178,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
9942
10178
  checks.push(nodeToolchainCheck(repoPath));
9943
10179
  return checks;
9944
10180
  }
9945
- var import_node_child_process15, import_node_fs24, import_node_path27, DEVSY_DISABLE_TELEMETRY;
10181
+ var import_node_child_process15, import_node_fs24, import_node_path28, DEVSY_DISABLE_TELEMETRY;
9946
10182
  var init_tool_diagnostics = __esm({
9947
10183
  "src/core/tool-diagnostics.ts"() {
9948
10184
  "use strict";
9949
10185
  import_node_child_process15 = require("child_process");
9950
10186
  import_node_fs24 = __toESM(require("fs"));
9951
- import_node_path27 = __toESM(require("path"));
10187
+ import_node_path28 = __toESM(require("path"));
9952
10188
  init_devsy_agent();
9953
10189
  init_workspace_runtime();
9954
10190
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -10054,6 +10290,54 @@ var init_devpod_workspaces = __esm({
10054
10290
  }
10055
10291
  });
10056
10292
 
10293
+ // src/core/managed-compose-population.ts
10294
+ function proveManagedComposePopulation(options) {
10295
+ const { plan, repoPath, composeProject, featureDirectory, devsyRoot, containers: containers2 } = options;
10296
+ const services = /* @__PURE__ */ new Set();
10297
+ for (const container of containers2) {
10298
+ const service = container.labels["com.docker.compose.service"] ?? "";
10299
+ if (!plan.nativeRunServices.includes(service) || services.has(service) || container.labels["com.docker.compose.project"] !== composeProject || !sameWorkspacePath(
10300
+ container.labels["com.docker.compose.project.working_dir"] ?? "",
10301
+ plan.composeDirectory
10302
+ )) {
10303
+ throw new Error("Managed stop found unexpected or duplicate project membership.");
10304
+ }
10305
+ services.add(service);
10306
+ const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
10307
+ 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]))) {
10308
+ throw new Error("Managed stop Compose file identity changed.");
10309
+ }
10310
+ for (const file of files.slice(plan.composeFiles.length)) {
10311
+ if (import_node_path29.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
10312
+ import_node_path29.default.basename(file)
10313
+ ) || import_node_fs25.default.realpathSync(file) !== import_node_path29.default.join(import_node_fs25.default.realpathSync(devsyRoot), import_node_path29.default.relative(devsyRoot, file))) {
10314
+ throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
10315
+ }
10316
+ }
10317
+ }
10318
+ if (plan.desiredServices.some((service) => !services.has(service))) {
10319
+ throw new Error("Managed stop cannot prove the complete retained service population.");
10320
+ }
10321
+ const primary = containers2.find(
10322
+ (c) => c.labels["com.docker.compose.service"] === plan.primaryService
10323
+ );
10324
+ if (primary?.mounts.filter(
10325
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
10326
+ ).length !== 1) {
10327
+ throw new Error("Managed stop cannot prove the exact primary workspace mount.");
10328
+ }
10329
+ return primary;
10330
+ }
10331
+ var import_node_fs25, import_node_path29;
10332
+ var init_managed_compose_population = __esm({
10333
+ "src/core/managed-compose-population.ts"() {
10334
+ "use strict";
10335
+ import_node_fs25 = __toESM(require("fs"));
10336
+ import_node_path29 = __toESM(require("path"));
10337
+ init_workspace();
10338
+ }
10339
+ });
10340
+
10057
10341
  // src/core/reliability-context.ts
10058
10342
  function claimLifecycleEffect() {
10059
10343
  claim?.();
@@ -10065,81 +10349,6 @@ var init_reliability_context = __esm({
10065
10349
  }
10066
10350
  });
10067
10351
 
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
10352
  // src/core/workspace-ownership.ts
10144
10353
  function commandError(command, repoPath, stderr) {
10145
10354
  return new Error(
@@ -10155,7 +10364,7 @@ function resolveGitCommonDir(repoPath) {
10155
10364
  if (result.status !== 0 || !output2) {
10156
10365
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
10157
10366
  }
10158
- return comparableWorkspacePath(import_node_path28.default.isAbsolute(output2) ? output2 : import_node_path28.default.resolve(repoPath, output2));
10367
+ return comparableWorkspacePath(import_node_path30.default.isAbsolute(output2) ? output2 : import_node_path30.default.resolve(repoPath, output2));
10159
10368
  }
10160
10369
  function resolveGitTopLevel(repoPath) {
10161
10370
  const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -10206,7 +10415,7 @@ function listGitWorktrees(repoPath) {
10206
10415
  return worktrees;
10207
10416
  }
10208
10417
  function ownershipDirectory(repoPath) {
10209
- return import_node_path28.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
10418
+ return import_node_path30.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
10210
10419
  }
10211
10420
  function validateWorkspace(value, label) {
10212
10421
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -10234,7 +10443,7 @@ function validateRecord(value, expectedWorkspace) {
10234
10443
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
10235
10444
  );
10236
10445
  }
10237
- if (typeof candidate.worktreePath !== "string" || !import_node_path28.default.isAbsolute(candidate.worktreePath)) {
10446
+ if (typeof candidate.worktreePath !== "string" || !import_node_path30.default.isAbsolute(candidate.worktreePath)) {
10238
10447
  throw new Error("invalid workspace ownership worktreePath");
10239
10448
  }
10240
10449
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -10252,7 +10461,7 @@ function validateRecord(value, expectedWorkspace) {
10252
10461
  };
10253
10462
  }
10254
10463
  function recordPath(repoPath, workspace) {
10255
- return import_node_path28.default.join(
10464
+ return import_node_path30.default.join(
10256
10465
  ownershipDirectory(repoPath),
10257
10466
  `${validateWorkspace(workspace, "workspace")}.json`
10258
10467
  );
@@ -10260,7 +10469,7 @@ function recordPath(repoPath, workspace) {
10260
10469
  function readRecordFile(filePath, expectedWorkspace) {
10261
10470
  let parsed;
10262
10471
  try {
10263
- parsed = JSON.parse(import_node_fs25.default.readFileSync(filePath, "utf-8"));
10472
+ parsed = JSON.parse(import_node_fs26.default.readFileSync(filePath, "utf-8"));
10264
10473
  } catch (error) {
10265
10474
  if (error instanceof SyntaxError) {
10266
10475
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -10285,7 +10494,7 @@ function listWorkspaceOwnership(repoPath) {
10285
10494
  function listWorkspaceOwnershipInDirectory(directory) {
10286
10495
  let entries;
10287
10496
  try {
10288
- entries = import_node_fs25.default.readdirSync(directory, { withFileTypes: true });
10497
+ entries = import_node_fs26.default.readdirSync(directory, { withFileTypes: true });
10289
10498
  } catch (error) {
10290
10499
  if (error.code === "ENOENT") return [];
10291
10500
  throw error;
@@ -10293,24 +10502,24 @@ function listWorkspaceOwnershipInDirectory(directory) {
10293
10502
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
10294
10503
  const workspace = entry.name.slice(0, -".json".length);
10295
10504
  validateWorkspace(workspace, "filename");
10296
- return readRecordFile(import_node_path28.default.join(directory, entry.name), workspace);
10505
+ return readRecordFile(import_node_path30.default.join(directory, entry.name), workspace);
10297
10506
  });
10298
10507
  }
10299
10508
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
10300
10509
  const workspace = validateWorkspace(input2.workspace, "workspace");
10301
10510
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
10302
10511
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
10303
- const filePath = import_node_path28.default.join(directory, `${workspace}.json`);
10512
+ const filePath = import_node_path30.default.join(directory, `${workspace}.json`);
10304
10513
  const now = (/* @__PURE__ */ new Date()).toISOString();
10305
10514
  const records = listWorkspaceOwnershipInDirectory(directory);
10306
- const existing = records.find((record4) => record4.workspace === workspace);
10515
+ const existing = records.find((record5) => record5.workspace === workspace);
10307
10516
  if (existing && !sameWorkspacePath(existing.worktreePath, worktreePath)) {
10308
10517
  throw new Error(
10309
10518
  `Workspace '${workspace}' already belongs to '${existing.worktreePath}', refusing '${worktreePath}'.`
10310
10519
  );
10311
10520
  }
10312
10521
  const pathOwner = records.find(
10313
- (record4) => record4.workspace !== workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
10522
+ (record5) => record5.workspace !== workspace && sameWorkspacePath(record5.worktreePath, worktreePath)
10314
10523
  );
10315
10524
  if (pathOwner) {
10316
10525
  throw new Error(
@@ -10322,7 +10531,7 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
10322
10531
  `Workspace '${workspace}' already owns DevPod '${existing.devpodId}', refusing '${devpodId}'.`
10323
10532
  );
10324
10533
  }
10325
- const record3 = {
10534
+ const record4 = {
10326
10535
  version: OWNERSHIP_VERSION,
10327
10536
  workspace,
10328
10537
  worktreePath,
@@ -10331,14 +10540,14 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
10331
10540
  createdAt: existing?.createdAt ?? validateTimestamp(now, "createdAt"),
10332
10541
  updatedAt: validateTimestamp(now, "updatedAt")
10333
10542
  };
10334
- writeFileAtomically(filePath, `${JSON.stringify(record3, null, 2)}
10543
+ writeFileAtomically(filePath, `${JSON.stringify(record4, null, 2)}
10335
10544
  `);
10336
- return record3;
10545
+ return record4;
10337
10546
  }
10338
10547
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
10339
- const filePath = import_node_path28.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
10548
+ const filePath = import_node_path30.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
10340
10549
  try {
10341
- import_node_fs25.default.rmSync(filePath);
10550
+ import_node_fs26.default.rmSync(filePath);
10342
10551
  return true;
10343
10552
  } catch (error) {
10344
10553
  if (error.code === "ENOENT") return false;
@@ -10349,7 +10558,7 @@ function sameOwnershipRecord(left, right) {
10349
10558
  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
10559
  }
10351
10560
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
10352
- const filePath = import_node_path28.default.join(
10561
+ const filePath = import_node_path30.default.join(
10353
10562
  directory,
10354
10563
  `${validateWorkspace(expected.workspace, "workspace")}.json`
10355
10564
  );
@@ -10361,14 +10570,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
10361
10570
  throw error;
10362
10571
  }
10363
10572
  if (!sameOwnershipRecord(current, expected)) return "changed";
10364
- import_node_fs25.default.rmSync(filePath);
10573
+ import_node_fs26.default.rmSync(filePath);
10365
10574
  return "removed";
10366
10575
  }
10367
10576
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
10368
10577
  const directory = ownershipDirectory(repoPath);
10369
- import_node_fs25.default.mkdirSync(directory, { recursive: true });
10578
+ import_node_fs26.default.mkdirSync(directory, { recursive: true });
10370
10579
  return withFileLockSync(
10371
- import_node_path28.default.join(directory, ".lock"),
10580
+ import_node_path30.default.join(directory, ".lock"),
10372
10581
  {
10373
10582
  activity: "workspace ownership transaction",
10374
10583
  target: `'${repoPath}'`,
@@ -10401,7 +10610,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
10401
10610
  try {
10402
10611
  workspace = readPersistedWorkspace(worktree.path);
10403
10612
  } catch (error) {
10404
- if (!import_node_fs25.default.existsSync(worktree.path)) continue;
10613
+ if (!import_node_fs26.default.existsSync(worktree.path)) continue;
10405
10614
  throw error;
10406
10615
  }
10407
10616
  if (!workspace) continue;
@@ -10415,7 +10624,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
10415
10624
  }
10416
10625
  function claimConflict(workspace, devpodId, worktreePath, records, providerWorkspaces, persistedOwners, exactRecord) {
10417
10626
  const recordOwner = records.find(
10418
- (record3) => record3 !== exactRecord && (record3.workspace === workspace || record3.devpodId === devpodId)
10627
+ (record4) => record4 !== exactRecord && (record4.workspace === workspace || record4.devpodId === devpodId)
10419
10628
  );
10420
10629
  if (recordOwner) {
10421
10630
  return `workspace owner record '${recordOwner.workspace}' for '${recordOwner.worktreePath}'`;
@@ -10438,7 +10647,7 @@ function claimWorkspaceIdentity(repoPath, input2) {
10438
10647
  return withWorkspaceOwnershipTransaction(repoPath, (transaction) => {
10439
10648
  const records = transaction.list();
10440
10649
  const exactRecords = records.filter(
10441
- (record3) => sameWorkspacePath(record3.worktreePath, worktreePath)
10650
+ (record4) => sameWorkspacePath(record4.worktreePath, worktreePath)
10442
10651
  );
10443
10652
  if (exactRecords.length > 1) {
10444
10653
  throw new Error(
@@ -10552,11 +10761,11 @@ function removeWorkspaceOwnership(repoPath, workspace) {
10552
10761
  (transaction) => transaction.remove(workspace)
10553
10762
  );
10554
10763
  }
10555
- function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10764
+ function inspectWorkspaceOwnership(record4, worktrees, devpods) {
10556
10765
  const worktree = worktrees.find(
10557
- (candidate) => sameWorkspacePath(candidate.path, record3.worktreePath)
10766
+ (candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
10558
10767
  );
10559
- const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record3.devpodId, record3.worktreePath) : void 0;
10768
+ const devpodOwnership = devpods ? inspectDevpodWorkspaceOwnership(devpods, record4.devpodId, record4.worktreePath) : void 0;
10560
10769
  const devpodStatus = devpodOwnership?.status ?? "unknown";
10561
10770
  if (devpodStatus === "conflict") {
10562
10771
  return { ownerStatus: "conflict", devpodStatus, worktree };
@@ -10564,7 +10773,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10564
10773
  if (worktree?.locked) {
10565
10774
  return { ownerStatus: "locked", devpodStatus, worktree };
10566
10775
  }
10567
- if ((!worktree || worktree.prunable) && import_node_fs25.default.existsSync(record3.worktreePath)) {
10776
+ if ((!worktree || worktree.prunable) && import_node_fs26.default.existsSync(record4.worktreePath)) {
10568
10777
  return { ownerStatus: "conflict", devpodStatus, worktree };
10569
10778
  }
10570
10779
  if (!worktree || worktree.prunable) {
@@ -10577,7 +10786,7 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10577
10786
  return { ownerStatus: "conflict", devpodStatus, worktree };
10578
10787
  }
10579
10788
  return {
10580
- ownerStatus: persisted === record3.workspace ? "present" : "conflict",
10789
+ ownerStatus: persisted === record4.workspace ? "present" : "conflict",
10581
10790
  devpodStatus,
10582
10791
  worktree
10583
10792
  };
@@ -10585,23 +10794,231 @@ function inspectWorkspaceOwnership(record3, worktrees, devpods) {
10585
10794
  function listMissingWorkspaceOwnership(repoPath) {
10586
10795
  const worktrees = listGitWorktrees(repoPath);
10587
10796
  return listWorkspaceOwnership(repoPath).filter(
10588
- (record3) => inspectWorkspaceOwnership(record3, worktrees, void 0).ownerStatus === "missing"
10797
+ (record4) => inspectWorkspaceOwnership(record4, worktrees, void 0).ownerStatus === "missing"
10589
10798
  );
10590
10799
  }
10591
- var import_node_child_process17, import_node_fs25, import_node_path28, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
10800
+ var import_node_child_process17, import_node_fs26, import_node_path30, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
10592
10801
  var init_workspace_ownership = __esm({
10593
10802
  "src/core/workspace-ownership.ts"() {
10594
10803
  "use strict";
10595
10804
  import_node_child_process17 = require("child_process");
10596
- import_node_fs25 = __toESM(require("fs"));
10597
- import_node_path28 = __toESM(require("path"));
10805
+ import_node_fs26 = __toESM(require("fs"));
10806
+ import_node_path30 = __toESM(require("path"));
10598
10807
  init_atomic_file();
10599
10808
  init_devpod_workspaces();
10600
10809
  init_file_lock();
10601
10810
  init_workspace();
10602
10811
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
10603
10812
  OWNERSHIP_VERSION = 1;
10604
- OWNERSHIP_DIR = import_node_path28.default.join("devrouter", "workspaces");
10813
+ OWNERSHIP_DIR = import_node_path30.default.join("devrouter", "workspaces");
10814
+ }
10815
+ });
10816
+
10817
+ // src/core/managed-stop-recovery.ts
10818
+ function registration(state) {
10819
+ resetWorkspaceRuntimeCaches();
10820
+ if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
10821
+ throw new Error("Stop provider changed.");
10822
+ const owner = inspectDevsyWorkspaceOwnership(
10823
+ listDevsyWorkspaces(),
10824
+ state.devpodId,
10825
+ state.repoPath
10826
+ );
10827
+ if (owner.status !== "owned") throw new Error("Stop requires one exact provider registration.");
10828
+ if (isLinkedWorktree(state.repoPath)) {
10829
+ const workspace = resolveWorktreeWorkspace(state.repoPath);
10830
+ const record4 = workspace ? readWorkspaceOwnership(state.repoPath, workspace) : void 0;
10831
+ if (!workspace || workspace !== state.workspace || !record4 || record4.devpodId !== state.devpodId || !sameWorkspacePath(record4.worktreePath, state.repoPath))
10832
+ throw new Error("Stop workspace identity changed.");
10833
+ resolveGitCommonDir(state.repoPath);
10834
+ } else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
10835
+ const value = owner.workspace;
10836
+ return {
10837
+ context: value.context ?? "",
10838
+ providerId: value.id,
10839
+ uid: value.uid ?? "",
10840
+ sourcePath: value.source.localFolder,
10841
+ sourceContainer: value.source.container ?? ""
10842
+ };
10843
+ }
10844
+ function compareText(left, right) {
10845
+ return left < right ? -1 : left > right ? 1 : 0;
10846
+ }
10847
+ function orderedMounts(mounts) {
10848
+ return mounts.map((mount) => ({ ...mount })).sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
10849
+ }
10850
+ function identity(container) {
10851
+ return {
10852
+ id: container.id,
10853
+ service: container.labels["com.docker.compose.service"] ?? "",
10854
+ configFiles: (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()),
10855
+ mounts: orderedMounts(container.mounts)
10856
+ };
10857
+ }
10858
+ function prove(state, baseline) {
10859
+ const owner = registration(state);
10860
+ if (Object.entries(owner).some(
10861
+ ([key, value]) => baseline[key] !== value
10862
+ )) {
10863
+ throw new Error("Retained provider identity changed.");
10864
+ }
10865
+ if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
10866
+ throw new Error("Stop daemon changed.");
10867
+ const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
10868
+ const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
10869
+ const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
10870
+ if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers2.some(
10871
+ (container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
10872
+ )) {
10873
+ throw new Error("Retained container population or immutable identity changed.");
10874
+ }
10875
+ const workspaceIds = inspectManagedStopWorkspaceIds(
10876
+ baseline.endpoint,
10877
+ baseline.composeDirectory
10878
+ ).sort();
10879
+ if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
10880
+ throw new Error("Workspace contains an unrecorded container population.");
10881
+ }
10882
+ const primary = baseline.containers.find(
10883
+ (container) => container.service === baseline.primaryService
10884
+ );
10885
+ if (!primary) throw new Error("Retained primary is missing.");
10886
+ const uidBytes = Buffer.byteLength(baseline.uid);
10887
+ const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
10888
+ if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
10889
+ throw new Error("Provider-to-container binding changed.");
10890
+ }
10891
+ if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
10892
+ throw new Error("Stop authority changed during inspection.");
10893
+ }
10894
+ return containers2;
10895
+ }
10896
+ function proveRetainedManagedStop(state) {
10897
+ const baseline = validateManagedStopBaseline(state.stopBaseline, state);
10898
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
10899
+ throw new Error("Retained stop generation changed.");
10900
+ }
10901
+ const result = prove(state, baseline);
10902
+ if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
10903
+ throw new Error("Retained stop generation changed during inspection.");
10904
+ }
10905
+ return result;
10906
+ }
10907
+ function stopFromManagedBaseline(state) {
10908
+ const baseline = validateManagedStopBaseline(state.stopBaseline, state);
10909
+ const stopped = /* @__PURE__ */ new Set();
10910
+ for (const initial of proveRetainedManagedStop(state))
10911
+ if (!initial.state.Running) stopped.add(initial.id);
10912
+ const inspect = () => {
10913
+ const current = proveRetainedManagedStop(state);
10914
+ if (current.some((container) => stopped.has(container.id) && container.state.Running)) {
10915
+ throw new Error("Retained container restarted during stop.");
10916
+ }
10917
+ for (const container of current) if (!container.state.Running) stopped.add(container.id);
10918
+ return current;
10919
+ };
10920
+ for (const retained of baseline.containers) {
10921
+ if (inspect().find((container) => container.id === retained.id)?.state.Running) {
10922
+ claimLifecycleEffect();
10923
+ stopPinnedManagedContainer(baseline.endpoint, retained.id);
10924
+ if (inspect().find((container) => container.id === retained.id)?.state.Running)
10925
+ throw new Error("Container cessation is not proven.");
10926
+ }
10927
+ }
10928
+ if (inspect().some((container) => container.state.Running))
10929
+ throw new Error("Retained workloads remain running.");
10930
+ }
10931
+ var import_node_os2, import_node_path31, import_node_util2;
10932
+ var init_managed_stop_recovery = __esm({
10933
+ "src/core/managed-stop-recovery.ts"() {
10934
+ "use strict";
10935
+ import_node_os2 = __toESM(require("os"));
10936
+ import_node_path31 = __toESM(require("path"));
10937
+ import_node_util2 = require("util");
10938
+ init_devpod_environment();
10939
+ init_devsy_workspaces();
10940
+ init_managed_compose_population();
10941
+ init_managed_runtime_state();
10942
+ init_managed_stop_baseline();
10943
+ init_reliability_context();
10944
+ init_workspace();
10945
+ init_workspace_ownership();
10946
+ init_workspace_runtime();
10947
+ }
10948
+ });
10949
+
10950
+ // src/core/route-publication.ts
10951
+ function routedAppsFromConfig(config) {
10952
+ return config.apps.filter((app) => app.kind !== "dependency");
10953
+ }
10954
+ function configuredProxyAppsFromConfig(config) {
10955
+ return routedAppsFromConfig(config).filter(
10956
+ (app) => app.runtime === "proxy"
10957
+ );
10958
+ }
10959
+ function proxyAppsFromConfig(config) {
10960
+ const routedApps = routedAppsFromConfig(config);
10961
+ const proxyApps = configuredProxyAppsFromConfig(config);
10962
+ const unsupported = routedApps.filter((app) => app.runtime !== "proxy");
10963
+ if (unsupported.length > 0) {
10964
+ throw new Error(
10965
+ `Environment reconciliation supports proxy runtime apps only; unsupported: ${unsupported.map((app) => `${app.name} (${app.runtime})`).join(", ")}`
10966
+ );
10967
+ }
10968
+ return proxyApps;
10969
+ }
10970
+ async function ensureRouteInfrastructure(apps, options = {}) {
10971
+ claimLifecycleEffect();
10972
+ ensureRouterFiles();
10973
+ await ensureNetwork(DEVNET_NAME);
10974
+ claimLifecycleEffect();
10975
+ const tlsCoverage = await ensureTLSHostsCovered(
10976
+ apps.map((app) => app.host),
10977
+ options
10978
+ );
10979
+ for (const app of apps) {
10980
+ if (app.protocol === "tcp") {
10981
+ if (!isTLSEnabled()) {
10982
+ throw new Error(
10983
+ `TCP route '${app.name}' requires TLS. Run: ${tlsSetupCommand(options.repoPath)}`
10984
+ );
10985
+ }
10986
+ activateTcpProtocol(app.tcpProtocol);
10987
+ }
10988
+ }
10989
+ claimLifecycleEffect();
10990
+ startRouterStack();
10991
+ return tlsCoverage;
10992
+ }
10993
+ async function replacePublishedProxyRoutes(repoPath, config, workspace, options = {}) {
10994
+ const apps = proxyAppsFromConfig(config);
10995
+ const tlsCoverage = options.prepareInfrastructure === false ? { refreshed: false } : await ensureRouteInfrastructure(apps, { repoPath });
10996
+ const routes = apps.map((app) => {
10997
+ const upstream = parseUpstream(app.upstream);
10998
+ return {
10999
+ name: app.name,
11000
+ host: app.host,
11001
+ protocol: app.protocol,
11002
+ tcpProtocol: app.protocol === "tcp" ? app.tcpProtocol : void 0,
11003
+ repoPath,
11004
+ port: upstream.port,
11005
+ mode: "proxy",
11006
+ upstreamHost: upstream.upstreamHost,
11007
+ workspace
11008
+ };
11009
+ });
11010
+ claimLifecycleEffect();
11011
+ replaceHostRoutesForRepo(repoPath, routes);
11012
+ return { routes, tlsRefreshed: tlsCoverage.refreshed };
11013
+ }
11014
+ var init_route_publication = __esm({
11015
+ "src/core/route-publication.ts"() {
11016
+ "use strict";
11017
+ init_docker();
11018
+ init_host_routes();
11019
+ init_reliability_context();
11020
+ init_router();
11021
+ init_tls();
10605
11022
  }
10606
11023
  });
10607
11024
 
@@ -10627,8 +11044,12 @@ function stopRetainedManagedDevsyWorkspace(options) {
10627
11044
  if (state.devpodId !== devsyId || linked && !workspace) {
10628
11045
  throw new Error("Managed stop requires the exact retained workspace identity.");
10629
11046
  }
11047
+ if (state.stopBaseline) {
11048
+ stopFromManagedBaseline(state);
11049
+ return true;
11050
+ }
10630
11051
  const workspaceEnv = workspace ? { token: workspace, gitCommonDir: resolveGitCommonDir(repoPath) } : void 0;
10631
- function registration() {
11052
+ function registration2() {
10632
11053
  resetWorkspaceRuntimeCaches();
10633
11054
  if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
10634
11055
  throw new Error("Managed stop provider selection changed.");
@@ -10638,19 +11059,19 @@ function stopRetainedManagedDevsyWorkspace(options) {
10638
11059
  throw new Error("Managed stop requires one exact retained Devsy registration.");
10639
11060
  }
10640
11061
  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) {
11062
+ const record4 = readWorkspaceOwnership(repoPath, workspace);
11063
+ if (!record4 || record4.devpodId !== devsyId || !sameWorkspacePath(record4.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir) {
10643
11064
  throw new Error("Managed stop workspace ownership changed.");
10644
11065
  }
10645
11066
  }
10646
11067
  return owner.workspace;
10647
11068
  }
10648
- const context = registration().context;
11069
+ const context = registration2().context;
10649
11070
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
10650
11071
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
10651
11072
  }
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(
11073
+ const devsyRoot = import_node_path32.default.resolve(process.env.DEVSY_HOME || import_node_path32.default.join(import_node_os3.default.homedir(), ".devsy"));
11074
+ const featureDirectory = import_node_path32.default.join(
10654
11075
  devsyRoot,
10655
11076
  "contexts",
10656
11077
  context,
@@ -10659,8 +11080,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
10659
11080
  "agent",
10660
11081
  ".docker-compose"
10661
11082
  );
10662
- function prove(previous) {
10663
- if (registration().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
11083
+ function prove2(previous) {
11084
+ if (registration2().context !== context || JSON.stringify(readManagedRuntimeState(repoPath, workspace)) !== JSON.stringify(state)) {
10664
11085
  throw new Error("Managed stop retained context or runtime record changed.");
10665
11086
  }
10666
11087
  const runtime = loadRuntimeConfig(repoPath, workspace ?? "", state.profile);
@@ -10683,49 +11104,24 @@ function stopRetainedManagedDevsyWorkspace(options) {
10683
11104
  throw new Error("Managed stop requires unchanged recorded resources and configuration.");
10684
11105
  }
10685
11106
  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(
11107
+ const primary = proveManagedComposePopulation({
11108
+ plan,
11109
+ repoPath,
11110
+ composeProject: state.composeProject,
11111
+ featureDirectory,
11112
+ devsyRoot,
11113
+ containers: containers2
11114
+ });
11115
+ if (previous && (!sameSet(
10715
11116
  previous.map((c) => c.id),
10716
11117
  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.");
11118
+ ) || containers2.some((container) => {
11119
+ const retained = previous.find((entry) => entry.id === container.id);
11120
+ return !retained || containerIdentity(retained) !== containerIdentity(container) || !retained.state.Running && container.state.Running;
11121
+ }))) {
11122
+ throw new Error("Managed stop container identity or quiescent state changed.");
10727
11123
  }
10728
- if (registration().context !== context) {
11124
+ if (registration2().context !== context) {
10729
11125
  throw new Error("Managed stop provider context changed during inspection.");
10730
11126
  }
10731
11127
  const status = inspectDevsyRuntimeStatus(devsyId);
@@ -10734,7 +11130,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10734
11130
  }
10735
11131
  return { containers: containers2, primary, status };
10736
11132
  }
10737
- const initial = prove();
11133
+ const initial = prove2();
10738
11134
  let providerError;
10739
11135
  let providerFailed = false;
10740
11136
  if (initial.status === "running") {
@@ -10747,7 +11143,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10747
11143
  }
10748
11144
  try {
10749
11145
  const stopped = () => {
10750
- const current = prove(initial.containers);
11146
+ const current = prove2(initial.containers);
10751
11147
  if (current.status !== "stopped" || current.primary.state.Running) {
10752
11148
  throw new Error("Managed stop has not stopped the exact primary container.");
10753
11149
  }
@@ -10779,17 +11175,18 @@ function stopRetainedManagedDevsyWorkspace(options) {
10779
11175
  if (providerFailed) throw providerError;
10780
11176
  return true;
10781
11177
  }
10782
- var import_node_fs26, import_node_os2, import_node_path29;
11178
+ var import_node_os3, import_node_path32;
10783
11179
  var init_managed_devsy_stop = __esm({
10784
11180
  "src/core/managed-devsy-stop.ts"() {
10785
11181
  "use strict";
10786
- import_node_fs26 = __toESM(require("fs"));
10787
- import_node_os2 = __toESM(require("os"));
10788
- import_node_path29 = __toESM(require("path"));
11182
+ import_node_os3 = __toESM(require("os"));
11183
+ import_node_path32 = __toESM(require("path"));
10789
11184
  init_devcontainer_profile();
10790
11185
  init_devpod_environment();
10791
11186
  init_devsy_workspaces();
11187
+ init_managed_compose_population();
10792
11188
  init_managed_runtime_state();
11189
+ init_managed_stop_recovery();
10793
11190
  init_repo_config();
10794
11191
  init_route_publication();
10795
11192
  init_workspace();
@@ -10875,20 +11272,20 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
10875
11272
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
10876
11273
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
10877
11274
  }
10878
- var import_node_child_process18, import_node_fs27, import_node_path30, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11275
+ var import_node_child_process18, import_node_fs27, import_node_path33, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
10879
11276
  var init_devsy_mutation = __esm({
10880
11277
  "src/core/devsy-mutation.ts"() {
10881
11278
  "use strict";
10882
11279
  import_node_child_process18 = require("child_process");
10883
11280
  import_node_fs27 = __toESM(require("fs"));
10884
- import_node_path30 = __toESM(require("path"));
11281
+ import_node_path33 = __toESM(require("path"));
10885
11282
  init_devsy_agent();
10886
11283
  init_devsy_workspaces();
10887
11284
  init_file_lock();
10888
11285
  init_managed_devsy_stop();
10889
11286
  init_reliability_context();
10890
11287
  init_router();
10891
- DEVSY_MUTATION_LOCK_FILE = import_node_path30.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
11288
+ DEVSY_MUTATION_LOCK_FILE = import_node_path33.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
10892
11289
  DEVSY_MUTATION_WAIT_MS = 18e5;
10893
11290
  }
10894
11291
  });
@@ -10981,20 +11378,20 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
10981
11378
  resetWorkspaceRuntimeCaches();
10982
11379
  return result;
10983
11380
  }
10984
- var import_node_child_process19, import_node_fs28, import_node_path31, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
11381
+ var import_node_child_process19, import_node_fs28, import_node_path34, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
10985
11382
  var init_devpod_mutation = __esm({
10986
11383
  "src/core/devpod-mutation.ts"() {
10987
11384
  "use strict";
10988
11385
  import_node_child_process19 = require("child_process");
10989
11386
  import_node_fs28 = __toESM(require("fs"));
10990
- import_node_path31 = __toESM(require("path"));
11387
+ import_node_path34 = __toESM(require("path"));
10991
11388
  init_devpod_workspaces();
10992
11389
  init_devsy_mutation();
10993
11390
  init_file_lock();
10994
11391
  init_reliability_context();
10995
11392
  init_router();
10996
11393
  init_workspace_runtime();
10997
- DEVPOD_MUTATION_LOCK_FILE = import_node_path31.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
11394
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path34.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
10998
11395
  DEVPOD_MUTATION_WAIT_MS = 18e5;
10999
11396
  }
11000
11397
  });
@@ -11009,10 +11406,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
11009
11406
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
11010
11407
  const comparableRepo = comparableWorkspacePath(repoPath);
11011
11408
  const comparableWorktree = comparableWorkspacePath(worktreePath);
11012
- const localRoot = import_node_path32.default.join(comparableRepo, "trees") + import_node_path32.default.sep;
11409
+ const localRoot = import_node_path35.default.join(comparableRepo, "trees") + import_node_path35.default.sep;
11013
11410
  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);
11411
+ const legacyPrefix = `${import_node_path35.default.basename(comparableRepo)}-`;
11412
+ return import_node_path35.default.dirname(comparableWorktree) === import_node_path35.default.dirname(comparableRepo) && import_node_path35.default.basename(comparableWorktree).startsWith(legacyPrefix);
11016
11413
  }
11017
11414
  function previewActions(devpodStatus, routeCount, includeRecord) {
11018
11415
  const actions = [
@@ -11041,15 +11438,15 @@ function ownedReason(ownerStatus) {
11041
11438
  return "Ownership conflicts with live Git, persisted token, or DevPod source evidence.";
11042
11439
  }
11043
11440
  }
11044
- function ownedCandidate(record3, worktrees, devpods, routes) {
11045
- const status = inspectWorkspaceOwnership(record3, worktrees, devpods);
11046
- const routeCount = routesForOwner(routes, record3.workspace, record3.worktreePath).length;
11441
+ function ownedCandidate(record4, worktrees, devpods, routes) {
11442
+ const status = inspectWorkspaceOwnership(record4, worktrees, devpods);
11443
+ const routeCount = routesForOwner(routes, record4.workspace, record4.worktreePath).length;
11047
11444
  const eligible = status.ownerStatus === "missing" && status.devpodStatus !== "conflict";
11048
11445
  return {
11049
11446
  kind: "owned",
11050
- workspace: record3.workspace,
11051
- devpodId: record3.devpodId,
11052
- worktreePath: record3.worktreePath,
11447
+ workspace: record4.workspace,
11448
+ devpodId: record4.devpodId,
11449
+ worktreePath: record4.worktreePath,
11053
11450
  ownerStatus: status.ownerStatus,
11054
11451
  devpodStatus: status.devpodStatus,
11055
11452
  routeCount,
@@ -11067,10 +11464,10 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
11067
11464
  const primaryPath = worktrees[0]?.path;
11068
11465
  const evidence = /* @__PURE__ */ new Map();
11069
11466
  const isRecordedRoute = (workspace, worktreePath) => records.some(
11070
- (record3) => record3.workspace === workspace && sameWorkspacePath(record3.worktreePath, worktreePath)
11467
+ (record4) => record4.workspace === workspace && sameWorkspacePath(record4.worktreePath, worktreePath)
11071
11468
  );
11072
11469
  const isRecordedDevpod = (devpodId, worktreePath) => records.some(
11073
- (record3) => record3.devpodId === devpodId && sameWorkspacePath(record3.worktreePath, worktreePath)
11470
+ (record4) => record4.devpodId === devpodId && sameWorkspacePath(record4.worktreePath, worktreePath)
11074
11471
  );
11075
11472
  const key = (workspace, worktreePath) => `${workspace}\0${comparableWorkspacePath(worktreePath)}`;
11076
11473
  for (const devpod of devpods) {
@@ -11109,8 +11506,8 @@ function legacyCandidates(repoPath, records, worktrees, devpods, routes) {
11109
11506
  }));
11110
11507
  }
11111
11508
  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) {
11509
+ const record4 = transaction.list().find((entry) => entry.workspace === candidate.workspace);
11510
+ if (!record4 || !sameWorkspacePath(record4.worktreePath, candidate.worktreePath) || record4.devpodId !== candidate.devpodId) {
11114
11511
  return {
11115
11512
  status: "blocked",
11116
11513
  candidate: {
@@ -11126,12 +11523,12 @@ function revalidateCandidate(candidate, repoPath, transaction) {
11126
11523
  };
11127
11524
  }
11128
11525
  const fresh2 = ownedCandidate(
11129
- record3,
11526
+ record4,
11130
11527
  listGitWorktrees(repoPath),
11131
- listDevpodWorkspaces(record3.worktreePath),
11528
+ listDevpodWorkspaces(record4.worktreePath),
11132
11529
  listHostRouteState()
11133
11530
  );
11134
- if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record3 };
11531
+ if (fresh2.eligible) return { status: "ready", candidate: fresh2, record: record4 };
11135
11532
  return {
11136
11533
  status: "blocked",
11137
11534
  candidate: {
@@ -11165,11 +11562,11 @@ function applyCandidate(candidate, repoPath) {
11165
11562
  };
11166
11563
  }
11167
11564
  if (revalidated.status === "blocked") return revalidated.candidate;
11168
- const { candidate: fresh2, record: record3 } = revalidated;
11565
+ const { candidate: fresh2, record: record4 } = revalidated;
11169
11566
  const actions = [];
11170
11567
  let devpodStatus;
11171
11568
  try {
11172
- const mutation = deleteOwnedDevpodWorkspace(record3.devpodId, record3.worktreePath);
11569
+ const mutation = deleteOwnedDevpodWorkspace(record4.devpodId, record4.worktreePath);
11173
11570
  devpodStatus = mutation.status === "changed" ? "owned" : "absent";
11174
11571
  } catch (error) {
11175
11572
  return { ...fresh2, actions: [failedAction("devpod", error)] };
@@ -11189,7 +11586,7 @@ function applyCandidate(candidate, repoPath) {
11189
11586
  return { ...fresh2, actions: [...actions, failedAction("routes", error)] };
11190
11587
  }
11191
11588
  try {
11192
- const removal = transaction.removeIfMatches(record3);
11589
+ const removal = transaction.removeIfMatches(record4);
11193
11590
  if (removal === "changed") {
11194
11591
  throw new Error("Ownership record changed during cleanup; record was retained.");
11195
11592
  }
@@ -11235,10 +11632,10 @@ function inspectWorkspaceGc(repo) {
11235
11632
  // that owns each record (snapshot reads: the apply path revalidates
11236
11633
  // live); legacy detection scans both registries.
11237
11634
  ...records.map(
11238
- (record3) => ownedCandidate(
11239
- record3,
11635
+ (record4) => ownedCandidate(
11636
+ record4,
11240
11637
  worktrees,
11241
- listDevpodWorkspacesFromSnapshots(record3.worktreePath),
11638
+ listDevpodWorkspacesFromSnapshots(record4.worktreePath),
11242
11639
  routes
11243
11640
  )
11244
11641
  ),
@@ -11264,11 +11661,11 @@ function applyWorkspaceGc(plan) {
11264
11661
  candidates
11265
11662
  };
11266
11663
  }
11267
- var import_node_path32;
11664
+ var import_node_path35;
11268
11665
  var init_workspace_gc = __esm({
11269
11666
  "src/core/workspace-gc.ts"() {
11270
11667
  "use strict";
11271
- import_node_path32 = __toESM(require("path"));
11668
+ import_node_path35 = __toESM(require("path"));
11272
11669
  init_devpod_mutation();
11273
11670
  init_devpod_workspaces();
11274
11671
  init_host_routes();
@@ -11621,7 +12018,7 @@ async function buildDoctorReport(options = {}) {
11621
12018
  const config = runtimeConfig.config;
11622
12019
  loadedConfig = config;
11623
12020
  loadedWorkspace = runtimeConfig.workspace;
11624
- const cliVersion = true ? "0.0.60" : "0.0.0-dev";
12021
+ const cliVersion = true ? "0.0.61" : "0.0.0-dev";
11625
12022
  const configVersion = config.devrouter?.version;
11626
12023
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
11627
12024
  addCheck(checks, {
@@ -11651,7 +12048,7 @@ async function buildDoctorReport(options = {}) {
11651
12048
  (app) => app.docker.composeFiles.map((filePath) => ({
11652
12049
  app: app.name,
11653
12050
  filePath,
11654
- absolutePath: import_node_path33.default.resolve(repo.path, filePath)
12051
+ absolutePath: import_node_path36.default.resolve(repo.path, filePath)
11655
12052
  }))
11656
12053
  ).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
11657
12054
  addCheck(checks, {
@@ -11696,7 +12093,7 @@ async function buildDoctorReport(options = {}) {
11696
12093
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
11697
12094
  app: app.name,
11698
12095
  cwd: app.hostRun.cwd,
11699
- absolutePath: import_node_path33.default.resolve(repo.path, app.hostRun.cwd)
12096
+ absolutePath: import_node_path36.default.resolve(repo.path, app.hostRun.cwd)
11700
12097
  })).filter((entry) => !import_node_fs29.default.existsSync(entry.absolutePath));
11701
12098
  addCheck(checks, {
11702
12099
  id: "repo.host-cwd",
@@ -11841,12 +12238,12 @@ async function buildDoctorReport(options = {}) {
11841
12238
  nextSteps
11842
12239
  };
11843
12240
  }
11844
- var import_node_fs29, import_node_path33, import_yaml5, POSTGRES_DEFAULTS;
12241
+ var import_node_fs29, import_node_path36, import_yaml5, POSTGRES_DEFAULTS;
11845
12242
  var init_doctor = __esm({
11846
12243
  "src/core/doctor.ts"() {
11847
12244
  "use strict";
11848
12245
  import_node_fs29 = __toESM(require("fs"));
11849
- import_node_path33 = __toESM(require("path"));
12246
+ import_node_path36 = __toESM(require("path"));
11850
12247
  import_yaml5 = __toESM(require("yaml"));
11851
12248
  init_docker();
11852
12249
  init_host_routes();
@@ -12255,7 +12652,7 @@ var init_up = __esm({
12255
12652
  });
12256
12653
 
12257
12654
  // src/core/reliability-model.ts
12258
- function record2(value) {
12655
+ function record3(value) {
12259
12656
  return typeof value === "object" && value !== null && !Array.isArray(value);
12260
12657
  }
12261
12658
  function oneOf2(value, values) {
@@ -12265,16 +12662,16 @@ function uniqueIds2(values) {
12265
12662
  return new Set(values).size === values.length && values.every(isReliabilityId);
12266
12663
  }
12267
12664
  function isFence(value) {
12268
- return record2(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
12665
+ return record3(value) && isReliabilityId(value.environmentId) && isReliabilityCounter(value.intentRevision) && isReliabilityCounter(value.runtimeGeneration) && isReliabilityCounter(value.controllerEpoch);
12269
12666
  }
12270
12667
  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);
12668
+ return record3(value) && isReliabilityId(value.id) && typeof value.pinned === "boolean" && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.length <= 128 && uniqueIds2(value.requiredCapabilities);
12272
12669
  }
12273
12670
  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);
12671
+ return record3(value) && isReliabilityId(value.capability) && oneOf2(value.infrastructure, INFRASTRUCTURE_VALUES) && oneOf2(value.application, APPLICATION_VALUES) && isReliabilityCounter(value.observedAtMs) && isReliabilityCounter(value.validForMs);
12275
12672
  }
12276
12673
  function assertReliabilityEvent(value) {
12277
- if (!record2(value) || !isFence(value) || typeof value.type !== "string") {
12674
+ if (!record3(value) || !isFence(value) || typeof value.type !== "string") {
12278
12675
  throw new Error("Invalid reliability event.");
12279
12676
  }
12280
12677
  let valid = false;
@@ -12476,7 +12873,6 @@ function handleOperationRequest(state, event) {
12476
12873
  );
12477
12874
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
12478
12875
  return unchanged(state, "conflict");
12479
- if (state.operationHistory.length >= RELIABILITY_MAX_ITEMS) return unchanged(state, "blocked");
12480
12876
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
12481
12877
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
12482
12878
  if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
@@ -12485,11 +12881,22 @@ function handleOperationRequest(state, event) {
12485
12881
  return unchanged(state, "blocked");
12486
12882
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
12487
12883
  return unchanged(state, "blocked");
12488
- if (state.desired !== "running") {
12884
+ const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
12885
+ let retired = -1;
12886
+ if (rollover) {
12887
+ if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
12888
+ return unchanged(state, "blocked");
12889
+ retired = state.operationHistory.findIndex(
12890
+ (entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
12891
+ );
12892
+ if (retired === -1) return unchanged(state, "blocked");
12893
+ }
12894
+ if (state.desired !== "running" || rollover) {
12489
12895
  const revision = advance(state.intentRevision);
12490
12896
  if (revision === null) return unchanged(state, "blocked");
12491
12897
  state.intentRevision = revision;
12492
12898
  }
12899
+ if (rollover) state.operationHistory.splice(retired, 1);
12493
12900
  state.desired = "running";
12494
12901
  state.phase = "queued";
12495
12902
  state.profile = event.profile;
@@ -12858,8 +13265,8 @@ var init_reliability_model = __esm({
12858
13265
  });
12859
13266
 
12860
13267
  // src/core/reliability-worker.ts
12861
- function sameFence2(record3, fence) {
12862
- return Object.entries(reliabilityFence(record3.state)).every(
13268
+ function sameFence2(record4, fence) {
13269
+ return Object.entries(reliabilityFence(record4.state)).every(
12863
13270
  ([key, value]) => value === fence[key]
12864
13271
  );
12865
13272
  }
@@ -12882,7 +13289,7 @@ function signalOwnedGroup(child, signal) {
12882
13289
  async function runLifecycleWorker(request) {
12883
13290
  if (process.platform === "win32")
12884
13291
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
12885
- const workerPath = import_node_path34.default.join(__dirname, "devrouter-lifecycle-worker.js");
13292
+ const workerPath = import_node_path37.default.join(__dirname, "devrouter-lifecycle-worker.js");
12886
13293
  if (!import_node_fs30.default.existsSync(workerPath))
12887
13294
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
12888
13295
  const child = (0, import_node_child_process21.fork)(workerPath, [], {
@@ -12930,28 +13337,28 @@ async function runLifecycleWorker(request) {
12930
13337
  if (request.kind !== "stop") {
12931
13338
  const birth = processBirthIdentity(child.pid);
12932
13339
  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) {
13340
+ updateReliabilityOperation(request.identity, (record4) => {
13341
+ if (!sameFence2(record4, request.fence) || record4.worker || record4.state.operation?.id !== request.operationId) {
12935
13342
  throw new Error("Lifecycle intent changed before dispatch.");
12936
13343
  }
12937
- record3.worker = {
13344
+ record4.worker = {
12938
13345
  id: request.workerId,
12939
13346
  operationId: request.operationId,
12940
13347
  pid: child.pid,
12941
13348
  birth
12942
13349
  };
12943
13350
  const transition2 = stepReliability(
12944
- record3.state,
13351
+ record4.state,
12945
13352
  { ...request.fence, type: "dispatch" },
12946
13353
  Date.now()
12947
13354
  );
12948
13355
  if (transition2.outcome !== "accepted")
12949
13356
  throw new Error("Lifecycle dispatch is blocked.");
12950
- record3.state = transition2.state;
13357
+ record4.state = transition2.state;
12951
13358
  });
12952
- updateReliabilityOperation(request.identity, (record3) => {
13359
+ updateReliabilityOperation(request.identity, (record4) => {
12953
13360
  const transition2 = stepReliability(
12954
- record3.state,
13361
+ record4.state,
12955
13362
  {
12956
13363
  ...request.fence,
12957
13364
  type: "dispatch-persisted",
@@ -12959,7 +13366,7 @@ async function runLifecycleWorker(request) {
12959
13366
  },
12960
13367
  Date.now()
12961
13368
  );
12962
- record3.state = transition2.state;
13369
+ record4.state = transition2.state;
12963
13370
  if (!transition2.effects.some((effect2) => effect2.kind === "launch")) {
12964
13371
  throw new Error("Lifecycle dispatch persistence was not acknowledged.");
12965
13372
  }
@@ -12973,8 +13380,8 @@ async function runLifecycleWorker(request) {
12973
13380
  });
12974
13381
  monitor = setInterval(() => {
12975
13382
  try {
12976
- const record3 = readReliabilityOperation(request.identity);
12977
- if (!record3 || !sameFence2(record3, request.fence)) cancel();
13383
+ const record4 = readReliabilityOperation(request.identity);
13384
+ if (!record4 || !sameFence2(record4, request.fence)) cancel();
12978
13385
  } catch (error) {
12979
13386
  failure = error instanceof Error ? error : new Error(String(error));
12980
13387
  cancel();
@@ -12991,13 +13398,13 @@ async function runLifecycleWorker(request) {
12991
13398
  child.once("close", () => {
12992
13399
  try {
12993
13400
  if (request.kind !== "stop") {
12994
- updateReliabilityOperation(request.identity, (record3) => {
12995
- if (record3.worker?.id !== request.workerId || record3.worker.pid !== child.pid) return;
13401
+ updateReliabilityOperation(request.identity, (record4) => {
13402
+ if (record4.worker?.id !== request.workerId || record4.worker.pid !== child.pid) return;
12996
13403
  if (!result) {
12997
- record3.state = stepReliability(
12998
- record3.state,
13404
+ record4.state = stepReliability(
13405
+ record4.state,
12999
13406
  {
13000
- ...reliabilityFence(record3.state),
13407
+ ...reliabilityFence(record4.state),
13001
13408
  type: "interrupted",
13002
13409
  operationId: request.operationId
13003
13410
  },
@@ -13005,16 +13412,16 @@ async function runLifecycleWorker(request) {
13005
13412
  ).state;
13006
13413
  }
13007
13414
  if (child.pid && workerGroupAbsent(child.pid)) {
13008
- record3.state = stepReliability(
13009
- record3.state,
13415
+ record4.state = stepReliability(
13416
+ record4.state,
13010
13417
  {
13011
- ...reliabilityFence(record3.state),
13418
+ ...reliabilityFence(record4.state),
13012
13419
  type: "drained",
13013
13420
  operationId: request.operationId
13014
13421
  },
13015
13422
  Date.now()
13016
13423
  ).state;
13017
- record3.worker = null;
13424
+ record4.worker = null;
13018
13425
  }
13019
13426
  });
13020
13427
  }
@@ -13041,14 +13448,14 @@ async function runLifecycleWorker(request) {
13041
13448
  function newLifecycleIds() {
13042
13449
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
13043
13450
  }
13044
- var import_node_child_process21, import_node_crypto16, import_node_fs30, import_node_path34;
13451
+ var import_node_child_process21, import_node_crypto16, import_node_fs30, import_node_path37;
13045
13452
  var init_reliability_worker = __esm({
13046
13453
  "src/core/reliability-worker.ts"() {
13047
13454
  "use strict";
13048
13455
  import_node_child_process21 = require("child_process");
13049
13456
  import_node_crypto16 = require("crypto");
13050
13457
  import_node_fs30 = __toESM(require("fs"));
13051
- import_node_path34 = __toESM(require("path"));
13458
+ import_node_path37 = __toESM(require("path"));
13052
13459
  init_file_lock();
13053
13460
  init_reliability_contract();
13054
13461
  init_reliability_model();
@@ -13202,19 +13609,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
13202
13609
  async function ensureTraefikRoutesRemoved(routes, options = {}) {
13203
13610
  return ensureTraefikRouteExpectation(routes, "removed", options);
13204
13611
  }
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;
13612
+ var import_node_child_process23, import_node_fs32, import_node_path38, 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
13613
  var init_traefik_route_health = __esm({
13207
13614
  "src/core/traefik-route-health.ts"() {
13208
13615
  "use strict";
13209
13616
  import_node_child_process23 = require("child_process");
13210
13617
  import_node_fs32 = __toESM(require("fs"));
13211
- import_node_path35 = __toESM(require("path"));
13618
+ import_node_path38 = __toESM(require("path"));
13212
13619
  init_file_lock();
13213
13620
  init_host_routes();
13214
13621
  init_reliability_context();
13215
13622
  init_router();
13216
13623
  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");
13624
+ ROUTER_RELOAD_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "router-reload.lock");
13218
13625
  ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
13219
13626
  DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
13220
13627
  DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -13253,17 +13660,18 @@ function resolveLinkedTarget(repoPath) {
13253
13660
  gitCommonDir: resolveGitCommonDir(repoPath)
13254
13661
  };
13255
13662
  }
13256
- var import_node_child_process24, import_node_fs33, import_node_path36;
13663
+ var import_node_child_process24, import_node_fs33, import_node_path39;
13257
13664
  var init_workspace_ensure = __esm({
13258
13665
  "src/core/workspace-ensure.ts"() {
13259
13666
  "use strict";
13260
13667
  import_node_child_process24 = require("child_process");
13261
13668
  import_node_fs33 = __toESM(require("fs"));
13262
- import_node_path36 = __toESM(require("path"));
13669
+ import_node_path39 = __toESM(require("path"));
13263
13670
  init_devcontainer_profile();
13264
13671
  init_devpod_environment();
13265
13672
  init_devpod_mutation();
13266
13673
  init_devpod_workspaces();
13674
+ init_devsy_mutation();
13267
13675
  init_file_lock();
13268
13676
  init_host_routes();
13269
13677
  init_http_route_probe();
@@ -13271,6 +13679,7 @@ var init_workspace_ensure = __esm({
13271
13679
  init_managed_post_start();
13272
13680
  init_managed_runtime_state();
13273
13681
  init_managed_runtime_status();
13682
+ init_managed_stop_recovery();
13274
13683
  init_reliability_lifecycle();
13275
13684
  init_repo_config();
13276
13685
  init_route_publication();
@@ -13292,50 +13701,51 @@ async function withLifecycleOperationLock(repoPath, operation) {
13292
13701
  }
13293
13702
  return withWorkspaceLifecycleLock(repoPath, operation);
13294
13703
  }
13295
- function stepRecord(record3, event) {
13296
- const transition2 = stepReliability(record3.state, event, Date.now());
13704
+ function stepRecord(record4, event) {
13705
+ const transition2 = stepReliability(record4.state, event, Date.now());
13297
13706
  if (!["accepted", "joined"].includes(transition2.outcome))
13298
13707
  throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
13299
- record3.state = transition2.state;
13708
+ record4.state = transition2.state;
13300
13709
  }
13301
- function reconcileDrained(record3) {
13302
- const worker = record3.worker;
13710
+ function reconcileDrained(record4) {
13711
+ const worker = record4.worker;
13303
13712
  if (!worker) {
13304
- if (record3.state.operation?.status === "NOT_STARTED" && !record3.state.operation.drained) {
13305
- stepRecord(record3, {
13306
- ...reliabilityFence(record3.state),
13713
+ if (record4.state.operation?.status === "NOT_STARTED" && !record4.state.operation.drained) {
13714
+ stepRecord(record4, {
13715
+ ...reliabilityFence(record4.state),
13307
13716
  type: "drained",
13308
- operationId: record3.state.operation.id
13717
+ operationId: record4.state.operation.id
13309
13718
  });
13310
13719
  }
13311
13720
  return;
13312
13721
  }
13313
13722
  if (processBirthIdentity(worker.pid) === worker.birth || !workerGroupAbsent(worker.pid)) return;
13314
- if (record3.state.operation) {
13723
+ if (record4.state.operation) {
13315
13724
  const interrupted = stepReliability(
13316
- record3.state,
13725
+ record4.state,
13317
13726
  {
13318
- ...reliabilityFence(record3.state),
13727
+ ...reliabilityFence(record4.state),
13319
13728
  type: "interrupted",
13320
13729
  operationId: worker.operationId
13321
13730
  },
13322
13731
  Date.now()
13323
13732
  );
13324
- record3.state = interrupted.state;
13325
- stepRecord(record3, {
13326
- ...reliabilityFence(record3.state),
13733
+ record4.state = interrupted.state;
13734
+ stepRecord(record4, {
13735
+ ...reliabilityFence(record4.state),
13327
13736
  type: "drained",
13328
13737
  operationId: worker.operationId
13329
13738
  });
13330
- record3.worker = null;
13739
+ record4.worker = null;
13331
13740
  }
13332
13741
  }
13333
13742
  async function superviseLifecycle(kind, repoPath, options = {}, command) {
13334
13743
  repoPath = comparableWorkspacePath(repoPath);
13335
13744
  if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
13745
+ if (kind === "stop") throw new Error("Stop requires the existing linked workspace identity.");
13336
13746
  resolveLinkedTarget(repoPath);
13337
13747
  }
13338
- const identity = {
13748
+ const identity2 = {
13339
13749
  repoPath,
13340
13750
  workspace: resolveWorktreeWorkspace(repoPath) ?? null,
13341
13751
  provider: resolveWorkspaceRuntimeOrDefault(repoPath)
@@ -13343,48 +13753,50 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
13343
13753
  const ids = newLifecycleIds();
13344
13754
  let fence;
13345
13755
  const runtimeRunning = kind === "exec" ? Boolean(resolveRunningWorkspaceContainer(repoPath)) : false;
13346
- updateReliabilityOperation(identity, (record3) => {
13347
- reconcileDrained(record3);
13756
+ updateReliabilityOperation(identity2, (record4) => {
13757
+ reconcileDrained(record4);
13348
13758
  if (kind === "stop") {
13349
- stepRecord(record3, { ...reliabilityFence(record3.state), type: "stop" });
13759
+ stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
13350
13760
  } else {
13351
- if (record3.worker)
13761
+ if (record4.worker)
13352
13762
  throw new Error(
13353
13763
  "An earlier lifecycle worker may still be active; use explicit stop to reconcile it."
13354
13764
  );
13355
- record3.outcome = null;
13356
- stepRecord(record3, {
13357
- ...reliabilityFence(record3.state),
13765
+ record4.outcome = null;
13766
+ stepRecord(record4, {
13767
+ ...reliabilityFence(record4.state),
13358
13768
  type: "operation-request",
13359
13769
  kind,
13360
13770
  key: ids.requestId,
13361
13771
  operationId: ids.operationId,
13362
- profile: options.profile ?? record3.state.profile ?? "full",
13772
+ profile: options.profile ?? record4.state.profile ?? "full",
13363
13773
  consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
13364
13774
  runtimeRunning
13365
13775
  });
13366
13776
  }
13367
- fence = reliabilityFence(record3.state);
13777
+ fence = reliabilityFence(record4.state);
13368
13778
  });
13369
13779
  return runLifecycleWorker({
13370
13780
  kind,
13371
13781
  repoPath,
13372
- identity,
13782
+ identity: identity2,
13373
13783
  ...ids,
13374
13784
  fence,
13375
13785
  options,
13376
13786
  ...command ? { command } : {}
13377
13787
  });
13378
13788
  }
13379
- var import_node_path37, activeWorker, lockHeld;
13789
+ var import_node_path40, activeWorker, lockHeld;
13380
13790
  var init_reliability_lifecycle = __esm({
13381
13791
  "src/core/reliability-lifecycle.ts"() {
13382
13792
  "use strict";
13383
- import_node_path37 = __toESM(require("path"));
13793
+ import_node_path40 = __toESM(require("path"));
13384
13794
  init_devpod_environment();
13795
+ init_devsy_mutation();
13385
13796
  init_file_lock();
13386
13797
  init_host_routes();
13387
13798
  init_managed_runtime_state();
13799
+ init_managed_stop_recovery();
13388
13800
  init_reliability_context();
13389
13801
  init_reliability_contract();
13390
13802
  init_reliability_model();
@@ -13798,7 +14210,7 @@ function readJson(filePath) {
13798
14210
  }
13799
14211
  }
13800
14212
  function relative(repoPath, filePath) {
13801
- return import_node_path38.default.relative(repoPath, filePath) || ".";
14213
+ return import_node_path41.default.relative(repoPath, filePath) || ".";
13802
14214
  }
13803
14215
  function redactEnvAssignments(value) {
13804
14216
  return value.replace(
@@ -13839,7 +14251,7 @@ function inspectPackageManager(repoPath, pkg) {
13839
14251
  ["bun.lock", "bun"]
13840
14252
  ];
13841
14253
  for (const [fileName, name] of lockfiles) {
13842
- if (import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))) {
14254
+ if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
13843
14255
  return { name, source: fileName };
13844
14256
  }
13845
14257
  }
@@ -13854,7 +14266,7 @@ function inspectNode(repoPath, pkg) {
13854
14266
  if (typeof engines?.node === "string") {
13855
14267
  return { version: engines.node, source: "package.json:engines.node" };
13856
14268
  }
13857
- const nvmrc = import_node_path38.default.join(repoPath, ".nvmrc");
14269
+ const nvmrc = import_node_path41.default.join(repoPath, ".nvmrc");
13858
14270
  if (import_node_fs34.default.existsSync(nvmrc)) {
13859
14271
  const version = import_node_fs34.default.readFileSync(nvmrc, "utf-8").trim();
13860
14272
  return { version, source: ".nvmrc" };
@@ -13917,7 +14329,7 @@ function configuredComposeFiles(repoPath) {
13917
14329
  const files = config.apps.filter(
13918
14330
  (app) => app.runtime === "docker"
13919
14331
  ).flatMap((app) => app.docker.composeFiles).filter(
13920
- (fileName) => !import_node_path38.default.isAbsolute(fileName) && !import_node_path38.default.normalize(fileName).startsWith("..")
14332
+ (fileName) => !import_node_path41.default.isAbsolute(fileName) && !import_node_path41.default.normalize(fileName).startsWith("..")
13921
14333
  );
13922
14334
  return Array.from(new Set(files));
13923
14335
  } catch {
@@ -13935,7 +14347,7 @@ function composeFiles(repoPath) {
13935
14347
  ...configuredComposeFiles(repoPath)
13936
14348
  ];
13937
14349
  return Array.from(new Set(candidates)).filter(
13938
- (fileName) => import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))
14350
+ (fileName) => import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))
13939
14351
  );
13940
14352
  }
13941
14353
  function stringArray2(value) {
@@ -13948,8 +14360,8 @@ function envNames(value) {
13948
14360
  if (Array.isArray(value)) {
13949
14361
  return value.filter((entry) => typeof entry === "string").map((entry) => entry.split("=")[0]?.trim()).filter((entry) => Boolean(entry));
13950
14362
  }
13951
- const record3 = asRecord3(value);
13952
- return record3 ? Object.keys(record3).sort() : [];
14363
+ const record4 = asRecord3(value);
14364
+ return record4 ? Object.keys(record4).sort() : [];
13953
14365
  }
13954
14366
  function inferServiceKind(name, image) {
13955
14367
  const source = `${name} ${image ?? ""}`.toLowerCase();
@@ -13973,7 +14385,7 @@ function inspectServices(repoPath) {
13973
14385
  const services = [];
13974
14386
  for (const fileName of composeFiles(repoPath)) {
13975
14387
  try {
13976
- const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(import_node_path38.default.join(repoPath, fileName), "utf-8"));
14388
+ const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8"));
13977
14389
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
13978
14390
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
13979
14391
  const service = asRecord3(value);
@@ -14009,7 +14421,7 @@ function inspectServices(repoPath) {
14009
14421
  }
14010
14422
  function inspectEnvFiles(repoPath) {
14011
14423
  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");
14424
+ const content = import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8");
14013
14425
  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
14426
  return { path: fileName, names };
14015
14427
  });
@@ -14023,13 +14435,13 @@ function inspectEnvFiles(repoPath) {
14023
14435
  };
14024
14436
  }
14025
14437
  function inspectDevcontainer(repoPath) {
14026
- const dir = import_node_path38.default.join(repoPath, ".devcontainer");
14438
+ const dir = import_node_path41.default.join(repoPath, ".devcontainer");
14027
14439
  if (!import_node_fs34.default.existsSync(dir)) {
14028
14440
  return { exists: false, files: [] };
14029
14441
  }
14030
14442
  return {
14031
14443
  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}`)
14444
+ files: import_node_fs34.default.readdirSync(dir).filter((fileName) => import_node_fs34.default.statSync(import_node_path41.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
14033
14445
  };
14034
14446
  }
14035
14447
  function inspectDevrouter(repoPath) {
@@ -14078,14 +14490,14 @@ function inspectAgentGuidance(repoPath) {
14078
14490
  ["AGENTS.md", "agents"],
14079
14491
  ["CLAUDE.md", "claude"]
14080
14492
  ]) {
14081
- if (import_node_fs34.default.existsSync(import_node_path38.default.join(repoPath, fileName))) {
14493
+ if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
14082
14494
  results.push({ path: fileName, kind });
14083
14495
  }
14084
14496
  }
14085
- const skillsDir = import_node_path38.default.join(repoPath, ".agents", "skills");
14497
+ const skillsDir = import_node_path41.default.join(repoPath, ".agents", "skills");
14086
14498
  if (import_node_fs34.default.existsSync(skillsDir)) {
14087
14499
  for (const name of import_node_fs34.default.readdirSync(skillsDir).sort()) {
14088
- const skillPath = import_node_path38.default.join(skillsDir, name, "SKILL.md");
14500
+ const skillPath = import_node_path41.default.join(skillsDir, name, "SKILL.md");
14089
14501
  if (import_node_fs34.default.existsSync(skillPath)) {
14090
14502
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
14091
14503
  }
@@ -14129,7 +14541,7 @@ function buildIssues(report) {
14129
14541
  }
14130
14542
  function inspectRepo(options = {}) {
14131
14543
  const repoPath = resolveRepoPath(options.repo);
14132
- const pkg = readJson(import_node_path38.default.join(repoPath, "package.json"));
14544
+ const pkg = readJson(import_node_path41.default.join(repoPath, "package.json"));
14133
14545
  const scripts = inspectScripts(pkg);
14134
14546
  const reportWithoutIssues = {
14135
14547
  repoPath,
@@ -14148,12 +14560,12 @@ function inspectRepo(options = {}) {
14148
14560
  issues: buildIssues(reportWithoutIssues)
14149
14561
  };
14150
14562
  }
14151
- var import_node_fs34, import_node_path38, import_yaml6;
14563
+ var import_node_fs34, import_node_path41, import_yaml6;
14152
14564
  var init_repo_inspect = __esm({
14153
14565
  "src/core/repo-inspect.ts"() {
14154
14566
  "use strict";
14155
14567
  import_node_fs34 = __toESM(require("fs"));
14156
- import_node_path38 = __toESM(require("path"));
14568
+ import_node_path41 = __toESM(require("path"));
14157
14569
  import_yaml6 = __toESM(require("yaml"));
14158
14570
  init_repo_config();
14159
14571
  }
@@ -14258,7 +14670,7 @@ function requiredFileChecks(repoPath) {
14258
14670
  ".devcontainer/docker-compose.yml",
14259
14671
  ".devrouter.yml"
14260
14672
  ];
14261
- const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(import_node_path39.default.join(repoPath, fileName)));
14673
+ const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName)));
14262
14674
  return {
14263
14675
  id: "repo.devcontainer.verify-files",
14264
14676
  level: missing.length === 0 ? "ok" : "error",
@@ -14483,12 +14895,12 @@ async function verifyDevcontainer(options = {}) {
14483
14895
  nextSteps: collectNextSteps3(checks)
14484
14896
  };
14485
14897
  }
14486
- var import_node_fs35, import_node_path39;
14898
+ var import_node_fs35, import_node_path42;
14487
14899
  var init_devcontainer_verify = __esm({
14488
14900
  "src/core/devcontainer-verify.ts"() {
14489
14901
  "use strict";
14490
14902
  import_node_fs35 = __toESM(require("fs"));
14491
- import_node_path39 = __toESM(require("path"));
14903
+ import_node_path42 = __toESM(require("path"));
14492
14904
  init_capabilities();
14493
14905
  init_doctor();
14494
14906
  init_host_routes();
@@ -14785,7 +15197,7 @@ function packageManagerIssues(repo) {
14785
15197
  }
14786
15198
  function plannedFiles(repoPath, version) {
14787
15199
  const repo = inspectRepo({ repo: repoPath });
14788
- const projectName = sanitizeProjectName(import_node_path40.default.basename(repo.repoPath));
15200
+ const projectName = sanitizeProjectName(import_node_path43.default.basename(repo.repoPath));
14789
15201
  const nodeMajor = majorVersion(repo.node?.version, "24");
14790
15202
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
14791
15203
  const port = inferPort2(repo);
@@ -14830,7 +15242,7 @@ function plannedFiles(repoPath, version) {
14830
15242
  };
14831
15243
  }
14832
15244
  function classifyFile(repoPath, file) {
14833
- const absolutePath = import_node_path40.default.join(repoPath, file.relativePath);
15245
+ const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
14834
15246
  if (!import_node_fs36.default.existsSync(absolutePath)) {
14835
15247
  return {
14836
15248
  path: file.relativePath,
@@ -14896,8 +15308,8 @@ function buildPlan(repoPath, dryRun, version) {
14896
15308
  };
14897
15309
  }
14898
15310
  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 });
15311
+ const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
15312
+ import_node_fs36.default.mkdirSync(import_node_path43.default.dirname(absolutePath), { recursive: true });
14901
15313
  import_node_fs36.default.writeFileSync(absolutePath, file.content, "utf-8");
14902
15314
  if (file.executable) {
14903
15315
  import_node_fs36.default.chmodSync(absolutePath, 493);
@@ -14938,12 +15350,12 @@ function writeDevcontainer(options = {}) {
14938
15350
  nextSteps: postWriteNextSteps(repoPath)
14939
15351
  };
14940
15352
  }
14941
- var import_node_fs36, import_node_path40, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
15353
+ var import_node_fs36, import_node_path43, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
14942
15354
  var init_devcontainer_write = __esm({
14943
15355
  "src/core/devcontainer-write.ts"() {
14944
15356
  "use strict";
14945
15357
  import_node_fs36 = __toESM(require("fs"));
14946
- import_node_path40 = __toESM(require("path"));
15358
+ import_node_path43 = __toESM(require("path"));
14947
15359
  init_repo_config();
14948
15360
  init_repo_inspect();
14949
15361
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -15277,7 +15689,7 @@ function buildProfilePlanReport(options) {
15277
15689
  return { ...report, contractPath, bindings };
15278
15690
  }
15279
15691
  function loadContract(repoPath, contractPath) {
15280
- if (import_node_path41.default.isAbsolute(contractPath)) {
15692
+ if (import_node_path44.default.isAbsolute(contractPath)) {
15281
15693
  fail3("profile plan contract path must be relative to the repository.");
15282
15694
  }
15283
15695
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
@@ -15302,7 +15714,7 @@ function loadContract(repoPath, contractPath) {
15302
15714
  }
15303
15715
  return {
15304
15716
  contract: parseProfilePlanContract(source),
15305
- contractPath: import_node_path41.default.relative(repoPath, absolutePath).split(import_node_path41.default.sep).join("/")
15717
+ contractPath: import_node_path44.default.relative(repoPath, absolutePath).split(import_node_path44.default.sep).join("/")
15306
15718
  };
15307
15719
  }
15308
15720
  function resolveProfilePlan(options) {
@@ -15316,12 +15728,12 @@ function resolveProfilePlan(options) {
15316
15728
  contractPath: loaded.contractPath
15317
15729
  });
15318
15730
  }
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;
15731
+ var import_node_fs37, import_node_path44, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
15320
15732
  var init_profile_plan = __esm({
15321
15733
  "src/core/profile-plan.ts"() {
15322
15734
  "use strict";
15323
15735
  import_node_fs37 = __toESM(require("fs"));
15324
- import_node_path41 = __toESM(require("path"));
15736
+ import_node_path44 = __toESM(require("path"));
15325
15737
  import_yaml7 = require("yaml");
15326
15738
  init_paths();
15327
15739
  init_profile_resolution();
@@ -15353,7 +15765,7 @@ async function runProfileResolveCommand(options) {
15353
15765
  async function runProfilePlanCommand(options) {
15354
15766
  const report = resolveProfilePlan(options);
15355
15767
  if (options.output) {
15356
- writeFileAtomically(import_node_path42.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15768
+ writeFileAtomically(import_node_path45.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15357
15769
  `);
15358
15770
  }
15359
15771
  if (options.json) {
@@ -15389,14 +15801,14 @@ function printProfilePlanSummary(report, output2) {
15389
15801
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
15390
15802
  `);
15391
15803
  }
15392
- if (output2) process.stdout.write(`Output: ${import_node_path42.default.resolve(output2)}
15804
+ if (output2) process.stdout.write(`Output: ${import_node_path45.default.resolve(output2)}
15393
15805
  `);
15394
15806
  }
15395
- var import_node_path42;
15807
+ var import_node_path45;
15396
15808
  var init_profile = __esm({
15397
15809
  "src/commands/profile.ts"() {
15398
15810
  "use strict";
15399
- import_node_path42 = __toESM(require("path"));
15811
+ import_node_path45 = __toESM(require("path"));
15400
15812
  init_atomic_file();
15401
15813
  init_output();
15402
15814
  init_profile_plan();
@@ -15686,7 +16098,7 @@ function sanitizeRouterId(value) {
15686
16098
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
15687
16099
  }
15688
16100
  function repoHash(repoPath) {
15689
- return (0, import_node_crypto17.createHash)("sha1").update(import_node_path43.default.resolve(repoPath)).digest("hex").slice(0, 12);
16101
+ return (0, import_node_crypto17.createHash)("sha1").update(import_node_path46.default.resolve(repoPath)).digest("hex").slice(0, 12);
15690
16102
  }
15691
16103
  function asDockerApp(app) {
15692
16104
  return app.runtime === "docker";
@@ -15765,9 +16177,9 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
15765
16177
  if (dockerApps.length === 0) {
15766
16178
  throw new Error("No docker apps selected to prepare compose overlay.");
15767
16179
  }
15768
- const cachePath = import_node_path43.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
16180
+ const cachePath = import_node_path46.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
15769
16181
  import_node_fs38.default.mkdirSync(cachePath, { recursive: true });
15770
- const overlayPath = import_node_path43.default.join(cachePath, "compose.devrouter.yml");
16182
+ const overlayPath = import_node_path46.default.join(cachePath, "compose.devrouter.yml");
15771
16183
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
15772
16184
  import_node_fs38.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
15773
16185
  return {
@@ -15889,14 +16301,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
15889
16301
  const port = Number(match[1]);
15890
16302
  return Number.isInteger(port) && port > 0 ? port : void 0;
15891
16303
  }
15892
- var import_node_child_process27, import_node_crypto17, import_node_fs38, import_node_path43, import_yaml8;
16304
+ var import_node_child_process27, import_node_crypto17, import_node_fs38, import_node_path46, import_yaml8;
15893
16305
  var init_docker_run = __esm({
15894
16306
  "src/core/docker-run.ts"() {
15895
16307
  "use strict";
15896
16308
  import_node_child_process27 = require("child_process");
15897
16309
  import_node_crypto17 = require("crypto");
15898
16310
  import_node_fs38 = __toESM(require("fs"));
15899
- import_node_path43 = __toESM(require("path"));
16311
+ import_node_path46 = __toESM(require("path"));
15900
16312
  import_yaml8 = __toESM(require("yaml"));
15901
16313
  init_docker_error_guidance();
15902
16314
  init_paths();
@@ -16750,7 +17162,7 @@ function measureWorktreeConsumption(worktreePath, options) {
16750
17162
  timedOut = true;
16751
17163
  break;
16752
17164
  }
16753
- const entryPath = import_node_path44.default.join(dirPath, entry.name);
17165
+ const entryPath = import_node_path47.default.join(dirPath, entry.name);
16754
17166
  let entryStat;
16755
17167
  try {
16756
17168
  entryStat = import_node_fs39.default.lstatSync(entryPath);
@@ -16827,12 +17239,12 @@ function describeError(error, worktreePath) {
16827
17239
  }
16828
17240
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
16829
17241
  }
16830
- var import_node_fs39, import_node_path44, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
17242
+ var import_node_fs39, import_node_path47, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
16831
17243
  var init_workspace_consumption = __esm({
16832
17244
  "src/core/workspace-consumption.ts"() {
16833
17245
  "use strict";
16834
17246
  import_node_fs39 = __toESM(require("fs"));
16835
- import_node_path44 = __toESM(require("path"));
17247
+ import_node_path47 = __toESM(require("path"));
16836
17248
  init_devpod_environment();
16837
17249
  DEFAULT_DEADLINE_MS = 1e4;
16838
17250
  BLOCK_SIZE_BYTES = 512;
@@ -17082,8 +17494,8 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17082
17494
  return { status: "unknown", reason: "Current HEAD or branch evidence is unavailable." };
17083
17495
  }
17084
17496
  const originUrl = gitOutput(repoPath, ["config", "--get", "remote.origin.url"], commandRunner);
17085
- const identity = originUrl ? parseRemoteIdentity(originUrl) : void 0;
17086
- if (!identity) {
17497
+ const identity2 = originUrl ? parseRemoteIdentity(originUrl) : void 0;
17498
+ if (!identity2) {
17087
17499
  return { status: "unknown", reason: "The origin is missing or uses an unsupported forge." };
17088
17500
  }
17089
17501
  const localTargetBranch = parseTargetBranch(
@@ -17128,11 +17540,11 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17128
17540
  };
17129
17541
  }
17130
17542
  const forgeChanges = [];
17131
- const forgeCommand = identity.provider === "github" ? [
17543
+ const forgeCommand = identity2.provider === "github" ? [
17132
17544
  "pr",
17133
17545
  "list",
17134
17546
  "--repo",
17135
- identity.project,
17547
+ identity2.project,
17136
17548
  "--state",
17137
17549
  "all",
17138
17550
  "--head",
@@ -17143,20 +17555,20 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17143
17555
  "mr",
17144
17556
  "list",
17145
17557
  "--repo",
17146
- identity.project,
17558
+ identity2.project,
17147
17559
  "--all",
17148
17560
  "--source-branch",
17149
17561
  branch,
17150
17562
  "--output",
17151
17563
  "json"
17152
17564
  ];
17153
- const forge = commandRunner(identity.provider === "github" ? "gh" : "glab", forgeCommand);
17565
+ const forge = commandRunner(identity2.provider === "github" ? "gh" : "glab", forgeCommand);
17154
17566
  if (forge.status !== 0 || forge.error) {
17155
17567
  return { status: "unknown", reason: "The forge query was unavailable or unauthenticated." };
17156
17568
  }
17157
17569
  try {
17158
17570
  const parsed = JSON.parse(forge.stdout);
17159
- const changes = identity.provider === "github" ? parseGitHubChanges(parsed, identity.project, branch) : parseGitLabChanges(parsed, identity.project, branch);
17571
+ const changes = identity2.provider === "github" ? parseGitHubChanges(parsed, identity2.project, branch) : parseGitLabChanges(parsed, identity2.project, branch);
17160
17572
  if (!changes) {
17161
17573
  return { status: "unknown", reason: "The forge response was malformed." };
17162
17574
  }
@@ -17222,31 +17634,31 @@ function inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, co
17222
17634
  reason: "Fresh target and forge evidence did not prove integration."
17223
17635
  };
17224
17636
  }
17225
- function routeStatus(record3, routes) {
17637
+ function routeStatus(record4, routes) {
17226
17638
  if (!routes) return "unknown";
17227
17639
  const matchingPath = routes.filter(
17228
- (route) => sameWorkspacePath(route.repoPath, record3.worktreePath)
17640
+ (route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
17229
17641
  );
17230
17642
  if (matchingPath.length === 0) return "absent";
17231
- return matchingPath.every((route) => route.workspace === record3.workspace) ? "owned" : "conflict";
17643
+ return matchingPath.every((route) => route.workspace === record4.workspace) ? "owned" : "conflict";
17232
17644
  }
17233
- function buildActivityEvidence(record3, providerStatus, provider, routeEntries, git) {
17645
+ function buildActivityEvidence(record4, providerStatus, provider, routeEntries, git) {
17234
17646
  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
17647
  const routeTimestamps = routeEntries?.map((route) => route.updatedAt) ?? [];
17236
17648
  const malformedRoute = routeTimestamps.some((timestamp) => !validTimestamp(timestamp));
17237
17649
  const newestRoute = routeTimestamps.filter(validTimestamp).sort((left, right) => (parseTimestamp(right) ?? 0) - (parseTimestamp(left) ?? 0))[0];
17238
17650
  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" };
17651
+ const ownerEvidence = validTimestamp(record4.updatedAt) ? { source: "ownership.updatedAt", status: "valid", timestamp: record4.updatedAt } : { source: "ownership.updatedAt", status: "malformed" };
17240
17652
  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
17653
  return [providerEvidence, routeEvidence, ownerEvidence, gitEvidence];
17242
17654
  }
17243
- function buildSuggestions(repoPath, record3, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
17655
+ function buildSuggestions(repoPath, record4, ownership, provider, runtime, checkout, route, activity, integration, worktree, checkMerged) {
17244
17656
  const reasons = [];
17245
17657
  const eligibleActions = [];
17246
17658
  const suggestions = [];
17247
17659
  const gcCommand = workspaceCommand(repoPath, ["gc"], [], ["--yes"]);
17248
- const keepCommand = workspaceCommand(repoPath, ["down", record3.workspace], ["--keep-worktree"]);
17249
- const downCommand = workspaceCommand(repoPath, ["down", record3.workspace]);
17660
+ const keepCommand = workspaceCommand(repoPath, ["down", record4.workspace], ["--keep-worktree"]);
17661
+ const downCommand = workspaceCommand(repoPath, ["down", record4.workspace]);
17250
17662
  const routeSafe = route === "owned" || route === "absent";
17251
17663
  const checkoutSafe = checkout === "clean" && !worktree?.locked;
17252
17664
  const providerSafe = provider === "owned";
@@ -17324,17 +17736,17 @@ function buildSuggestions(repoPath, record3, ownership, provider, runtime, check
17324
17736
  }
17325
17737
  return { eligibleActions, suggestions, reasons };
17326
17738
  }
17327
- function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
17328
- const ownershipEvidence = inspectOwnershipFn(record3, worktrees, devpods);
17739
+ function buildRow(repoPath, record4, worktrees, devpods, routes, snapshot, cutoff, checkMerged, commandRunner, inspectOwnershipFn, inspectIntegrationFn, inspectRuntimeFn) {
17740
+ const ownershipEvidence = inspectOwnershipFn(record4, worktrees, devpods);
17329
17741
  const providerOwnership = devpods?.find(
17330
- (devpod) => devpod.id === record3.devpodId && sameWorkspacePath(devpod.source.localFolder, record3.worktreePath)
17742
+ (devpod) => devpod.id === record4.devpodId && sameWorkspacePath(devpod.source.localFolder, record4.worktreePath)
17331
17743
  );
17332
17744
  const matchingRoutes = routes?.filter(
17333
- (route) => sameWorkspacePath(route.repoPath, record3.worktreePath)
17745
+ (route) => sameWorkspacePath(route.repoPath, record4.worktreePath)
17334
17746
  );
17335
17747
  const activity = evaluateWorkspaceActivity(
17336
17748
  buildActivityEvidence(
17337
- record3,
17749
+ record4,
17338
17750
  ownershipEvidence.devpodStatus,
17339
17751
  providerOwnership,
17340
17752
  matchingRoutes,
@@ -17342,23 +17754,23 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17342
17754
  ),
17343
17755
  cutoff
17344
17756
  );
17345
- const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record3.branch;
17757
+ const branch = snapshot.checkout === "detached" ? null : snapshot.worktree.branch ?? record4.branch;
17346
17758
  const integration = inspectIntegrationFn?.(repoPath, snapshot, branch, checkMerged) ?? inspectWorkspaceIntegration(repoPath, snapshot, branch, checkMerged, commandRunner);
17347
17759
  const runtime = ownershipEvidence.devpodStatus === "absent" ? "absent" : ownershipEvidence.devpodStatus === "owned" ? (() => {
17348
17760
  try {
17349
- return inspectRuntimeFn(record3.devpodId);
17761
+ return inspectRuntimeFn(record4.devpodId);
17350
17762
  } catch {
17351
17763
  return "unknown";
17352
17764
  }
17353
17765
  })() : "unknown";
17354
17766
  const suggestions = buildSuggestions(
17355
17767
  repoPath,
17356
- record3,
17768
+ record4,
17357
17769
  ownershipEvidence.ownerStatus,
17358
17770
  ownershipEvidence.devpodStatus,
17359
17771
  runtime,
17360
17772
  snapshot.checkout,
17361
- routeStatus(record3, routes),
17773
+ routeStatus(record4, routes),
17362
17774
  activity.status,
17363
17775
  integration,
17364
17776
  snapshot.worktree,
@@ -17369,7 +17781,7 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17369
17781
  `provider=${ownershipEvidence.devpodStatus}`,
17370
17782
  `runtime=${runtime}`,
17371
17783
  `checkout=${snapshot.checkout}`,
17372
- `route=${routeStatus(record3, routes)}`,
17784
+ `route=${routeStatus(record4, routes)}`,
17373
17785
  `activity=${activity.status}`,
17374
17786
  `integration=${integration.status}`,
17375
17787
  ...integration.reason ? [integration.reason] : [],
@@ -17377,16 +17789,16 @@ function buildRow(repoPath, record3, worktrees, devpods, routes, snapshot, cutof
17377
17789
  ];
17378
17790
  return {
17379
17791
  schemaVersion: 2,
17380
- workspace: record3.workspace,
17792
+ workspace: record4.workspace,
17381
17793
  branch,
17382
17794
  repo: repoPath,
17383
- worktreePath: record3.worktreePath,
17384
- devpodId: record3.devpodId,
17795
+ worktreePath: record4.worktreePath,
17796
+ devpodId: record4.devpodId,
17385
17797
  ownership: ownershipEvidence.ownerStatus,
17386
17798
  provider: ownershipEvidence.devpodStatus,
17387
17799
  runtime,
17388
17800
  checkout: snapshot.checkout,
17389
- route: routeStatus(record3, routes),
17801
+ route: routeStatus(record4, routes),
17390
17802
  activity: activity.status,
17391
17803
  cutoff,
17392
17804
  latestTimestamp: activity.latestTimestamp,
@@ -17453,30 +17865,30 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17453
17865
  const measureContainersFn = dependencies.measureContainers ?? measureContainerConsumption;
17454
17866
  let containers2 = /* @__PURE__ */ new Map();
17455
17867
  if (measureSize) {
17456
- const worktreePaths = records.map((record3) => record3.worktreePath);
17868
+ const worktreePaths = records.map((record4) => record4.worktreePath);
17457
17869
  try {
17458
17870
  containers2 = measureContainersFn(worktreePaths);
17459
17871
  } catch (error) {
17460
17872
  const reason = `container measurement failed: ${describeCause(error)}`;
17461
- containers2 = new Map(worktreePaths.map((path45) => [path45, unknownContainers(reason)]));
17873
+ containers2 = new Map(worktreePaths.map((path48) => [path48, unknownContainers(reason)]));
17462
17874
  }
17463
17875
  }
17464
- const rows = records.map((record3) => {
17876
+ const rows = records.map((record4) => {
17465
17877
  const worktree = worktrees.find(
17466
- (candidate) => sameWorkspacePath(candidate.path, record3.worktreePath)
17878
+ (candidate) => sameWorkspacePath(candidate.path, record4.worktreePath)
17467
17879
  );
17468
17880
  const snapshot = (dependencies.readGitSnapshot ?? ((candidate) => readGitSnapshot(candidate, commandRunner)))(
17469
17881
  worktree ?? {
17470
- path: record3.worktreePath,
17471
- branch: record3.branch ?? void 0,
17882
+ path: record4.worktreePath,
17883
+ branch: record4.branch ?? void 0,
17472
17884
  locked: false,
17473
17885
  prunable: true
17474
17886
  }
17475
17887
  );
17476
- const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record3.worktreePath);
17888
+ const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record4.worktreePath);
17477
17889
  const row = buildRow(
17478
17890
  repoPath,
17479
- record3,
17891
+ record4,
17480
17892
  worktrees,
17481
17893
  devpods,
17482
17894
  routes,
@@ -17489,11 +17901,11 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17489
17901
  return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
17490
17902
  }),
17491
17903
  dependencies.inspectIntegration,
17492
- dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record3.worktreePath))
17904
+ dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record4.worktreePath))
17493
17905
  );
17494
17906
  return measureSize ? {
17495
17907
  ...row,
17496
- consumption: collectConsumption(record3.worktreePath, measureWorktreeFn, containers2)
17908
+ consumption: collectConsumption(record4.worktreePath, measureWorktreeFn, containers2)
17497
17909
  } : row;
17498
17910
  }).sort((left, right) => left.workspace.localeCompare(right.workspace));
17499
17911
  return {
@@ -17559,7 +17971,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
17559
17971
  );
17560
17972
  }
17561
17973
  function defaultWorktreePath(mainRepo, ws) {
17562
- return import_node_path45.default.join(mainRepo, "trees", ws);
17974
+ return import_node_path48.default.join(mainRepo, "trees", ws);
17563
17975
  }
17564
17976
  function assertDefaultWorktreeRootIgnored(mainRepo) {
17565
17977
  const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -17567,12 +17979,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
17567
17979
  });
17568
17980
  if (ignored.status !== 0) {
17569
17981
  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.`
17982
+ `Default worktree root '${import_node_path48.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path48.default.join(mainRepo, ".gitignore")}' or use --path.`
17571
17983
  );
17572
17984
  }
17573
17985
  }
17574
17986
  function legacyDefaultWorktreePath(mainRepo, ws) {
17575
- return import_node_path45.default.join(import_node_path45.default.dirname(mainRepo), `${import_node_path45.default.basename(mainRepo)}-${ws}`);
17987
+ return import_node_path48.default.join(import_node_path48.default.dirname(mainRepo), `${import_node_path48.default.basename(mainRepo)}-${ws}`);
17576
17988
  }
17577
17989
  function teardownFallbackPath(mainRepo, workspace) {
17578
17990
  const candidates = [
@@ -17625,7 +18037,7 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17625
18037
  }
17626
18038
  const liveRecord = oneRecordMatch(
17627
18039
  target,
17628
- records.filter((record4) => sameWorkspacePath(record4.worktreePath, liveBranchWorktree.path))
18040
+ records.filter((record5) => sameWorkspacePath(record5.worktreePath, liveBranchWorktree.path))
17629
18041
  );
17630
18042
  return {
17631
18043
  workspace: liveRecord?.workspace ?? liveWorkspace,
@@ -17634,13 +18046,13 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17634
18046
  record: liveRecord
17635
18047
  };
17636
18048
  }
17637
- const record3 = records.find((candidate) => candidate.workspace === requestedWorkspace);
17638
- if (record3) {
18049
+ const record4 = records.find((candidate) => candidate.workspace === requestedWorkspace);
18050
+ if (record4) {
17639
18051
  return {
17640
- workspace: record3.workspace,
17641
- worktreePath: record3.worktreePath,
17642
- worktree: worktreeForRecord(worktrees, record3),
17643
- record: record3
18052
+ workspace: record4.workspace,
18053
+ worktreePath: record4.worktreePath,
18054
+ worktree: worktreeForRecord(worktrees, record4),
18055
+ record: record4
17644
18056
  };
17645
18057
  }
17646
18058
  const linked = worktrees.filter((worktree2) => worktree2.workspace !== void 0);
@@ -17661,8 +18073,8 @@ function resolveWorkspaceTarget(mainRepo, target, worktrees, records) {
17661
18073
  record: void 0
17662
18074
  };
17663
18075
  }
17664
- function worktreeForRecord(worktrees, record3) {
17665
- return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record3.worktreePath));
18076
+ function worktreeForRecord(worktrees, record4) {
18077
+ return worktrees.find((candidate) => sameWorkspacePath(candidate.path, record4.worktreePath));
17666
18078
  }
17667
18079
  function assertDevpodTargetSafe(target, worktrees, devpods) {
17668
18080
  if (target.record) {
@@ -17726,7 +18138,7 @@ async function workspaceUp(branch, opts = {}) {
17726
18138
  const generatedPath = candidatePaths.find(
17727
18139
  (candidate) => !import_node_fs41.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
17728
18140
  );
17729
- const selectedPath = opts.path ? import_node_path45.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
18141
+ const selectedPath = opts.path ? import_node_path48.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
17730
18142
  if (!selectedPath) {
17731
18143
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
17732
18144
  }
@@ -17799,26 +18211,26 @@ function workspaceLs(repoPath) {
17799
18211
  const paths = Array.from(
17800
18212
  /* @__PURE__ */ new Set([
17801
18213
  ...worktrees.map((worktree) => worktree.path),
17802
- ...records.map((record3) => record3.worktreePath)
18214
+ ...records.map((record4) => record4.worktreePath)
17803
18215
  ])
17804
18216
  );
17805
18217
  const routesByWorktreePath = listRoutesForWorktreePaths(paths);
17806
18218
  const worktreesByPath = new Map(worktrees.map((worktree) => [worktree.path, worktree]));
17807
- const recordsByPath = new Map(records.map((record3) => [record3.worktreePath, record3]));
18219
+ const recordsByPath = new Map(records.map((record4) => [record4.worktreePath, record4]));
17808
18220
  return paths.map((worktreePath) => {
17809
18221
  const worktree = worktreesByPath.get(worktreePath);
17810
- const record3 = recordsByPath.get(worktreePath);
18222
+ const record4 = recordsByPath.get(worktreePath);
17811
18223
  const devpods = devpodsForPath(worktreePath);
17812
- const status = record3 ? inspectWorkspaceOwnership(record3, worktrees, devpods) : void 0;
18224
+ const status = record4 ? inspectWorkspaceOwnership(record4, worktrees, devpods) : void 0;
17813
18225
  const matchingDevpods = devpods?.filter(
17814
18226
  (devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
17815
18227
  );
17816
18228
  const wsRoutes = routesByWorktreePath.get(worktreePath) ?? [];
17817
18229
  return {
17818
- workspace: record3?.workspace ?? worktree?.workspace,
17819
- branch: worktree?.branch ?? record3?.branch ?? void 0,
18230
+ workspace: record4?.workspace ?? worktree?.workspace,
18231
+ branch: worktree?.branch ?? record4?.branch ?? void 0,
17820
18232
  worktreePath,
17821
- legacy: !record3 && worktree?.workspace !== void 0,
18233
+ legacy: !record4 && worktree?.workspace !== void 0,
17822
18234
  ownerStatus: status?.ownerStatus,
17823
18235
  devpodStatus: status?.devpodStatus ?? (devpods === void 0 ? "unknown" : matchingDevpods?.length === 0 ? "absent" : matchingDevpods?.length === 1 ? "owned" : "conflict"),
17824
18236
  routeCount: wsRoutes.length,
@@ -17902,13 +18314,13 @@ async function workspaceStop(target, opts = {}) {
17902
18314
  async function workspaceDown(target, opts = {}) {
17903
18315
  return runWorkspaceLifecycle("down", target, opts);
17904
18316
  }
17905
- var import_node_child_process30, import_node_fs41, import_node_path45, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
18317
+ var import_node_child_process30, import_node_fs41, import_node_path48, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
17906
18318
  var init_workspace_lifecycle = __esm({
17907
18319
  "src/core/workspace-lifecycle.ts"() {
17908
18320
  "use strict";
17909
18321
  import_node_child_process30 = require("child_process");
17910
18322
  import_node_fs41 = __toESM(require("fs"));
17911
- import_node_path45 = __toESM(require("path"));
18323
+ import_node_path48 = __toESM(require("path"));
17912
18324
  init_devpod_mutation();
17913
18325
  init_devpod_workspaces();
17914
18326
  init_managed_runtime_state();
@@ -18067,7 +18479,7 @@ var init_version = __esm({
18067
18479
 
18068
18480
  // src/cli.ts
18069
18481
  var import_commander = require("commander");
18070
- var CLI_VERSION = true ? "0.0.60" : "0.0.0-dev";
18482
+ var CLI_VERSION = true ? "0.0.61" : "0.0.0-dev";
18071
18483
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
18072
18484
  function withErrorHandling(action2) {
18073
18485
  return async (...args) => {