@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-lifecycle-worker.js +252 -20
- package/dist/devrouter.js +521 -185
- package/package.json +1 -1
- package/upgrade-prompts/0.0.66.md +31 -0
- package/upgrade-prompts/0.0.67.md +41 -0
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.
|
|
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, [
|
|
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(
|
|
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
|
|
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" || !
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
12485
|
+
const filePath = import_node_path34.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
12353
12486
|
try {
|
|
12354
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
12515
|
+
import_node_fs30.default.mkdirSync(directory, { recursive: true });
|
|
12383
12516
|
return withFileLockSync(
|
|
12384
|
-
|
|
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 (!
|
|
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) &&
|
|
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,
|
|
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
|
-
|
|
12610
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
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 =
|
|
12933
|
-
const featureDirectory =
|
|
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
|
|
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
|
-
|
|
13042
|
-
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
13102
|
-
const stat =
|
|
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 =
|
|
13378
|
+
const descriptor = import_node_fs31.default.openSync(
|
|
13107
13379
|
full,
|
|
13108
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
13147
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
13272
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
13384
|
-
|
|
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 =
|
|
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 =
|
|
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 = `${
|
|
13411
|
-
return
|
|
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
|
|
13935
|
+
var import_node_path40;
|
|
13664
13936
|
var init_workspace_gc = __esm({
|
|
13665
13937
|
"src/core/workspace-gc.ts"() {
|
|
13666
13938
|
"use strict";
|
|
13667
|
-
|
|
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 (!
|
|
14009
|
+
if (!import_node_fs34.default.existsSync(absolutePath)) {
|
|
13738
14010
|
continue;
|
|
13739
14011
|
}
|
|
13740
14012
|
try {
|
|
13741
|
-
const raw =
|
|
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.
|
|
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:
|
|
14323
|
+
absolutePath: import_node_path41.default.resolve(repo.path, filePath)
|
|
14052
14324
|
}))
|
|
14053
|
-
).filter((entry) => !
|
|
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:
|
|
14097
|
-
})).filter((entry) => !
|
|
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
|
|
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
|
-
|
|
14246
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
|
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;
|