@devrouter/cli 0.0.65 → 0.0.67

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
@@ -2520,7 +2520,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs6.default.read
2520
2520
  const config = parseConfig(parsed ?? {}, configPath);
2521
2521
  const requiredVersion = config.devrouter?.version;
2522
2522
  if (requiredVersion && !hasWarnedVersionMismatch) {
2523
- const cliVersion = true ? "0.0.65" : "0.0.0-dev";
2523
+ const cliVersion = true ? "0.0.67" : "0.0.0-dev";
2524
2524
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
2525
2525
  hasWarnedVersionMismatch = true;
2526
2526
  process.stderr.write(
@@ -3494,6 +3494,19 @@ var init_reliability_contract = __esm({
3494
3494
  });
3495
3495
 
3496
3496
  // src/core/reliability-operation-store.ts
3497
+ function installedCliVersion() {
3498
+ return true ? "0.0.67" : "0.0.0-dev";
3499
+ }
3500
+ function compareCliVersions(left, right) {
3501
+ const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
3502
+ const leftParts = parts(left);
3503
+ const rightParts = parts(right);
3504
+ for (let index = 0; index < 3; index += 1) {
3505
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
3506
+ if (difference !== 0) return difference;
3507
+ }
3508
+ return 0;
3509
+ }
3497
3510
  function identityKey(identity2) {
3498
3511
  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)) {
3499
3512
  throw new Error("Invalid reliability workspace identity.");
@@ -3509,7 +3522,21 @@ function keys(value, expected) {
3509
3522
  }
3510
3523
  }
3511
3524
  function validate(record5, identity2) {
3512
- keys(record5, ["version", "identity", "revision", "state", "worker", "effectSequence", "outcome"]);
3525
+ keys(record5, [
3526
+ "version",
3527
+ "identity",
3528
+ "revision",
3529
+ "state",
3530
+ "worker",
3531
+ "effectSequence",
3532
+ "outcome",
3533
+ "writtenByVersion"
3534
+ ]);
3535
+ if (record5.writtenByVersion !== void 0 && compareCliVersions(record5.writtenByVersion, installedCliVersion()) > 0) {
3536
+ throw new Error(
3537
+ `Reliability record was last written by devrouter ${record5.writtenByVersion}, which is newer than the installed ${installedCliVersion()}; upgrade devrouter (devrouter upgrade) before running lifecycle commands.`
3538
+ );
3539
+ }
3513
3540
  keys(record5.identity, ["repoPath", "workspace", "provider"]);
3514
3541
  if (record5.version !== 1 || record5.identity.repoPath !== identity2.repoPath || record5.identity.workspace !== identity2.workspace || record5.identity.provider !== identity2.provider || !isReliabilityCounter(record5.revision) || !isReliabilityCounter(record5.effectSequence)) {
3515
3542
  throw new Error("Reliability record has invalid version, ownership, or counters.");
@@ -3650,6 +3677,7 @@ function updateReliabilityOperation(identity2, operation) {
3650
3677
  if (result && typeof result.then === "function") {
3651
3678
  throw new Error("Reliability record updates must be synchronous.");
3652
3679
  }
3680
+ record5.writtenByVersion = installedCliVersion();
3653
3681
  validate(record5, identity2);
3654
3682
  record5.revision += 1;
3655
3683
  persist(record5);
@@ -7462,6 +7490,10 @@ var init_ai_prompt = __esm({
7462
7490
  {
7463
7491
  command: "devrouter workspace gc [--json] [--yes]",
7464
7492
  purpose: "Report missing owners without mutation by default; --yes deletes only exact eligible ledger-owned resources and records, never Git worktrees."
7493
+ },
7494
+ {
7495
+ command: "devrouter workspace journal settle [path] [--json]",
7496
+ purpose: "Settle a lifecycle operation whose worker is provably gone as unobservable so ensure/stop can proceed; never hand-edit the devrouter state directory."
7465
7497
  }
7466
7498
  ];
7467
7499
  }
@@ -11467,6 +11499,17 @@ function inspectDevsyRuntimeStatus(devsyId) {
11467
11499
  if (result.status !== 0 || result.error) return "unknown";
11468
11500
  return parseDevsyRuntimeStatus(result.stdout, devsyId);
11469
11501
  }
11502
+ function inspectDevsyRuntimeAbsence(devsyId) {
11503
+ const result = (0, import_node_child_process16.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
11504
+ encoding: "utf-8",
11505
+ timeout: 1e4
11506
+ });
11507
+ if (!result.error && result.status === 0) {
11508
+ return parseDevsyRuntimeStatus(result.stdout, devsyId) === "not-found";
11509
+ }
11510
+ const output2 = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n");
11511
+ return /workspace not found/i.test(output2);
11512
+ }
11470
11513
  function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
11471
11514
  const idOwners = workspaces.filter((workspace) => workspace.id === devsyId);
11472
11515
  const pathOwners = workspaces.filter(
@@ -12140,6 +12183,96 @@ var init_managed_compose_population = __esm({
12140
12183
  }
12141
12184
  });
12142
12185
 
12186
+ // src/core/devsy-exec-proof.ts
12187
+ function proveLocalDockerSelection(workspace) {
12188
+ const name = workspace.providerName;
12189
+ const context = workspace.context;
12190
+ if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
12191
+ throw new Error("Retained exec provider configuration identity is unavailable.");
12192
+ const file = import_node_path33.default.join(
12193
+ process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os2.default.homedir(), ".devsy"),
12194
+ "contexts",
12195
+ context,
12196
+ "providers",
12197
+ name,
12198
+ "provider.json"
12199
+ );
12200
+ try {
12201
+ if (import_node_fs29.default.statSync(file).size > 1024 * 1024) throw new Error();
12202
+ const config = JSON.parse(import_node_fs29.default.readFileSync(file, "utf8"));
12203
+ const driver = config.agent?.driver ?? "docker";
12204
+ const configuredPath = config.agent?.docker?.path ?? "";
12205
+ if (typeof configuredPath !== "string") throw new Error();
12206
+ const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
12207
+ if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
12208
+ } catch {
12209
+ throw new Error("Retained exec requires a provider configured for the local Docker command.");
12210
+ }
12211
+ return name;
12212
+ }
12213
+ function captureDevsyExecProof(repoPath) {
12214
+ repoPath = comparableWorkspacePath(repoPath);
12215
+ const registry = listDevsyWorkspaces();
12216
+ const workspace = selectDevsyWorkspace(registry, repoPath);
12217
+ if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
12218
+ throw new Error("Retained exec requires one exact running Devsy registration.");
12219
+ const providerName = proveLocalDockerSelection(workspace);
12220
+ const endpoint = resolveManagedStopEndpoint();
12221
+ if (!supportsManagedStopBaseline(endpoint))
12222
+ throw new Error("Retained exec requires an exact local Docker endpoint.");
12223
+ const daemon = inspectManagedStopDaemon(endpoint);
12224
+ const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
12225
+ (container) => container.state.Running
12226
+ );
12227
+ if (candidates.length !== 1)
12228
+ throw new Error("Retained exec requires one exact running primary container.");
12229
+ const candidate = candidates[0];
12230
+ const project = candidate.labels["com.docker.compose.project"];
12231
+ if (!project) throw new Error("Retained exec primary has no Compose identity.");
12232
+ const pinned = workspaceAppContainers(
12233
+ inspectManagedStopContainers(project, endpoint),
12234
+ repoPath
12235
+ ).filter((container) => container.state.Running);
12236
+ if (pinned.length !== 1 || pinned[0].id !== candidate.id)
12237
+ throw new Error("Retained exec primary changed during inspection.");
12238
+ const mounts = pinned[0].mounts.filter(
12239
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
12240
+ );
12241
+ if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
12242
+ throw new Error("Retained exec requires one exact source mount.");
12243
+ const uidBytes = Buffer.byteLength(workspace.uid);
12244
+ const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
12245
+ const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
12246
+ if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
12247
+ throw new Error("Retained exec runner does not select the exact primary container.");
12248
+ if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
12249
+ throw new Error("Retained exec provider or Docker identity changed.");
12250
+ return {
12251
+ repoPath,
12252
+ id: workspace.id,
12253
+ uid: workspace.uid,
12254
+ context: workspace.context,
12255
+ providerName,
12256
+ endpoint,
12257
+ daemon,
12258
+ containerId: candidate.id,
12259
+ workspacePath: mounts[0].Destination
12260
+ };
12261
+ }
12262
+ var import_node_fs29, import_node_os2, import_node_path33, import_node_util2;
12263
+ var init_devsy_exec_proof = __esm({
12264
+ "src/core/devsy-exec-proof.ts"() {
12265
+ "use strict";
12266
+ import_node_fs29 = __toESM(require("fs"));
12267
+ import_node_os2 = __toESM(require("os"));
12268
+ import_node_path33 = __toESM(require("path"));
12269
+ import_node_util2 = require("util");
12270
+ init_devpod_environment();
12271
+ init_devsy_workspaces();
12272
+ init_workspace();
12273
+ }
12274
+ });
12275
+
12143
12276
  // src/core/reliability-context.ts
12144
12277
  function claimLifecycleEffect() {
12145
12278
  claim?.();
@@ -12168,7 +12301,7 @@ function resolveGitCommonDir(repoPath) {
12168
12301
  if (result.status !== 0 || !output2) {
12169
12302
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
12170
12303
  }
12171
- return comparableWorkspacePath(import_node_path33.default.isAbsolute(output2) ? output2 : import_node_path33.default.resolve(repoPath, output2));
12304
+ return comparableWorkspacePath(import_node_path34.default.isAbsolute(output2) ? output2 : import_node_path34.default.resolve(repoPath, output2));
12172
12305
  }
12173
12306
  function resolveGitTopLevel(repoPath) {
12174
12307
  const result = (0, import_node_child_process20.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -12219,7 +12352,7 @@ function listGitWorktrees(repoPath) {
12219
12352
  return worktrees;
12220
12353
  }
12221
12354
  function ownershipDirectory(repoPath) {
12222
- return import_node_path33.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
12355
+ return import_node_path34.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
12223
12356
  }
12224
12357
  function validateWorkspace(value, label) {
12225
12358
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -12247,7 +12380,7 @@ function validateRecord(value, expectedWorkspace) {
12247
12380
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
12248
12381
  );
12249
12382
  }
12250
- if (typeof candidate.worktreePath !== "string" || !import_node_path33.default.isAbsolute(candidate.worktreePath)) {
12383
+ if (typeof candidate.worktreePath !== "string" || !import_node_path34.default.isAbsolute(candidate.worktreePath)) {
12251
12384
  throw new Error("invalid workspace ownership worktreePath");
12252
12385
  }
12253
12386
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -12265,7 +12398,7 @@ function validateRecord(value, expectedWorkspace) {
12265
12398
  };
12266
12399
  }
12267
12400
  function recordPath(repoPath, workspace) {
12268
- return import_node_path33.default.join(
12401
+ return import_node_path34.default.join(
12269
12402
  ownershipDirectory(repoPath),
12270
12403
  `${validateWorkspace(workspace, "workspace")}.json`
12271
12404
  );
@@ -12273,7 +12406,7 @@ function recordPath(repoPath, workspace) {
12273
12406
  function readRecordFile(filePath, expectedWorkspace) {
12274
12407
  let parsed;
12275
12408
  try {
12276
- parsed = JSON.parse(import_node_fs29.default.readFileSync(filePath, "utf-8"));
12409
+ parsed = JSON.parse(import_node_fs30.default.readFileSync(filePath, "utf-8"));
12277
12410
  } catch (error) {
12278
12411
  if (error instanceof SyntaxError) {
12279
12412
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -12298,7 +12431,7 @@ function listWorkspaceOwnership(repoPath) {
12298
12431
  function listWorkspaceOwnershipInDirectory(directory) {
12299
12432
  let entries;
12300
12433
  try {
12301
- entries = import_node_fs29.default.readdirSync(directory, { withFileTypes: true });
12434
+ entries = import_node_fs30.default.readdirSync(directory, { withFileTypes: true });
12302
12435
  } catch (error) {
12303
12436
  if (error.code === "ENOENT") return [];
12304
12437
  throw error;
@@ -12306,14 +12439,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
12306
12439
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
12307
12440
  const workspace = entry.name.slice(0, -".json".length);
12308
12441
  validateWorkspace(workspace, "filename");
12309
- return readRecordFile(import_node_path33.default.join(directory, entry.name), workspace);
12442
+ return readRecordFile(import_node_path34.default.join(directory, entry.name), workspace);
12310
12443
  });
12311
12444
  }
12312
12445
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
12313
12446
  const workspace = validateWorkspace(input2.workspace, "workspace");
12314
12447
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
12315
12448
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
12316
- const filePath = import_node_path33.default.join(directory, `${workspace}.json`);
12449
+ const filePath = import_node_path34.default.join(directory, `${workspace}.json`);
12317
12450
  const now = (/* @__PURE__ */ new Date()).toISOString();
12318
12451
  const records = listWorkspaceOwnershipInDirectory(directory);
12319
12452
  const existing = records.find((record6) => record6.workspace === workspace);
@@ -12349,9 +12482,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
12349
12482
  return record5;
12350
12483
  }
12351
12484
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
12352
- const filePath = import_node_path33.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
12485
+ const filePath = import_node_path34.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
12353
12486
  try {
12354
- import_node_fs29.default.rmSync(filePath);
12487
+ import_node_fs30.default.rmSync(filePath);
12355
12488
  return true;
12356
12489
  } catch (error) {
12357
12490
  if (error.code === "ENOENT") return false;
@@ -12362,7 +12495,7 @@ function sameOwnershipRecord(left, right) {
12362
12495
  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;
12363
12496
  }
12364
12497
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
12365
- const filePath = import_node_path33.default.join(
12498
+ const filePath = import_node_path34.default.join(
12366
12499
  directory,
12367
12500
  `${validateWorkspace(expected.workspace, "workspace")}.json`
12368
12501
  );
@@ -12374,14 +12507,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
12374
12507
  throw error;
12375
12508
  }
12376
12509
  if (!sameOwnershipRecord(current, expected)) return "changed";
12377
- import_node_fs29.default.rmSync(filePath);
12510
+ import_node_fs30.default.rmSync(filePath);
12378
12511
  return "removed";
12379
12512
  }
12380
12513
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
12381
12514
  const directory = ownershipDirectory(repoPath);
12382
- import_node_fs29.default.mkdirSync(directory, { recursive: true });
12515
+ import_node_fs30.default.mkdirSync(directory, { recursive: true });
12383
12516
  return withFileLockSync(
12384
- import_node_path33.default.join(directory, ".lock"),
12517
+ import_node_path34.default.join(directory, ".lock"),
12385
12518
  {
12386
12519
  activity: "workspace ownership transaction",
12387
12520
  target: `'${repoPath}'`,
@@ -12414,7 +12547,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
12414
12547
  try {
12415
12548
  workspace = readPersistedWorkspace(worktree.path);
12416
12549
  } catch (error) {
12417
- if (!import_node_fs29.default.existsSync(worktree.path)) continue;
12550
+ if (!import_node_fs30.default.existsSync(worktree.path)) continue;
12418
12551
  throw error;
12419
12552
  }
12420
12553
  if (!workspace) continue;
@@ -12577,7 +12710,7 @@ function inspectWorkspaceOwnership(record5, worktrees, devpods) {
12577
12710
  if (worktree?.locked) {
12578
12711
  return { ownerStatus: "locked", devpodStatus, worktree };
12579
12712
  }
12580
- if ((!worktree || worktree.prunable) && import_node_fs29.default.existsSync(record5.worktreePath)) {
12713
+ if ((!worktree || worktree.prunable) && import_node_fs30.default.existsSync(record5.worktreePath)) {
12581
12714
  return { ownerStatus: "conflict", devpodStatus, worktree };
12582
12715
  }
12583
12716
  if (!worktree || worktree.prunable) {
@@ -12601,20 +12734,20 @@ function listMissingWorkspaceOwnership(repoPath) {
12601
12734
  (record5) => inspectWorkspaceOwnership(record5, worktrees, void 0).ownerStatus === "missing"
12602
12735
  );
12603
12736
  }
12604
- var import_node_child_process20, import_node_fs29, import_node_path33, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
12737
+ var import_node_child_process20, import_node_fs30, import_node_path34, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
12605
12738
  var init_workspace_ownership = __esm({
12606
12739
  "src/core/workspace-ownership.ts"() {
12607
12740
  "use strict";
12608
12741
  import_node_child_process20 = require("child_process");
12609
- import_node_fs29 = __toESM(require("fs"));
12610
- import_node_path33 = __toESM(require("path"));
12742
+ import_node_fs30 = __toESM(require("fs"));
12743
+ import_node_path34 = __toESM(require("path"));
12611
12744
  init_atomic_file();
12612
12745
  init_devpod_workspaces();
12613
12746
  init_file_lock();
12614
12747
  init_workspace();
12615
12748
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
12616
12749
  OWNERSHIP_VERSION = 1;
12617
- OWNERSHIP_DIR = import_node_path33.default.join("devrouter", "workspaces");
12750
+ OWNERSHIP_DIR = import_node_path34.default.join("devrouter", "workspaces");
12618
12751
  }
12619
12752
  });
12620
12753
 
@@ -12636,7 +12769,16 @@ function registration(state) {
12636
12769
  throw new Error("Stop workspace identity changed.");
12637
12770
  resolveGitCommonDir(state.repoPath);
12638
12771
  } else if (state.workspace !== void 0) throw new Error("Stop checkout identity changed.");
12639
- const value = owner.workspace;
12772
+ return identityOf(owner.workspace);
12773
+ }
12774
+ function managedRunnerId(uid, providerId) {
12775
+ if (uid === void 0) {
12776
+ return providerId;
12777
+ }
12778
+ const bytes = Buffer.byteLength(uid);
12779
+ return bytes === 16 || bytes === 40 ? uid : providerId;
12780
+ }
12781
+ function identityOf(value) {
12640
12782
  return {
12641
12783
  context: value.context ?? "",
12642
12784
  providerId: value.id,
@@ -12671,7 +12813,7 @@ function prove(state, baseline) {
12671
12813
  const containers2 = inspectManagedStopContainers(baseline.project, baseline.endpoint);
12672
12814
  const expected = baseline.containers.map((container) => ({ ...container, mounts: orderedMounts(container.mounts) })).sort((a, b) => compareText(a.id, b.id));
12673
12815
  const observed = containers2.map(identity).sort((a, b) => compareText(a.id, b.id));
12674
- if (!(0, import_node_util2.isDeepStrictEqual)(expected, observed) || containers2.some(
12816
+ if (!(0, import_node_util3.isDeepStrictEqual)(expected, observed) || containers2.some(
12675
12817
  (container) => container.labels["com.docker.compose.project.working_dir"] !== baseline.composeDirectory
12676
12818
  )) {
12677
12819
  throw new Error("Retained container population or immutable identity changed.");
@@ -12680,7 +12822,7 @@ function prove(state, baseline) {
12680
12822
  baseline.endpoint,
12681
12823
  baseline.composeDirectory
12682
12824
  ).sort();
12683
- if (!(0, import_node_util2.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
12825
+ if (!(0, import_node_util3.isDeepStrictEqual)(workspaceIds, expected.map((container) => container.id).sort())) {
12684
12826
  throw new Error("Workspace contains an unrecorded container population.");
12685
12827
  }
12686
12828
  const primary = baseline.containers.find(
@@ -12692,18 +12834,18 @@ function prove(state, baseline) {
12692
12834
  if (!baseline.sourceContainer && inspectManagedStopRunnerId(baseline.endpoint, primary.id) !== runnerId) {
12693
12835
  throw new Error("Provider-to-container binding changed.");
12694
12836
  }
12695
- if (!(0, import_node_util2.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
12837
+ if (!(0, import_node_util3.isDeepStrictEqual)(registration(state), owner) || inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId) {
12696
12838
  throw new Error("Stop authority changed during inspection.");
12697
12839
  }
12698
12840
  return containers2;
12699
12841
  }
12700
12842
  function proveRetainedManagedStop(state) {
12701
12843
  const baseline = validateManagedStopBaseline(state.stopBaseline, state);
12702
- if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
12844
+ if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
12703
12845
  throw new Error("Retained stop generation changed.");
12704
12846
  }
12705
12847
  const result = prove(state, baseline);
12706
- if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
12848
+ if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state)) {
12707
12849
  throw new Error("Retained stop generation changed during inspection.");
12708
12850
  }
12709
12851
  return result;
@@ -12728,6 +12870,64 @@ function absentRegistrationIdentity(state) {
12728
12870
  }
12729
12871
  return { record: record5, gitCommonDir: resolveGitCommonDir(state.repoPath) };
12730
12872
  }
12873
+ function replacementObservation(state) {
12874
+ resetWorkspaceRuntimeCaches();
12875
+ if (resolveWorkspaceRuntimeOrDefault(state.repoPath) !== "devsy")
12876
+ throw new Error("Stop provider changed.");
12877
+ if (!state.workspace || !isLinkedWorktree(state.repoPath))
12878
+ throw new Error("Replacement stop requires a linked workspace.");
12879
+ const record5 = readWorkspaceOwnership(state.repoPath, state.workspace);
12880
+ if (!record5 || record5.workspace !== state.workspace || record5.devpodId !== state.devpodId || !sameWorkspacePath(record5.worktreePath, state.repoPath) || resolveWorktreeWorkspace(state.repoPath) !== state.workspace || inspectWorkspaceOwnership(record5, listGitWorktrees(state.repoPath), void 0).ownerStatus !== "present")
12881
+ throw new Error("Replacement stop workspace ownership changed.");
12882
+ const owner = inspectDevsyWorkspaceOwnership(
12883
+ listDevsyWorkspaces(),
12884
+ state.devpodId,
12885
+ state.repoPath
12886
+ );
12887
+ if (owner.status !== "owned")
12888
+ throw new Error("Replacement stop requires one exact provider registration.");
12889
+ if (listDevpodWorkspacesRaw({ allowMissingExecutable: true }).some(
12890
+ (entry) => entry.id === state.devpodId || sameWorkspacePath(entry.source.localFolder, state.repoPath)
12891
+ ))
12892
+ throw new Error("Replacement stop requires no competing provider registration.");
12893
+ proveLocalDockerSelection(owner.workspace);
12894
+ return owner.workspace;
12895
+ }
12896
+ function proveReplacementStopAbsence(state, baseline, replacement) {
12897
+ const observation = () => {
12898
+ const workspace = replacementObservation(state);
12899
+ if (workspace.uid !== replacement.uid)
12900
+ throw new Error("Replacement stop registration changed during inspection.");
12901
+ if (resolveManagedStopEndpoint() !== baseline.endpoint)
12902
+ throw new Error("Stop endpoint changed.");
12903
+ if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
12904
+ throw new Error("Stop daemon changed.");
12905
+ return workspace;
12906
+ };
12907
+ const before = observation();
12908
+ if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12909
+ throw new Error("Retained stop generation changed.");
12910
+ assertManagedStopContainersAbsent(
12911
+ baseline.endpoint,
12912
+ baseline.containers.map((container) => container.id)
12913
+ );
12914
+ const oldRunner = managedRunnerId(baseline.uid, baseline.providerId);
12915
+ const newRunner = managedRunnerId(before.uid, baseline.providerId);
12916
+ const runners = oldRunner === newRunner ? [oldRunner] : [oldRunner, newRunner];
12917
+ const assertEmptyPopulation = () => {
12918
+ if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || runners.some((id4) => inspectProviderRunnerContainers(baseline.endpoint, id4).length !== 0))
12919
+ throw new Error("Replacement stop observed a remaining workspace population.");
12920
+ };
12921
+ assertEmptyPopulation();
12922
+ const after = observation();
12923
+ if (after.uid !== before.uid || !(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12924
+ throw new Error("Replacement stop authority changed during inspection.");
12925
+ assertManagedStopContainersAbsent(
12926
+ baseline.endpoint,
12927
+ baseline.containers.map((container) => container.id)
12928
+ );
12929
+ assertEmptyPopulation();
12930
+ }
12731
12931
  function proveManagedStop(state) {
12732
12932
  const baseline = validateManagedStopBaseline(state.stopBaseline, state);
12733
12933
  const owner = inspectDevsyWorkspaceOwnership(
@@ -12735,11 +12935,20 @@ function proveManagedStop(state) {
12735
12935
  state.devpodId,
12736
12936
  state.repoPath
12737
12937
  );
12938
+ if (owner.status === "owned") {
12939
+ const identityKeys = ["context", "providerId", "uid", "sourcePath", "sourceContainer"];
12940
+ const current = identityOf(owner.workspace);
12941
+ const changed = identityKeys.filter((key) => baseline[key] !== current[key]);
12942
+ if (changed.length === 1 && changed[0] === "uid" && !baseline.sourceContainer && !current.sourceContainer && state.workspace !== void 0 && isLinkedWorktree(state.repoPath)) {
12943
+ proveReplacementStopAbsence(state, baseline, { uid: current.uid || void 0 });
12944
+ return { status: "proven-absent", containers: [] };
12945
+ }
12946
+ }
12738
12947
  if (owner.status !== "absent")
12739
12948
  return { status: "retained", containers: proveRetainedManagedStop(state) };
12740
12949
  if (baseline.sourceContainer) throw new Error("Absent stop cannot prove a source container.");
12741
12950
  const before = absentRegistrationIdentity(state);
12742
- if (!(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12951
+ if (!(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12743
12952
  throw new Error("Retained stop generation changed.");
12744
12953
  if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId)
12745
12954
  throw new Error("Stop daemon changed.");
@@ -12751,7 +12960,7 @@ function proveManagedStop(state) {
12751
12960
  const runnerId = uidBytes === 16 || uidBytes === 40 ? baseline.uid : baseline.providerId;
12752
12961
  if (inspectManagedStopContainers(baseline.project, baseline.endpoint).length !== 0 || inspectManagedStopWorkspaceIds(baseline.endpoint, baseline.composeDirectory).length !== 0 || inspectProviderRunnerContainers(baseline.endpoint, runnerId).length !== 0)
12753
12962
  throw new Error("Absent stop observed a remaining workspace population.");
12754
- if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId || !(0, import_node_util2.isDeepStrictEqual)(absentRegistrationIdentity(state), before) || !(0, import_node_util2.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12963
+ if (inspectManagedStopDaemon(baseline.endpoint) !== baseline.daemonId || !(0, import_node_util3.isDeepStrictEqual)(absentRegistrationIdentity(state), before) || !(0, import_node_util3.isDeepStrictEqual)(readManagedRuntimeState(state.repoPath, state.workspace), state))
12755
12964
  throw new Error("Absent stop authority changed during inspection.");
12756
12965
  return { status: "proven-absent", containers: [] };
12757
12966
  }
@@ -12787,15 +12996,16 @@ function managedStopRouteReferences(state) {
12787
12996
  (name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
12788
12997
  );
12789
12998
  }
12790
- var import_node_os2, import_node_path34, import_node_util2;
12999
+ var import_node_os3, import_node_path35, import_node_util3;
12791
13000
  var init_managed_stop_recovery = __esm({
12792
13001
  "src/core/managed-stop-recovery.ts"() {
12793
13002
  "use strict";
12794
- import_node_os2 = __toESM(require("os"));
12795
- import_node_path34 = __toESM(require("path"));
12796
- import_node_util2 = require("util");
13003
+ import_node_os3 = __toESM(require("os"));
13004
+ import_node_path35 = __toESM(require("path"));
13005
+ import_node_util3 = require("util");
12797
13006
  init_devpod_environment();
12798
13007
  init_devpod_registry();
13008
+ init_devsy_exec_proof();
12799
13009
  init_devsy_workspaces();
12800
13010
  init_managed_compose_population();
12801
13011
  init_managed_runtime_state();
@@ -12925,12 +13135,72 @@ function stopRetainedManagedDevsyWorkspace(options) {
12925
13135
  }
12926
13136
  return owner.workspace;
12927
13137
  }
13138
+ function registrationStatus() {
13139
+ resetWorkspaceRuntimeCaches();
13140
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy") {
13141
+ throw new Error("Managed stop provider selection changed.");
13142
+ }
13143
+ return inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
13144
+ }
13145
+ function proveAbsentRegistrationStop() {
13146
+ const observe = () => {
13147
+ const owner = registrationStatus();
13148
+ if (owner.status === "conflict") throw new Error(owner.reason);
13149
+ if (owner.status !== "absent") {
13150
+ throw new Error("Managed stop absence proof requires the registration to remain absent.");
13151
+ }
13152
+ if (listDevpodWorkspacesRaw({ allowMissingExecutable: true }).some(
13153
+ (entry) => entry.id === devsyId || sameWorkspacePath(entry.source.localFolder, repoPath)
13154
+ )) {
13155
+ throw new Error(
13156
+ "Managed stop absence proof requires both provider registrations to remain absent."
13157
+ );
13158
+ }
13159
+ if (!inspectDevsyRuntimeAbsence(devsyId)) {
13160
+ throw new Error(
13161
+ "Managed stop absence proof requires Devsy to report the runtime not-found."
13162
+ );
13163
+ }
13164
+ if (inspectManagedStopContainers(state.composeProject).length !== 0) {
13165
+ throw new Error("Managed stop absence proof observed a remaining compose population.");
13166
+ }
13167
+ if (inspectProviderRunnerContainers(resolveManagedStopEndpoint(), devsyId).length !== 0) {
13168
+ throw new Error("Managed stop absence proof observed a remaining runner population.");
13169
+ }
13170
+ const owned = inspectWorkspaceContainers().filter(
13171
+ (container) => sameWorkspacePath(
13172
+ container.labels["com.docker.compose.project.working_dir"] ?? "",
13173
+ import_node_path36.default.join(repoPath, ".devcontainer")
13174
+ )
13175
+ );
13176
+ if (owned.length !== 0) {
13177
+ throw new Error("Managed stop absence proof observed a remaining workspace container.");
13178
+ }
13179
+ if (workspace) {
13180
+ const record5 = readWorkspaceOwnership(repoPath, workspace);
13181
+ if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== workspaceEnv?.gitCommonDir || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present") {
13182
+ throw new Error("Managed stop workspace ownership changed during absence proof.");
13183
+ }
13184
+ }
13185
+ if (!(0, import_node_util4.isDeepStrictEqual)(readManagedRuntimeState(repoPath, workspace), state)) {
13186
+ throw new Error("Managed stop retained state changed during absence proof.");
13187
+ }
13188
+ };
13189
+ observe();
13190
+ observe();
13191
+ }
13192
+ const initialOwner = registrationStatus();
13193
+ if (initialOwner.status === "conflict") throw new Error(initialOwner.reason);
13194
+ if (initialOwner.status === "absent") {
13195
+ proveAbsentRegistrationStop();
13196
+ return "proven-absent";
13197
+ }
12928
13198
  const context = registration2().context;
12929
13199
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
12930
13200
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
12931
13201
  }
12932
- const devsyRoot = import_node_path35.default.resolve(process.env.DEVSY_HOME || import_node_path35.default.join(import_node_os3.default.homedir(), ".devsy"));
12933
- const featureDirectory = import_node_path35.default.join(
13202
+ const devsyRoot = import_node_path36.default.resolve(process.env.DEVSY_HOME || import_node_path36.default.join(import_node_os4.default.homedir(), ".devsy"));
13203
+ const featureDirectory = import_node_path36.default.join(
12934
13204
  devsyRoot,
12935
13205
  "contexts",
12936
13206
  context,
@@ -13034,14 +13304,16 @@ function stopRetainedManagedDevsyWorkspace(options) {
13034
13304
  if (providerFailed) throw providerError;
13035
13305
  return true;
13036
13306
  }
13037
- var import_node_os3, import_node_path35;
13307
+ var import_node_os4, import_node_path36, import_node_util4;
13038
13308
  var init_managed_devsy_stop = __esm({
13039
13309
  "src/core/managed-devsy-stop.ts"() {
13040
13310
  "use strict";
13041
- import_node_os3 = __toESM(require("os"));
13042
- import_node_path35 = __toESM(require("path"));
13311
+ import_node_os4 = __toESM(require("os"));
13312
+ import_node_path36 = __toESM(require("path"));
13313
+ import_node_util4 = require("util");
13043
13314
  init_devcontainer_profile();
13044
13315
  init_devpod_environment();
13316
+ init_devpod_registry();
13045
13317
  init_devsy_workspaces();
13046
13318
  init_managed_compose_population();
13047
13319
  init_managed_runtime_state();
@@ -13082,14 +13354,14 @@ var init_network_provider_binding = __esm({
13082
13354
 
13083
13355
  // src/core/network-claim-lookup.ts
13084
13356
  function networkOwnerKey(repoPath) {
13085
- if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
13357
+ if (!import_node_path37.default.isAbsolute(repoPath) || import_node_path37.default.resolve(repoPath) !== repoPath)
13086
13358
  throw new Error("Invalid network owner path.");
13087
13359
  return (0, import_node_crypto18.createHash)("sha256").update(repoPath).digest("hex");
13088
13360
  }
13089
13361
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13090
13362
  let names;
13091
13363
  try {
13092
- names = import_node_fs30.default.readdirSync(root);
13364
+ names = import_node_fs31.default.readdirSync(root);
13093
13365
  } catch (error) {
13094
13366
  if (error.code === "ENOENT") return void 0;
13095
13367
  throw new Error("Network claims cannot be inspected; repair is required.");
@@ -13098,21 +13370,21 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13098
13370
  if (files.length > 128) throw new Error("Network daemon inventory exceeds its bound.");
13099
13371
  let found;
13100
13372
  for (const file of files) {
13101
- const full = import_node_path36.default.join(root, file);
13102
- const stat = import_node_fs30.default.lstatSync(full);
13373
+ const full = import_node_path37.default.join(root, file);
13374
+ const stat = import_node_fs31.default.lstatSync(full);
13103
13375
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
13104
13376
  throw new Error("Invalid retained network claim file.");
13105
13377
  let value;
13106
- const descriptor = import_node_fs30.default.openSync(
13378
+ const descriptor = import_node_fs31.default.openSync(
13107
13379
  full,
13108
- import_node_fs30.default.constants.O_RDONLY | import_node_fs30.default.constants.O_NONBLOCK | import_node_fs30.default.constants.O_NOFOLLOW
13380
+ import_node_fs31.default.constants.O_RDONLY | import_node_fs31.default.constants.O_NONBLOCK | import_node_fs31.default.constants.O_NOFOLLOW
13109
13381
  );
13110
13382
  try {
13111
- if (!import_node_fs30.default.fstatSync(descriptor).isFile()) throw new Error();
13383
+ if (!import_node_fs31.default.fstatSync(descriptor).isFile()) throw new Error();
13112
13384
  const bytes = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
13113
13385
  let size = 0;
13114
13386
  while (size < bytes.length) {
13115
- const count = import_node_fs30.default.readSync(descriptor, bytes, size, bytes.length - size, null);
13387
+ const count = import_node_fs31.default.readSync(descriptor, bytes, size, bytes.length - size, null);
13116
13388
  if (!count) break;
13117
13389
  size += count;
13118
13390
  }
@@ -13122,7 +13394,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13122
13394
  } catch {
13123
13395
  throw new Error("Retained network claim could not be read safely.");
13124
13396
  } finally {
13125
- import_node_fs30.default.closeSync(descriptor);
13397
+ import_node_fs31.default.closeSync(descriptor);
13126
13398
  }
13127
13399
  if (typeof value.daemonId !== "string" || `${(0, import_node_crypto18.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
13128
13400
  throw new Error("Retained network daemon identity is invalid.");
@@ -13138,13 +13410,13 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
13138
13410
  }
13139
13411
  return found;
13140
13412
  }
13141
- var import_node_crypto18, import_node_fs30, import_node_path36;
13413
+ var import_node_crypto18, import_node_fs31, import_node_path37;
13142
13414
  var init_network_claim_lookup = __esm({
13143
13415
  "src/core/network-claim-lookup.ts"() {
13144
13416
  "use strict";
13145
13417
  import_node_crypto18 = require("crypto");
13146
- import_node_fs30 = __toESM(require("fs"));
13147
- import_node_path36 = __toESM(require("path"));
13418
+ import_node_fs31 = __toESM(require("fs"));
13419
+ import_node_path37 = __toESM(require("path"));
13148
13420
  init_network_claims();
13149
13421
  }
13150
13422
  });
@@ -13182,7 +13454,7 @@ var init_network_provider_effect = __esm({
13182
13454
 
13183
13455
  // src/core/devsy-mutation.ts
13184
13456
  function withMutationLock(activity, target, operation) {
13185
- import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13457
+ import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13186
13458
  return withFileLockSync(
13187
13459
  DEVSY_MUTATION_LOCK_FILE,
13188
13460
  {
@@ -13263,13 +13535,13 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
13263
13535
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
13264
13536
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
13265
13537
  }
13266
- var import_node_child_process21, import_node_fs31, import_node_path37, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
13538
+ var import_node_child_process21, import_node_fs32, import_node_path38, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
13267
13539
  var init_devsy_mutation = __esm({
13268
13540
  "src/core/devsy-mutation.ts"() {
13269
13541
  "use strict";
13270
13542
  import_node_child_process21 = require("child_process");
13271
- import_node_fs31 = __toESM(require("fs"));
13272
- import_node_path37 = __toESM(require("path"));
13543
+ import_node_fs32 = __toESM(require("fs"));
13544
+ import_node_path38 = __toESM(require("path"));
13273
13545
  init_devsy_agent();
13274
13546
  init_devsy_workspaces();
13275
13547
  init_file_lock();
@@ -13278,14 +13550,14 @@ var init_devsy_mutation = __esm({
13278
13550
  init_network_provider_effect();
13279
13551
  init_reliability_context();
13280
13552
  init_router();
13281
- DEVSY_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
13553
+ DEVSY_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
13282
13554
  DEVSY_MUTATION_WAIT_MS = 18e5;
13283
13555
  }
13284
13556
  });
13285
13557
 
13286
13558
  // src/core/devpod-mutation.ts
13287
13559
  function withMutationLock2(activity, target, operation) {
13288
- import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13560
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13289
13561
  return withFileLockSync(
13290
13562
  DEVPOD_MUTATION_LOCK_FILE,
13291
13563
  {
@@ -13375,13 +13647,13 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
13375
13647
  resetWorkspaceRuntimeCaches();
13376
13648
  return result;
13377
13649
  }
13378
- var import_node_child_process22, import_node_fs32, import_node_path38, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13650
+ var import_node_child_process22, import_node_fs33, import_node_path39, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13379
13651
  var init_devpod_mutation = __esm({
13380
13652
  "src/core/devpod-mutation.ts"() {
13381
13653
  "use strict";
13382
13654
  import_node_child_process22 = require("child_process");
13383
- import_node_fs32 = __toESM(require("fs"));
13384
- import_node_path38 = __toESM(require("path"));
13655
+ import_node_fs33 = __toESM(require("fs"));
13656
+ import_node_path39 = __toESM(require("path"));
13385
13657
  init_devpod_workspaces();
13386
13658
  init_devsy_mutation();
13387
13659
  init_file_lock();
@@ -13390,7 +13662,7 @@ var init_devpod_mutation = __esm({
13390
13662
  init_reliability_context();
13391
13663
  init_router();
13392
13664
  init_workspace_runtime();
13393
- DEVPOD_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13665
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13394
13666
  DEVPOD_MUTATION_WAIT_MS = 18e5;
13395
13667
  }
13396
13668
  });
@@ -13405,10 +13677,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
13405
13677
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
13406
13678
  const comparableRepo = comparableWorkspacePath(repoPath);
13407
13679
  const comparableWorktree = comparableWorkspacePath(worktreePath);
13408
- const localRoot = import_node_path39.default.join(comparableRepo, "trees") + import_node_path39.default.sep;
13680
+ const localRoot = import_node_path40.default.join(comparableRepo, "trees") + import_node_path40.default.sep;
13409
13681
  if (comparableWorktree.startsWith(localRoot)) return true;
13410
- const legacyPrefix = `${import_node_path39.default.basename(comparableRepo)}-`;
13411
- return import_node_path39.default.dirname(comparableWorktree) === import_node_path39.default.dirname(comparableRepo) && import_node_path39.default.basename(comparableWorktree).startsWith(legacyPrefix);
13682
+ const legacyPrefix = `${import_node_path40.default.basename(comparableRepo)}-`;
13683
+ return import_node_path40.default.dirname(comparableWorktree) === import_node_path40.default.dirname(comparableRepo) && import_node_path40.default.basename(comparableWorktree).startsWith(legacyPrefix);
13412
13684
  }
13413
13685
  function previewActions(devpodStatus, routeCount, includeRecord) {
13414
13686
  const actions = [
@@ -13660,11 +13932,11 @@ function applyWorkspaceGc(plan) {
13660
13932
  candidates
13661
13933
  };
13662
13934
  }
13663
- var import_node_path39;
13935
+ var import_node_path40;
13664
13936
  var init_workspace_gc = __esm({
13665
13937
  "src/core/workspace-gc.ts"() {
13666
13938
  "use strict";
13667
- import_node_path39 = __toESM(require("path"));
13939
+ import_node_path40 = __toESM(require("path"));
13668
13940
  init_devpod_mutation();
13669
13941
  init_devpod_workspaces();
13670
13942
  init_host_routes();
@@ -13734,11 +14006,11 @@ function inspectPostgresCredentials(repoPath, config) {
13734
14006
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
13735
14007
  continue;
13736
14008
  }
13737
- if (!import_node_fs33.default.existsSync(absolutePath)) {
14009
+ if (!import_node_fs34.default.existsSync(absolutePath)) {
13738
14010
  continue;
13739
14011
  }
13740
14012
  try {
13741
- const raw = import_node_fs33.default.readFileSync(absolutePath, "utf-8");
14013
+ const raw = import_node_fs34.default.readFileSync(absolutePath, "utf-8");
13742
14014
  const parsed = import_yaml5.default.parse(raw);
13743
14015
  const root = asRecord2(parsed);
13744
14016
  const services = asRecord2(root?.services);
@@ -14018,7 +14290,7 @@ async function buildDoctorReport(options = {}) {
14018
14290
  const config = runtimeConfig.config;
14019
14291
  loadedConfig = config;
14020
14292
  loadedWorkspace = runtimeConfig.workspace;
14021
- const cliVersion = true ? "0.0.65" : "0.0.0-dev";
14293
+ const cliVersion = true ? "0.0.67" : "0.0.0-dev";
14022
14294
  const configVersion = config.devrouter?.version;
14023
14295
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
14024
14296
  addCheck(checks, {
@@ -14048,9 +14320,9 @@ async function buildDoctorReport(options = {}) {
14048
14320
  (app) => app.docker.composeFiles.map((filePath) => ({
14049
14321
  app: app.name,
14050
14322
  filePath,
14051
- absolutePath: import_node_path40.default.resolve(repo.path, filePath)
14323
+ absolutePath: import_node_path41.default.resolve(repo.path, filePath)
14052
14324
  }))
14053
- ).filter((entry) => !import_node_fs33.default.existsSync(entry.absolutePath));
14325
+ ).filter((entry) => !import_node_fs34.default.existsSync(entry.absolutePath));
14054
14326
  addCheck(checks, {
14055
14327
  id: "repo.compose-files",
14056
14328
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -14093,8 +14365,8 @@ async function buildDoctorReport(options = {}) {
14093
14365
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
14094
14366
  app: app.name,
14095
14367
  cwd: app.hostRun.cwd,
14096
- absolutePath: import_node_path40.default.resolve(repo.path, app.hostRun.cwd)
14097
- })).filter((entry) => !import_node_fs33.default.existsSync(entry.absolutePath));
14368
+ absolutePath: import_node_path41.default.resolve(repo.path, app.hostRun.cwd)
14369
+ })).filter((entry) => !import_node_fs34.default.existsSync(entry.absolutePath));
14098
14370
  addCheck(checks, {
14099
14371
  id: "repo.host-cwd",
14100
14372
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -14238,12 +14510,12 @@ async function buildDoctorReport(options = {}) {
14238
14510
  nextSteps
14239
14511
  };
14240
14512
  }
14241
- var import_node_fs33, import_node_path40, import_yaml5, POSTGRES_DEFAULTS;
14513
+ var import_node_fs34, import_node_path41, import_yaml5, POSTGRES_DEFAULTS;
14242
14514
  var init_doctor = __esm({
14243
14515
  "src/core/doctor.ts"() {
14244
14516
  "use strict";
14245
- import_node_fs33 = __toESM(require("fs"));
14246
- import_node_path40 = __toESM(require("path"));
14517
+ import_node_fs34 = __toESM(require("fs"));
14518
+ import_node_path41 = __toESM(require("path"));
14247
14519
  import_yaml5 = __toESM(require("yaml"));
14248
14520
  init_docker();
14249
14521
  init_host_routes();
@@ -14652,96 +14924,6 @@ var init_up = __esm({
14652
14924
  }
14653
14925
  });
14654
14926
 
14655
- // src/core/devsy-exec-proof.ts
14656
- function proveLocalDockerSelection(workspace) {
14657
- const name = workspace.providerName;
14658
- const context = workspace.context;
14659
- if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
14660
- throw new Error("Retained exec provider configuration identity is unavailable.");
14661
- const file = import_node_path41.default.join(
14662
- process.env.DEVSY_HOME || import_node_path41.default.join(import_node_os4.default.homedir(), ".devsy"),
14663
- "contexts",
14664
- context,
14665
- "providers",
14666
- name,
14667
- "provider.json"
14668
- );
14669
- try {
14670
- if (import_node_fs34.default.statSync(file).size > 1024 * 1024) throw new Error();
14671
- const config = JSON.parse(import_node_fs34.default.readFileSync(file, "utf8"));
14672
- const driver = config.agent?.driver ?? "docker";
14673
- const configuredPath = config.agent?.docker?.path ?? "";
14674
- if (typeof configuredPath !== "string") throw new Error();
14675
- const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
14676
- if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
14677
- } catch {
14678
- throw new Error("Retained exec requires a provider configured for the local Docker command.");
14679
- }
14680
- return name;
14681
- }
14682
- function captureDevsyExecProof(repoPath) {
14683
- repoPath = comparableWorkspacePath(repoPath);
14684
- const registry = listDevsyWorkspaces();
14685
- const workspace = selectDevsyWorkspace(registry, repoPath);
14686
- if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
14687
- throw new Error("Retained exec requires one exact running Devsy registration.");
14688
- const providerName = proveLocalDockerSelection(workspace);
14689
- const endpoint = resolveManagedStopEndpoint();
14690
- if (!supportsManagedStopBaseline(endpoint))
14691
- throw new Error("Retained exec requires an exact local Docker endpoint.");
14692
- const daemon = inspectManagedStopDaemon(endpoint);
14693
- const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
14694
- (container) => container.state.Running
14695
- );
14696
- if (candidates.length !== 1)
14697
- throw new Error("Retained exec requires one exact running primary container.");
14698
- const candidate = candidates[0];
14699
- const project = candidate.labels["com.docker.compose.project"];
14700
- if (!project) throw new Error("Retained exec primary has no Compose identity.");
14701
- const pinned = workspaceAppContainers(
14702
- inspectManagedStopContainers(project, endpoint),
14703
- repoPath
14704
- ).filter((container) => container.state.Running);
14705
- if (pinned.length !== 1 || pinned[0].id !== candidate.id)
14706
- throw new Error("Retained exec primary changed during inspection.");
14707
- const mounts = pinned[0].mounts.filter(
14708
- (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
14709
- );
14710
- if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
14711
- throw new Error("Retained exec requires one exact source mount.");
14712
- const uidBytes = Buffer.byteLength(workspace.uid);
14713
- const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
14714
- const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
14715
- if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
14716
- throw new Error("Retained exec runner does not select the exact primary container.");
14717
- if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
14718
- throw new Error("Retained exec provider or Docker identity changed.");
14719
- return {
14720
- repoPath,
14721
- id: workspace.id,
14722
- uid: workspace.uid,
14723
- context: workspace.context,
14724
- providerName,
14725
- endpoint,
14726
- daemon,
14727
- containerId: candidate.id,
14728
- workspacePath: mounts[0].Destination
14729
- };
14730
- }
14731
- var import_node_fs34, import_node_os4, import_node_path41, import_node_util3;
14732
- var init_devsy_exec_proof = __esm({
14733
- "src/core/devsy-exec-proof.ts"() {
14734
- "use strict";
14735
- import_node_fs34 = __toESM(require("fs"));
14736
- import_node_os4 = __toESM(require("os"));
14737
- import_node_path41 = __toESM(require("path"));
14738
- import_node_util3 = require("util");
14739
- init_devpod_environment();
14740
- init_devsy_workspaces();
14741
- init_workspace();
14742
- }
14743
- });
14744
-
14745
14927
  // src/core/reliability-model.ts
14746
14928
  function record4(value) {
14747
14929
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -14794,6 +14976,7 @@ function assertReliabilityEvent(value) {
14794
14976
  case "launched":
14795
14977
  case "interrupted":
14796
14978
  case "not-started":
14979
+ case "settle":
14797
14980
  valid = isReliabilityId(value.operationId);
14798
14981
  break;
14799
14982
  case "completion":
@@ -14863,8 +15046,13 @@ function transition(state, outcome, effects = []) {
14863
15046
  assertReliabilityState(state);
14864
15047
  return { state, effects, outcome };
14865
15048
  }
14866
- function unchanged(state, outcome) {
14867
- return transition(cloneState(state), outcome);
15049
+ function unchanged(state, outcome, reason) {
15050
+ const result = transition(cloneState(state), outcome);
15051
+ if (reason) result.reason = reason;
15052
+ return result;
15053
+ }
15054
+ function blocked2(state, reason) {
15055
+ return unchanged(state, "blocked", reason);
14868
15056
  }
14869
15057
  function advance(value) {
14870
15058
  return value === Number.MAX_SAFE_INTEGER ? null : value + 1;
@@ -14889,7 +15077,10 @@ function updateRunningPhase(state) {
14889
15077
  }
14890
15078
  function handleRequest(state, event) {
14891
15079
  if (state.executionPolicy === "manual" && event.mode === "start")
14892
- return unchanged(state, "blocked");
15080
+ return blocked2(
15081
+ state,
15082
+ "start requests are not admitted under manual execution policy; use the ensure/stop commands."
15083
+ );
14893
15084
  const existing = state.requests.find((request) => request.key === event.key);
14894
15085
  if (existing) {
14895
15086
  const consumer = state.consumers.find((candidate) => candidate.id === existing.consumerId);
@@ -14956,7 +15147,8 @@ function handleRequest(state, event) {
14956
15147
  }
14957
15148
  function canExecAfterInterruptedEnsure(state) {
14958
15149
  const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
14959
- return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
15150
+ const quiescent = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
15151
+ return state.executionPolicy === "manual" && (state.desired === "running" || quiescent && state.desired === "stopped-by-user") && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && (state.phase === "recovering" || quiescent) || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
14960
15152
  }
14961
15153
  function handleOperationRequest(state, event) {
14962
15154
  if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
@@ -14969,25 +15161,39 @@ function handleOperationRequest(state, event) {
14969
15161
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
14970
15162
  return unchanged(state, "conflict");
14971
15163
  if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
14972
- return unchanged(state, "blocked");
15164
+ return blocked2(
15165
+ state,
15166
+ "the latest ensure is INTERRUPTED and drained; exec requires recovering it first (run ensure, or exec with recovery proof)."
15167
+ );
14973
15168
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
14974
15169
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
14975
15170
  if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
14976
- return unchanged(state, "blocked");
15171
+ return blocked2(
15172
+ state,
15173
+ `operation ${state.operation.id} is ${state.operation.status} (drained=${state.operation.drained}) and no stop proof, ensure reconciliation, or exec recovery supersedes it.`
15174
+ );
14977
15175
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
14978
- return unchanged(state, "blocked");
15176
+ return blocked2(
15177
+ state,
15178
+ `phase is '${state.phase}' with desired '${state.desired}'; wait for the running stop to finish.`
15179
+ );
14979
15180
  if (event.kind === "exec" && (!event.runtimeRunning || state.desired !== "running" && state.intentRevision !== 0))
14980
- return unchanged(state, "blocked");
15181
+ return blocked2(
15182
+ state,
15183
+ `exec requires a running workspace (runtimeRunning=${event.runtimeRunning}, desired='${state.desired}').`
15184
+ );
14981
15185
  const rollover = state.operationHistory.length >= RELIABILITY_MAX_ITEMS;
14982
15186
  let retired = -1;
14983
15187
  if (rollover) {
14984
- if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
14985
- return unchanged(state, "blocked");
14986
15188
  const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
14987
15189
  retired = state.operationHistory.findIndex(
14988
- (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
15190
+ (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED", "INTERRUPTED"].includes(entry.status)
14989
15191
  );
14990
- if (retired === -1) return unchanged(state, "blocked");
15192
+ if (retired === -1)
15193
+ return blocked2(
15194
+ state,
15195
+ `journal holds ${state.operationHistory.length} entries and every retirable entry is the current operation or the latest ensure.`
15196
+ );
14991
15197
  }
14992
15198
  if (state.desired !== "running" || rollover) {
14993
15199
  const revision = advance(state.intentRevision);
@@ -15042,7 +15248,10 @@ function handleDispatch(state) {
15042
15248
  if (state.operation?.drained || state.operation?.status !== "NOT_STARTED")
15043
15249
  return unchanged(state, "stale");
15044
15250
  if (state.desired !== "running" || state.executionPolicy === "capacity-managed" && state.admission !== "admitted" || state.consumers.length === 0) {
15045
- return unchanged(state, "blocked");
15251
+ return blocked2(
15252
+ state,
15253
+ `dispatch requires desired 'running' with a registered consumer (desired='${state.desired}', consumers=${state.consumers.length}).`
15254
+ );
15046
15255
  }
15047
15256
  const corrective = state.phase === "recovering" && state.incident !== null;
15048
15257
  state.operation = { ...state.operation, status: "DISPATCH_PENDING", exitCode: null };
@@ -15057,7 +15266,10 @@ function handleDispatchPersisted(state, event) {
15057
15266
  if (state.desired !== "running" || state.executionPolicy === "capacity-managed" && (state.admission !== "admitted" || !state.chargeHeld)) {
15058
15267
  state.operation = setUnknown(state.operation);
15059
15268
  state.chargeHeld = state.executionPolicy === "capacity-managed";
15060
- return transition(state, "blocked");
15269
+ return blocked2(
15270
+ state,
15271
+ `dispatch persistence requires desired 'running' (desired='${state.desired}'); the operation outcome is unknown.`
15272
+ );
15061
15273
  }
15062
15274
  if (state.phase === "recovering" && state.incident !== null && state.incident.correctiveActionsTaken >= state.incident.actionLimit) {
15063
15275
  return unchanged(state, "blocked");
@@ -15105,6 +15317,22 @@ function handleInterrupted(state, event) {
15105
15317
  if (state.desired === "running") state.phase = "recovering";
15106
15318
  return transition(state, "accepted");
15107
15319
  }
15320
+ function handleSettle(state, event) {
15321
+ if (state.executionPolicy !== "manual") return unchanged(state, "stale");
15322
+ if (!state.operation || state.operation.id !== event.operationId)
15323
+ return unchanged(state, "stale");
15324
+ if (state.operation.drained && state.operation.status === "INTERRUPTED")
15325
+ return unchanged(state, "joined");
15326
+ if (state.operation.status === "COMPLETED") return unchanged(state, "joined");
15327
+ state.operation = {
15328
+ ...state.operation,
15329
+ status: "INTERRUPTED",
15330
+ drained: true,
15331
+ exitCode: null
15332
+ };
15333
+ if (state.desired === "running") state.phase = "recovering";
15334
+ return transition(state, "accepted");
15335
+ }
15108
15336
  function handleObservation(state, event, nowMs) {
15109
15337
  if (state.desired !== "running") return unchanged(state, "stale");
15110
15338
  const previous = state.observations.find(
@@ -15114,7 +15342,10 @@ function handleObservation(state, event, nowMs) {
15114
15342
  return unchanged(state, "stale");
15115
15343
  }
15116
15344
  if (!previous && state.observations.length >= RELIABILITY_MAX_ITEMS)
15117
- return unchanged(state, "blocked");
15345
+ return blocked2(
15346
+ state,
15347
+ `observation set holds ${state.observations.length} distinct capabilities; observations repeat per capability and never retire.`
15348
+ );
15118
15349
  state.observations = state.observations.filter(
15119
15350
  (observation) => observation.capability !== event.observation.capability
15120
15351
  );
@@ -15142,7 +15373,10 @@ function handleStop(state) {
15142
15373
  function handleStopProof(state, event) {
15143
15374
  if (state.desired === "running") return unchanged(state, "stale");
15144
15375
  if (state.executionPolicy === "manual" && state.operation && !state.operation.drained && event.workloadsStopped && event.routesRemoved)
15145
- return unchanged(state, "blocked");
15376
+ return blocked2(
15377
+ state,
15378
+ `stop proof cannot settle while operation ${state.operation.id} is not drained; wait for its worker or settle the journal.`
15379
+ );
15146
15380
  const previous = { ...state.stopProof };
15147
15381
  if (previous.workloadsStopped === event.workloadsStopped && previous.routesRemoved === event.routesRemoved)
15148
15382
  return unchanged(state, "joined");
@@ -15321,6 +15555,8 @@ function applyReliabilityEvent(state, event, nowMs) {
15321
15555
  return handleCompletion(next, event);
15322
15556
  case "interrupted":
15323
15557
  return handleInterrupted(next, event);
15558
+ case "settle":
15559
+ return handleSettle(next, event);
15324
15560
  case "observation":
15325
15561
  return handleObservation(next, event, nowMs);
15326
15562
  case "stop-proof":
@@ -15481,7 +15717,9 @@ async function runLifecycleWorker(request, beforeAdmission) {
15481
15717
  Date.now()
15482
15718
  );
15483
15719
  if (admitted.outcome !== "accepted")
15484
- throw new Error(`Lifecycle admission is ${admitted.outcome}.`);
15720
+ throw new Error(
15721
+ `Lifecycle admission is ${admitted.outcome}.${admitted.reason ? ` ${admitted.reason}` : ""}`
15722
+ );
15485
15723
  record5.state = admitted.state;
15486
15724
  record5.outcome = null;
15487
15725
  request.fence = reliabilityFence(record5.state);
@@ -15971,7 +16209,9 @@ async function withLifecycleOperationLock(repoPath, operation) {
15971
16209
  function stepRecord(record5, event) {
15972
16210
  const transition2 = stepReliability(record5.state, event, Date.now());
15973
16211
  if (!["accepted", "joined"].includes(transition2.outcome))
15974
- throw new Error(`Lifecycle transition is ${transition2.outcome}.`);
16212
+ throw new Error(
16213
+ `Lifecycle transition is ${transition2.outcome}.${transition2.reason ? ` ${transition2.reason}` : ""}`
16214
+ );
15975
16215
  record5.state = transition2.state;
15976
16216
  }
15977
16217
  function reconcileDrained(record5) {
@@ -20320,6 +20560,70 @@ var init_workspace_cleanup = __esm({
20320
20560
  }
20321
20561
  });
20322
20562
 
20563
+ // src/core/workspace-journal-settle.ts
20564
+ async function settleWorkspaceJournal(repoPath) {
20565
+ repoPath = comparableWorkspacePath(repoPath);
20566
+ if (isLinkedWorktree(repoPath) && !readPersistedWorkspace(repoPath)) {
20567
+ throw new Error("Settle requires the existing linked workspace identity.");
20568
+ }
20569
+ const identity2 = {
20570
+ repoPath,
20571
+ workspace: resolveWorktreeWorkspace(repoPath) ?? null,
20572
+ provider: resolveWorkspaceRuntimeOrDefault(repoPath)
20573
+ };
20574
+ if (!readReliabilityOperation(identity2)) {
20575
+ throw new Error(`No reliability journal exists for '${repoPath}'; nothing to settle.`);
20576
+ }
20577
+ const settled = await withWorkspaceLifecycleLock(
20578
+ repoPath,
20579
+ async () => updateReliabilityOperation(identity2, (record5) => settleRecord(record5))
20580
+ );
20581
+ return { repoPath, workspace: identity2.workspace, ...settled };
20582
+ }
20583
+ function settleRecord(record5) {
20584
+ if (record5.worker) {
20585
+ const birth = processBirthIdentity(record5.worker.pid);
20586
+ if (birth === record5.worker.birth || !workerGroupAbsent(record5.worker.pid)) {
20587
+ throw new Error(
20588
+ `Lifecycle worker '${record5.worker.id}' is still running; settle is refused while its outcome is observable.`
20589
+ );
20590
+ }
20591
+ reconcileDrained(record5);
20592
+ if (record5.worker) throw new Error("Dead lifecycle worker could not be drained.");
20593
+ }
20594
+ const operation = record5.state.operation;
20595
+ if (!operation) throw new Error("No lifecycle operation is recorded; nothing to settle.");
20596
+ const priorStatus = operation.status;
20597
+ const transition2 = stepReliability(
20598
+ record5.state,
20599
+ { ...reliabilityFence(record5.state), type: "settle", operationId: operation.id },
20600
+ Date.now()
20601
+ );
20602
+ if (!["accepted", "joined"].includes(transition2.outcome))
20603
+ throw new Error(
20604
+ `Lifecycle settlement is ${transition2.outcome}.${transition2.reason ? ` ${transition2.reason}` : ""}`
20605
+ );
20606
+ record5.state = transition2.state;
20607
+ return {
20608
+ operationId: operation.id,
20609
+ status: transition2.outcome === "joined" ? "already-settled" : "settled",
20610
+ priorStatus
20611
+ };
20612
+ }
20613
+ var init_workspace_journal_settle = __esm({
20614
+ "src/core/workspace-journal-settle.ts"() {
20615
+ "use strict";
20616
+ init_file_lock();
20617
+ init_reliability_contract();
20618
+ init_reliability_lifecycle();
20619
+ init_reliability_model();
20620
+ init_reliability_operation_store();
20621
+ init_reliability_worker();
20622
+ init_workspace();
20623
+ init_workspace_runtime();
20624
+ }
20625
+ });
20626
+
20323
20627
  // src/core/workspace-lifecycle.ts
20324
20628
  function withWorkspaceAllocationTransaction(repoPath, operation) {
20325
20629
  return withWorkspaceOwnershipTransaction(repoPath, operation, {
@@ -20723,6 +21027,7 @@ __export(workspace_exports, {
20723
21027
  runWorkspaceCleanupCommand: () => runWorkspaceCleanupCommand,
20724
21028
  runWorkspaceDownCommand: () => runWorkspaceDownCommand,
20725
21029
  runWorkspaceGcCommand: () => runWorkspaceGcCommand,
21030
+ runWorkspaceJournalSettleCommand: () => runWorkspaceJournalSettleCommand,
20726
21031
  runWorkspaceLsCommand: () => runWorkspaceLsCommand,
20727
21032
  runWorkspaceStopCommand: () => runWorkspaceStopCommand,
20728
21033
  runWorkspaceUpCommand: () => runWorkspaceUpCommand
@@ -20812,6 +21117,26 @@ function runWorkspaceGcCommand(options) {
20812
21117
  }
20813
21118
  if (report.summary.errors > 0) process.exitCode = 1;
20814
21119
  }
21120
+ async function runWorkspaceJournalSettleCommand(options) {
21121
+ const repoPath = resolveGitCheckoutPath(options.path);
21122
+ const result = await settleWorkspaceJournal(repoPath);
21123
+ if (options.json) {
21124
+ printJSON(result);
21125
+ return;
21126
+ }
21127
+ const workspaceLabel = result.workspace ? ` (workspace '${result.workspace}')` : "";
21128
+ if (result.status === "already-settled") {
21129
+ process.stdout.write(
21130
+ `Journal operation ${result.operationId} is already settled${workspaceLabel}.
21131
+ `
21132
+ );
21133
+ return;
21134
+ }
21135
+ process.stdout.write(
21136
+ `Settled journal operation ${result.operationId} (was ${result.priorStatus})${workspaceLabel}; ensure and stop may proceed.
21137
+ `
21138
+ );
21139
+ }
20815
21140
  var init_workspace2 = __esm({
20816
21141
  "src/commands/workspace.ts"() {
20817
21142
  "use strict";
@@ -20819,8 +21144,10 @@ var init_workspace2 = __esm({
20819
21144
  init_repo_config();
20820
21145
  init_workspace_cleanup();
20821
21146
  init_workspace_gc();
21147
+ init_workspace_journal_settle();
20822
21148
  init_workspace_lifecycle();
20823
21149
  init_workspace_ownership();
21150
+ init_environment_path();
20824
21151
  }
20825
21152
  });
20826
21153
 
@@ -20862,7 +21189,7 @@ var init_version = __esm({
20862
21189
 
20863
21190
  // src/cli.ts
20864
21191
  var import_commander = require("commander");
20865
- var CLI_VERSION = true ? "0.0.65" : "0.0.0-dev";
21192
+ var CLI_VERSION = true ? "0.0.67" : "0.0.0-dev";
20866
21193
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
20867
21194
  function withErrorHandling(action2) {
20868
21195
  return async (...args) => {
@@ -21232,6 +21559,15 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
21232
21559
  await runWorkspaceDownCommand2(target, options);
21233
21560
  })
21234
21561
  );
21562
+ workspaceCommand2.command("journal settle").description(
21563
+ "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
21564
+ ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
21565
+ withErrorHandling(async (path54, _options, command) => {
21566
+ const options = command.opts();
21567
+ const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
21568
+ await runWorkspaceJournalSettleCommand2({ ...path54 ? { path: path54 } : {}, ...options });
21569
+ })
21570
+ );
21235
21571
  function parseVersionRequest(argv) {
21236
21572
  let requested = false;
21237
21573
  let repo;