@devrouter/cli 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devrouter-lifecycle-worker.js +41 -4
- package/dist/devrouter.js +56 -10
- package/package.json +1 -1
- package/upgrade-prompts/0.5.2.md +25 -0
- package/upgrade-prompts/0.5.3.md +36 -0
|
@@ -936,6 +936,20 @@ function hasExactComposeIdentity(container, options) {
|
|
|
936
936
|
const actualFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()).filter(Boolean);
|
|
937
937
|
const providerGeneratedFiles = actualFiles.filter((file) => {
|
|
938
938
|
const normalized = file.replaceAll("\\", "/");
|
|
939
|
+
if (process.env.DEVPOD_HOME) {
|
|
940
|
+
const relative = import_node_path4.default.relative(import_node_path4.default.resolve(process.env.DEVPOD_HOME), file).replaceAll("\\", "/");
|
|
941
|
+
if (/^agent\/contexts\/[^/]+\/workspaces\/[^/]+\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
942
|
+
relative
|
|
943
|
+
))
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
if (process.env.DEVSY_HOME) {
|
|
947
|
+
const relative = import_node_path4.default.relative(import_node_path4.default.resolve(process.env.DEVSY_HOME), file).replaceAll("\\", "/");
|
|
948
|
+
if (/^contexts\/[^/]+\/workspaces\/[^/]+\/agent\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
949
|
+
relative
|
|
950
|
+
))
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
939
953
|
return /\/(?:\.devpod\/agent\/contexts\/[^/]+\/workspaces\/[^/]+|\.devsy\/contexts\/[^/]+\/workspaces\/[^/]+\/agent)\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
940
954
|
normalized
|
|
941
955
|
);
|
|
@@ -2192,7 +2206,7 @@ function installedVersionOutput() {
|
|
|
2192
2206
|
return [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || void 0;
|
|
2193
2207
|
}
|
|
2194
2208
|
function nativeAssetName(platform, arch) {
|
|
2195
|
-
if (platform !== "darwin") return void 0;
|
|
2209
|
+
if (platform !== "darwin" && platform !== "linux") return void 0;
|
|
2196
2210
|
if (arch === "arm64") return "devsy-linux-arm64";
|
|
2197
2211
|
if (arch === "x64") return "devsy-linux-amd64";
|
|
2198
2212
|
return void 0;
|
|
@@ -5383,7 +5397,7 @@ function retain(session, previous) {
|
|
|
5383
5397
|
|
|
5384
5398
|
// src/core/reliability-operation-store.ts
|
|
5385
5399
|
function installedCliVersion() {
|
|
5386
|
-
return true ? "0.5.
|
|
5400
|
+
return true ? "0.5.3" : "0.0.0-dev";
|
|
5387
5401
|
}
|
|
5388
5402
|
function compareCliVersions(left, right) {
|
|
5389
5403
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -7393,7 +7407,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
|
|
|
7393
7407
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
7394
7408
|
const requiredVersion = config.devrouter?.version;
|
|
7395
7409
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
7396
|
-
const cliVersion = true ? "0.5.
|
|
7410
|
+
const cliVersion = true ? "0.5.3" : "0.0.0-dev";
|
|
7397
7411
|
if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
|
|
7398
7412
|
hasWarnedVersionMismatch = true;
|
|
7399
7413
|
process.stderr.write(
|
|
@@ -11325,10 +11339,33 @@ var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
|
|
|
11325
11339
|
function workerGroupAbsent(pid) {
|
|
11326
11340
|
try {
|
|
11327
11341
|
process.kill(-pid, 0);
|
|
11328
|
-
return false;
|
|
11329
11342
|
} catch (error) {
|
|
11330
11343
|
return error.code === "ESRCH";
|
|
11331
11344
|
}
|
|
11345
|
+
if (process.platform !== "linux") return false;
|
|
11346
|
+
let observedZombie = false;
|
|
11347
|
+
try {
|
|
11348
|
+
for (const entry of import_node_fs29.default.readdirSync("/proc")) {
|
|
11349
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
11350
|
+
let stat;
|
|
11351
|
+
try {
|
|
11352
|
+
stat = import_node_fs29.default.readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
11353
|
+
} catch (error) {
|
|
11354
|
+
if (error.code === "ENOENT") continue;
|
|
11355
|
+
return false;
|
|
11356
|
+
}
|
|
11357
|
+
const end = stat.lastIndexOf(")");
|
|
11358
|
+
if (end < 0) return false;
|
|
11359
|
+
const fields2 = stat.slice(end + 1).trim().split(/\s+/);
|
|
11360
|
+
if (!/^\d+$/.test(fields2[2] ?? "") || !/^[A-Za-z]$/.test(fields2[0] ?? "")) return false;
|
|
11361
|
+
if (Number(fields2[2]) !== pid) continue;
|
|
11362
|
+
if (fields2[0] !== "Z") return false;
|
|
11363
|
+
observedZombie = true;
|
|
11364
|
+
}
|
|
11365
|
+
} catch {
|
|
11366
|
+
return false;
|
|
11367
|
+
}
|
|
11368
|
+
return observedZombie;
|
|
11332
11369
|
}
|
|
11333
11370
|
|
|
11334
11371
|
// src/core/workspace-ensure.ts
|
package/dist/devrouter.js
CHANGED
|
@@ -1067,6 +1067,20 @@ function hasExactComposeIdentity(container, options) {
|
|
|
1067
1067
|
const actualFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()).filter(Boolean);
|
|
1068
1068
|
const providerGeneratedFiles = actualFiles.filter((file) => {
|
|
1069
1069
|
const normalized = file.replaceAll("\\", "/");
|
|
1070
|
+
if (process.env.DEVPOD_HOME) {
|
|
1071
|
+
const relative2 = import_node_path4.default.relative(import_node_path4.default.resolve(process.env.DEVPOD_HOME), file).replaceAll("\\", "/");
|
|
1072
|
+
if (/^agent\/contexts\/[^/]+\/workspaces\/[^/]+\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
1073
|
+
relative2
|
|
1074
|
+
))
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
if (process.env.DEVSY_HOME) {
|
|
1078
|
+
const relative2 = import_node_path4.default.relative(import_node_path4.default.resolve(process.env.DEVSY_HOME), file).replaceAll("\\", "/");
|
|
1079
|
+
if (/^contexts\/[^/]+\/workspaces\/[^/]+\/agent\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
1080
|
+
relative2
|
|
1081
|
+
))
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1070
1084
|
return /\/(?:\.devpod\/agent\/contexts\/[^/]+\/workspaces\/[^/]+|\.devsy\/contexts\/[^/]+\/workspaces\/[^/]+\/agent)\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
1071
1085
|
normalized
|
|
1072
1086
|
);
|
|
@@ -4774,7 +4788,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs8.default.read
|
|
|
4774
4788
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4775
4789
|
const requiredVersion = config.devrouter?.version;
|
|
4776
4790
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4777
|
-
const cliVersion = true ? "0.5.
|
|
4791
|
+
const cliVersion = true ? "0.5.3" : "0.0.0-dev";
|
|
4778
4792
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4779
4793
|
hasWarnedVersionMismatch = true;
|
|
4780
4794
|
process.stderr.write(
|
|
@@ -6773,7 +6787,7 @@ var init_reliability_contract = __esm({
|
|
|
6773
6787
|
|
|
6774
6788
|
// src/core/reliability-operation-store.ts
|
|
6775
6789
|
function installedCliVersion() {
|
|
6776
|
-
return true ? "0.5.
|
|
6790
|
+
return true ? "0.5.3" : "0.0.0-dev";
|
|
6777
6791
|
}
|
|
6778
6792
|
function compareCliVersions(left, right) {
|
|
6779
6793
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -10596,7 +10610,7 @@ function installedVersionOutput() {
|
|
|
10596
10610
|
return [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || void 0;
|
|
10597
10611
|
}
|
|
10598
10612
|
function nativeAssetName(platform, arch) {
|
|
10599
|
-
if (platform !== "darwin") return void 0;
|
|
10613
|
+
if (platform !== "darwin" && platform !== "linux") return void 0;
|
|
10600
10614
|
if (arch === "arm64") return "devsy-linux-arm64";
|
|
10601
10615
|
if (arch === "x64") return "devsy-linux-amd64";
|
|
10602
10616
|
return void 0;
|
|
@@ -15530,6 +15544,12 @@ var init_reliability_model = __esm({
|
|
|
15530
15544
|
function isLifecycleProgressPhase(value) {
|
|
15531
15545
|
return typeof value === "string" && Object.hasOwn(ROLES, value);
|
|
15532
15546
|
}
|
|
15547
|
+
function muteLifecycleProgressHeartbeat() {
|
|
15548
|
+
heartbeatMuted = true;
|
|
15549
|
+
}
|
|
15550
|
+
function lifecycleProgressHeartbeatMuted() {
|
|
15551
|
+
return heartbeatMuted;
|
|
15552
|
+
}
|
|
15533
15553
|
function lifecycleProgressSnapshot(phase, receivedAt, now) {
|
|
15534
15554
|
const elapsedMs = receivedAt === void 0 ? null : Math.max(0, Math.floor(now - receivedAt));
|
|
15535
15555
|
return {
|
|
@@ -15541,7 +15561,7 @@ function lifecycleProgressSnapshot(phase, receivedAt, now) {
|
|
|
15541
15561
|
liveness: "unknown"
|
|
15542
15562
|
};
|
|
15543
15563
|
}
|
|
15544
|
-
var ROLES;
|
|
15564
|
+
var ROLES, heartbeatMuted;
|
|
15545
15565
|
var init_lifecycle_progress = __esm({
|
|
15546
15566
|
"src/core/lifecycle-progress.ts"() {
|
|
15547
15567
|
"use strict";
|
|
@@ -15559,6 +15579,7 @@ var init_lifecycle_progress = __esm({
|
|
|
15559
15579
|
rollback: "lifecycle-worker",
|
|
15560
15580
|
stop: "lifecycle-stop"
|
|
15561
15581
|
};
|
|
15582
|
+
heartbeatMuted = false;
|
|
15562
15583
|
}
|
|
15563
15584
|
});
|
|
15564
15585
|
|
|
@@ -15579,10 +15600,33 @@ function hasDuplicateOperation(record5, requestId, operationId) {
|
|
|
15579
15600
|
function workerGroupAbsent(pid) {
|
|
15580
15601
|
try {
|
|
15581
15602
|
process.kill(-pid, 0);
|
|
15582
|
-
return false;
|
|
15583
15603
|
} catch (error) {
|
|
15584
15604
|
return error.code === "ESRCH";
|
|
15585
15605
|
}
|
|
15606
|
+
if (process.platform !== "linux") return false;
|
|
15607
|
+
let observedZombie = false;
|
|
15608
|
+
try {
|
|
15609
|
+
for (const entry of import_node_fs31.default.readdirSync("/proc")) {
|
|
15610
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
15611
|
+
let stat;
|
|
15612
|
+
try {
|
|
15613
|
+
stat = import_node_fs31.default.readFileSync(`/proc/${entry}/stat`, "utf8");
|
|
15614
|
+
} catch (error) {
|
|
15615
|
+
if (error.code === "ENOENT") continue;
|
|
15616
|
+
return false;
|
|
15617
|
+
}
|
|
15618
|
+
const end = stat.lastIndexOf(")");
|
|
15619
|
+
if (end < 0) return false;
|
|
15620
|
+
const fields2 = stat.slice(end + 1).trim().split(/\s+/);
|
|
15621
|
+
if (!/^\d+$/.test(fields2[2] ?? "") || !/^[A-Za-z]$/.test(fields2[0] ?? "")) return false;
|
|
15622
|
+
if (Number(fields2[2]) !== pid) continue;
|
|
15623
|
+
if (fields2[0] !== "Z") return false;
|
|
15624
|
+
observedZombie = true;
|
|
15625
|
+
}
|
|
15626
|
+
} catch {
|
|
15627
|
+
return false;
|
|
15628
|
+
}
|
|
15629
|
+
return observedZombie;
|
|
15586
15630
|
}
|
|
15587
15631
|
function signalOwnedGroup(child, signal) {
|
|
15588
15632
|
if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
|
|
@@ -15620,7 +15664,7 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
|
|
|
15620
15664
|
let progressReceivedAt;
|
|
15621
15665
|
let progressTimer;
|
|
15622
15666
|
const emitProgress = () => {
|
|
15623
|
-
if (closed || cancelled || request2.kind === "exec") return;
|
|
15667
|
+
if (closed || cancelled || request2.kind === "exec" || lifecycleProgressHeartbeatMuted()) return;
|
|
15624
15668
|
const data = `${JSON.stringify(lifecycleProgressSnapshot(progressPhase, progressReceivedAt, performance.now()))}
|
|
15625
15669
|
`;
|
|
15626
15670
|
try {
|
|
@@ -22349,7 +22393,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
22349
22393
|
"- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not detect or prevent OOM; bounded automatic recovery runs only when the operator enables the capacity recovery policy.",
|
|
22350
22394
|
"- Capacity admission is opt-in: only an operator-enabled capacity policy with an enrolled checkout routes ensure and exec through controller admission. The CLI follows the decision with bounded reconnecting waits that survive controller restarts, worker results are journalled atomically, and stop bypasses admission. Without an enabled policy lifecycle behavior is unchanged. An optional recovery block adds bounded automatic recovery: one journal-admitted corrective ensure per positively failed required capability, bounded by per-scope restart counts, an aggregate corrective-action cap and a rolling observation window. It is inert unless enabled.",
|
|
22351
22395
|
"- A lock that cannot prove its owner fails closed with the failing inspection stage, the exact lock path, and a portable reproduction command; run the canonical command in a permitted host context. No identity fallback is attempted, and `devrouter controller` prints the underlying cause on stderr while keeping the stable `controller-unavailable` JSON on stdout.",
|
|
22352
|
-
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once per installed Devsy release, rerunning it after a Devsy update, so Devrouter acquires and verifies the official Linux agent in its own machine cache. Devrouter supports releases in the range >=1.16.2 <2.0.0, verifies a release's published asset against the SHA-256 digest GitHub reports, and never injects a release without one. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access; a supported release with no recorded manifest is `ready` with a drift warning and no injected agent until `setup` records one, while a version outside the supported range or unparseable stays `stale`. `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match the official asset for the installed supported release. `ensure` and `stop` stream bounded phase progress on stderr, so parse command JSON from stdout only.",
|
|
22396
|
+
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once per installed Devsy release, rerunning it after a Devsy update, so Devrouter acquires and verifies the official Linux agent in its own machine cache. Devrouter selects agents on macOS and Linux arm64/x64 hosts. Use Devsy 1.16.2 for warm profile changes; 1.23.2 refuses changed runServices without recreation, which devrouter never applies automatically. Devrouter supports agent releases in the range >=1.16.2 <2.0.0, verifies a release's published asset against the SHA-256 digest GitHub reports, and never injects a release without one. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access; a supported release with no recorded manifest is `ready` with a drift warning and no injected agent until `setup` records one, while a version outside the supported range or unparseable stays `stale`. `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match the official asset for the installed supported release. `ensure` and `stop` stream bounded phase progress on stderr, so parse command JSON from stdout only.",
|
|
22353
22397
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
22354
22398
|
"- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
|
|
22355
22399
|
"- Managed adapters may use devrouter-process ensure --prepare-command <command> for synchronous dependency preparation under the process lock before app launch. Unchanged owned processes skip preparation; it participates in default fingerprints and must not daemonize or detach.",
|
|
@@ -24479,7 +24523,7 @@ function probeInstalledCliVersion(executable) {
|
|
|
24479
24523
|
}
|
|
24480
24524
|
function buildCliPathCheck(options = {}) {
|
|
24481
24525
|
const id5 = "global.cli-path";
|
|
24482
|
-
const runningVersion = options.runningVersion ?? (true ? "0.5.
|
|
24526
|
+
const runningVersion = options.runningVersion ?? (true ? "0.5.3" : "0.0.0-dev");
|
|
24483
24527
|
if (runningVersion === "0.0.0-dev") {
|
|
24484
24528
|
return {
|
|
24485
24529
|
id: id5,
|
|
@@ -25375,7 +25419,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
25375
25419
|
suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
|
|
25376
25420
|
});
|
|
25377
25421
|
}
|
|
25378
|
-
const cliVersion = true ? "0.5.
|
|
25422
|
+
const cliVersion = true ? "0.5.3" : "0.0.0-dev";
|
|
25379
25423
|
const configVersion = config.devrouter?.version;
|
|
25380
25424
|
const installedKnown = cliVersion !== "0.0.0-dev";
|
|
25381
25425
|
const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
|
|
@@ -33775,6 +33819,7 @@ async function runWorkspaceIdleCommand(options) {
|
|
|
33775
33819
|
let stops = [];
|
|
33776
33820
|
if (options.stop) {
|
|
33777
33821
|
const paths = idleSweepPaths();
|
|
33822
|
+
if (!process.stderr.isTTY) muteLifecycleProgressHeartbeat();
|
|
33778
33823
|
import_node_fs61.default.mkdirSync(paths.directory, { recursive: true, mode: 448 });
|
|
33779
33824
|
const result = await withFileLock(paths.lock, { activity: "idle sweep" }, async () => {
|
|
33780
33825
|
try {
|
|
@@ -33983,6 +34028,7 @@ var init_workspace2 = __esm({
|
|
|
33983
34028
|
init_docker_leftovers();
|
|
33984
34029
|
init_file_lock();
|
|
33985
34030
|
init_idle_sweep();
|
|
34031
|
+
init_lifecycle_progress();
|
|
33986
34032
|
init_output();
|
|
33987
34033
|
init_reliability_lifecycle();
|
|
33988
34034
|
init_repo_config();
|
|
@@ -34036,7 +34082,7 @@ var init_version = __esm({
|
|
|
34036
34082
|
|
|
34037
34083
|
// src/cli.ts
|
|
34038
34084
|
var import_commander = require("commander");
|
|
34039
|
-
var CLI_VERSION = true ? "0.5.
|
|
34085
|
+
var CLI_VERSION = true ? "0.5.3" : "0.0.0-dev";
|
|
34040
34086
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
34041
34087
|
function withErrorHandling(action2) {
|
|
34042
34088
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.5.2
|
|
2
|
+
|
|
3
|
+
This patch release keeps the idle-stop agent's `sweep.log` readable.
|
|
4
|
+
`workspace idle --stop` no longer writes a lifecycle progress line every ten
|
|
5
|
+
seconds of each stop when its stderr is not a terminal, as under launchd, so
|
|
6
|
+
the log records only stop outcomes and errors. An interactive sweep still shows
|
|
7
|
+
progress. There is no configuration change and no migration.
|
|
8
|
+
|
|
9
|
+
## Task
|
|
10
|
+
|
|
11
|
+
Install the published 0.5.2 CLI. No repository change is needed. Bump a
|
|
12
|
+
repository's `devrouter.version` to 0.5.2 only when consumer source changes are
|
|
13
|
+
authorized. A machine that uses the idle-stop agent needs no setup rerun when
|
|
14
|
+
devrouter is installed through Volta, because the agent's entry path does not
|
|
15
|
+
change between versions.
|
|
16
|
+
|
|
17
|
+
## Validation
|
|
18
|
+
|
|
19
|
+
1. Run `devrouter -V` from the consumer checkout.
|
|
20
|
+
2. Run `devrouter workspace idle --json` and confirm it completes.
|
|
21
|
+
|
|
22
|
+
## Report
|
|
23
|
+
|
|
24
|
+
Report the release revision and the resolved executable with its installed
|
|
25
|
+
version.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.5.3
|
|
2
|
+
|
|
3
|
+
This patch release supports verified Devsy agents on Linux arm64/x64,
|
|
4
|
+
custom DevPod and Devsy runtime homes, and drainage of Linux process groups
|
|
5
|
+
proven to contain only zombies. There is no configuration schema migration.
|
|
6
|
+
|
|
7
|
+
## Task
|
|
8
|
+
|
|
9
|
+
Install the published 0.5.3 CLI. Bump a consumer's `devrouter.version` only when
|
|
10
|
+
consumer source changes are authorized. Preserve exact checkout ownership,
|
|
11
|
+
containers, databases, dependency volumes, and existing runtime directories.
|
|
12
|
+
Changing the machine preference does not migrate a DevPod-owned checkout.
|
|
13
|
+
|
|
14
|
+
For Devsy, run `devrouter setup --yes --workspace-runtime devsy` to verify the
|
|
15
|
+
installed release's official Linux agent. Pin Devsy 1.16.2 when warm profile
|
|
16
|
+
changes are needed: 1.23.2 rejects a changed `runServices` list as structural.
|
|
17
|
+
Do not follow its recreation suggestion on a retained environment. Keep any
|
|
18
|
+
explicit `DEVSY_AGENT_BINARY` consistent with the installed Devsy release.
|
|
19
|
+
|
|
20
|
+
## Validation
|
|
21
|
+
|
|
22
|
+
1. Run `devrouter -V` from the consumer checkout and confirm 0.5.3.
|
|
23
|
+
2. Run `devrouter doctor --json` and inspect runtime ownership and agent readiness.
|
|
24
|
+
3. Run `devrouter ensure .` with the consumer's intended profile, then an existing
|
|
25
|
+
smoke or browser test. Do not replay destructive bootstrap hooks for a
|
|
26
|
+
retained database just to test an upgrade.
|
|
27
|
+
4. If testing profile changes or stop/resume, verify the exact container and
|
|
28
|
+
volumes are retained. Never bypass lifecycle fences or edit ownership records.
|
|
29
|
+
|
|
30
|
+
A zombie lifecycle worker retaining its recorded birth identity remains a
|
|
31
|
+
known recovery limitation on hosts whose PID 1 does not reap children.
|
|
32
|
+
|
|
33
|
+
## Report
|
|
34
|
+
|
|
35
|
+
Report the published version and resolved executable, selected runtime and
|
|
36
|
+
version, tested profile and functional outcomes, and any remaining limitations.
|