@danypops/pi-packed 0.28.9 → 0.28.11
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/adoption/service-doctor.d.ts.map +1 -1
- package/dist/daemon/daemon-service.d.ts +1 -0
- package/dist/daemon/daemon-service.d.ts.map +1 -1
- package/dist/daemon/isolated-service-install.d.ts.map +1 -1
- package/package.json +5 -5
- package/service/src/adoption/service-doctor.ts +10 -2
- package/service/src/daemon/daemon-service.ts +33 -2
- package/service/src/daemon/isolated-service-install.ts +5 -2
- package/service/test/daemon-service.test.ts +28 -0
- package/service/test/isolated-service-install.test.ts +19 -2
- package/service/test/service-doctor.test.ts +26 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-doctor.d.ts","sourceRoot":"","sources":["../../service/src/adoption/service-doctor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"service-doctor.d.ts","sourceRoot":"","sources":["../../service/src/adoption/service-doctor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAK3E,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,2BAA2B,GAAG,qBAAqB,GAAG,4BAA4B,GAAG,oBAAoB,CAAC;IAChH,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC5D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,+EAA+E;IAC/E,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC7C;AAwBD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,iBAAiB,GACrB,mBAAmB,CAqFrB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAO5F"}
|
|
@@ -36,6 +36,7 @@ interface ResolvedDaemonEntrypoint {
|
|
|
36
36
|
restartOnFailure?: boolean;
|
|
37
37
|
restartSec?: number;
|
|
38
38
|
version: string;
|
|
39
|
+
contentSignature?: string;
|
|
39
40
|
}
|
|
40
41
|
export declare function detectVehicleDaemonService(packageDir: string, fallbackName: string, hoistedNodeModulesDir?: string): ResolvedDaemonEntrypoint | undefined;
|
|
41
42
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolated-service-install.d.ts","sourceRoot":"","sources":["../../service/src/daemon/isolated-service-install.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAuDD,qBAAa,oCAAqC,YAAW,yBAAyB;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAIrF,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGtC;AAED;;;;;;GAMG;AACH,qBAAa,iCAAkC,YAAW,yBAAyB;IAEjF,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,cAAc,EAAE,MAAM,EACtB,MAAM,SAAgE;IAGlF,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"isolated-service-install.d.ts","sourceRoot":"","sources":["../../service/src/daemon/isolated-service-install.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAChF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAuDD,qBAAa,oCAAqC,YAAW,yBAAyB;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAIrF,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGtC;AAED;;;;;;GAMG;AACH,qBAAa,iCAAkC,YAAW,yBAAyB;IAEjF,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,cAAc,EAAE,MAAM,EACtB,MAAM,SAAgE;IAGlF,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IA2FrF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM5B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIrC,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,KAAK;CAkBb"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.11",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"typecheck": "bunx tsc --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@danypops/armada": "^0.5.
|
|
30
|
+
"@danypops/armada": "^0.5.1",
|
|
31
31
|
"@danypops/packed": "^0.7.0",
|
|
32
32
|
"@danypops/pi-extension-harness": "^0.7.0",
|
|
33
|
-
"@danypops/vehicle-client": "^0.10.
|
|
33
|
+
"@danypops/vehicle-client": "^0.10.6",
|
|
34
34
|
"@danypops/vehicle-core": "^0.18.4",
|
|
35
|
-
"@danypops/vehicle-server": "^0.26.
|
|
35
|
+
"@danypops/vehicle-server": "^0.26.1",
|
|
36
36
|
"jiti": "^2.7.0",
|
|
37
37
|
"malevich-tui-components": "^0.32.1",
|
|
38
38
|
"publint": "0.3.22",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@danypops/pi-tui-harness": "^0.0.1",
|
|
43
|
-
"@danypops/vehicle-client-pi": "^0.45.
|
|
43
|
+
"@danypops/vehicle-client-pi": "^0.45.2",
|
|
44
44
|
"@danypops/vehicle-conformance": "^0.4.0",
|
|
45
45
|
"@types/semver": "^7.7.1",
|
|
46
46
|
"follow-redirects": "^1.16.0"
|
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import type { ServiceInstallDeps } from "@danypops/vehicle-server/service";
|
|
3
3
|
import { readInstalledPackagesAcrossScopes } from "../packages/installed.ts";
|
|
4
4
|
import { resolveDaemonServiceSpec } from "../daemon/daemon-service.ts";
|
|
5
|
+
import { RECONCILE_INTERVAL_DEFAULT_MS } from "../shared/constants.ts";
|
|
5
6
|
|
|
6
7
|
export interface ServiceUnitDiagnostic {
|
|
7
8
|
code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE";
|
|
@@ -27,8 +28,15 @@ interface ArmadaStatus {
|
|
|
27
28
|
vehicles?: Array<{ name?: string; executable?: string; nativeStatus?: string; ready?: boolean; nativePid?: number }>;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Armada's own "vehicle-reconcile" maintenance task already self-heals a Vehicle whose content
|
|
33
|
+
* (not just its declared version) drifted, within one RECONCILE_INTERVAL_DEFAULT_MS resync --
|
|
34
|
+
* see @danypops/armada's contentSignature. This grace period is set well past two full resync
|
|
35
|
+
* cycles so SERVICE_STALE_CODE only ever fires as a backstop for a genuinely broken operator
|
|
36
|
+
* (the reconcile task itself not running, or erroring every pass), never as the routine, expected
|
|
37
|
+
* path for ordinary content drift a healthy operator already corrects on its own.
|
|
38
|
+
*/
|
|
39
|
+
const STALE_CODE_GRACE_MS = RECONCILE_INTERVAL_DEFAULT_MS * 2;
|
|
32
40
|
|
|
33
41
|
/** Undefined (never throws) when `pid` isn't a running process this host can inspect, or `ps` isn't available. */
|
|
34
42
|
function processStartTimeMs(pid: number, deps: ServiceDoctorDeps): number | undefined {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* on disk instead of asking for one more declaration.
|
|
27
27
|
*/
|
|
28
28
|
import { spawn } from "node:child_process";
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
29
30
|
import { readdirSync, readFileSync } from "node:fs";
|
|
30
31
|
import { join } from "node:path";
|
|
31
32
|
import { fileURLToPath } from "node:url";
|
|
@@ -81,6 +82,7 @@ interface ResolvedDaemonEntrypoint {
|
|
|
81
82
|
restartOnFailure?: boolean;
|
|
82
83
|
restartSec?: number;
|
|
83
84
|
version: string;
|
|
85
|
+
contentSignature?: string;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
interface InstalledPackageJson {
|
|
@@ -99,6 +101,20 @@ function readPackageJson(dir: string): InstalledPackageJson | undefined {
|
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/**
|
|
105
|
+
* A hash of the installed package's own package.json bytes -- a content-derived signal distinct
|
|
106
|
+
* from its declared `version` string, so Armada's own drift detection can react to real content
|
|
107
|
+
* changes (a local rebuild, a dependency bump) even when a caller never bumped the version.
|
|
108
|
+
* Undefined (never throws) when the file can't be read.
|
|
109
|
+
*/
|
|
110
|
+
function packageContentSignature(dir: string): string | undefined {
|
|
111
|
+
try {
|
|
112
|
+
return createHash("sha256").update(readFileSync(join(dir, "package.json"))).digest("hex");
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
function firstBinPath(pkg: InstalledPackageJson): string | undefined {
|
|
103
119
|
if (typeof pkg.bin === "string") return pkg.bin;
|
|
104
120
|
if (pkg.bin && typeof pkg.bin === "object") return Object.values(pkg.bin)[0];
|
|
@@ -156,11 +172,18 @@ function resolveDependencyCandidate(depDir: string, depName: string): ResolvedDa
|
|
|
156
172
|
restartOnFailure: depManifest.restartOnFailure,
|
|
157
173
|
restartSec: depManifest.restartSec,
|
|
158
174
|
version: dep.version,
|
|
175
|
+
contentSignature: packageContentSignature(depDir),
|
|
159
176
|
};
|
|
160
177
|
}
|
|
161
178
|
const depBin = firstBinPath(dep);
|
|
162
179
|
if (depBin && dependsOnVehicle(dep) && dep.version) {
|
|
163
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
binPath: join(depDir, depBin),
|
|
182
|
+
args: ["serve"],
|
|
183
|
+
name: unscopedName(dep.name ?? depName),
|
|
184
|
+
version: dep.version,
|
|
185
|
+
contentSignature: packageContentSignature(depDir),
|
|
186
|
+
};
|
|
164
187
|
}
|
|
165
188
|
return undefined;
|
|
166
189
|
}
|
|
@@ -208,7 +231,13 @@ export function detectVehicleDaemonService(
|
|
|
208
231
|
|
|
209
232
|
const ownBin = firstBinPath(own);
|
|
210
233
|
if (ownBin && dependsOnVehicle(own) && own.version) {
|
|
211
|
-
return {
|
|
234
|
+
return {
|
|
235
|
+
binPath: join(packageDir, ownBin),
|
|
236
|
+
args: ["serve"],
|
|
237
|
+
name: unscopedName(own.name ?? fallbackName),
|
|
238
|
+
version: own.version,
|
|
239
|
+
contentSignature: packageContentSignature(packageDir),
|
|
240
|
+
};
|
|
212
241
|
}
|
|
213
242
|
|
|
214
243
|
// A pi-X package can carry OTHER Vehicle-shaped dependencies for unrelated reasons (e.g.
|
|
@@ -256,6 +285,7 @@ function buildSpec(entry: ResolvedDaemonEntrypoint): ServiceSpec {
|
|
|
256
285
|
name: entry.name,
|
|
257
286
|
displayName: entry.displayName,
|
|
258
287
|
version: entry.version,
|
|
288
|
+
...(entry.contentSignature === undefined ? {} : { contentSignature: entry.contentSignature }),
|
|
259
289
|
binPath: entry.binPath,
|
|
260
290
|
args: entry.args,
|
|
261
291
|
handlePath: paths.handle,
|
|
@@ -294,6 +324,7 @@ export function resolveDaemonServiceSpec(piHome: string, source: string): Resolv
|
|
|
294
324
|
restartOnFailure: manifest.restartOnFailure,
|
|
295
325
|
restartSec: manifest.restartSec,
|
|
296
326
|
version: pkg.version ?? "0.0.0",
|
|
327
|
+
contentSignature: packageContentSignature(packageDir),
|
|
297
328
|
}),
|
|
298
329
|
};
|
|
299
330
|
}
|
|
@@ -109,7 +109,7 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
109
109
|
if (!VERSION_DIRECTORY_RE.test(version)) throw new Error(`cannot isolate ${packageName}: installed package version is unsafe`);
|
|
110
110
|
|
|
111
111
|
const packageRoot = join(this.stateDirectory, "services", installKey(packageName));
|
|
112
|
-
const finalDirectory = join(packageRoot, version);
|
|
112
|
+
const finalDirectory = join(packageRoot, `${version}-f${INSTALL_FORMAT_VERSION}`);
|
|
113
113
|
const finalPiHome = isolatedPiHome(finalDirectory);
|
|
114
114
|
const finalPackageJson = resolvedPackageJson(finalPiHome, packageName);
|
|
115
115
|
mkdirSync(packageRoot, { recursive: true, mode: 0o700 });
|
|
@@ -123,7 +123,6 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
127
126
|
const temporaryDirectory = join(packageRoot, `.${version}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
128
127
|
const temporaryPiHome = isolatedPiHome(temporaryDirectory);
|
|
129
128
|
const projectDirectory = join(temporaryPiHome, "npm");
|
|
@@ -164,6 +163,10 @@ export class IsolatedDaemonPackageMaterializer implements DaemonPackageMateriali
|
|
|
164
163
|
throw new Error(`isolated npm install did not resolve ${packageName}@${version}`);
|
|
165
164
|
}
|
|
166
165
|
try {
|
|
166
|
+
// A damaged directory for this exact format is replaced only after the complete
|
|
167
|
+
// candidate exists. Format migrations use a different final path, so the service's
|
|
168
|
+
// currently registered executable remains present throughout installation.
|
|
169
|
+
if (existsSync(finalDirectory)) rmSync(finalDirectory, { recursive: true, force: true });
|
|
167
170
|
renameSync(temporaryDirectory, finalDirectory);
|
|
168
171
|
publishedByThisCall = true;
|
|
169
172
|
} catch (error) {
|
|
@@ -60,6 +60,7 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
60
60
|
name: "web-spider-daemon",
|
|
61
61
|
displayName: undefined,
|
|
62
62
|
version: "1.0.0",
|
|
63
|
+
contentSignature: expect.any(String) as unknown as string,
|
|
63
64
|
binPath: join(piHome, "npm", "node_modules", "@danypops/web-spider-daemon", "dist/cli.js"),
|
|
64
65
|
args: ["serve"],
|
|
65
66
|
handlePath: expect.stringContaining("web-spider-daemon") as unknown as string,
|
|
@@ -70,6 +71,32 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
70
71
|
});
|
|
71
72
|
});
|
|
72
73
|
|
|
74
|
+
it("derives contentSignature from the installed package.json's own bytes, changing when content drifts even though version does not", () => {
|
|
75
|
+
const piHome = fakePiHome();
|
|
76
|
+
writePackage(piHome, "@danypops/web-spider-daemon", { binPath: "dist/cli.js", args: ["serve"] });
|
|
77
|
+
const first = resolveDaemonServiceSpec(piHome, "npm:@danypops/web-spider-daemon");
|
|
78
|
+
expect(first.ok).toBe(true);
|
|
79
|
+
if (!first.ok) throw new Error("expected ok");
|
|
80
|
+
|
|
81
|
+
// Same declared version, different real bytes -- e.g. a local rebuild or a dependency bump
|
|
82
|
+
// that never touched the version field.
|
|
83
|
+
writeFileSync(
|
|
84
|
+
join(piHome, "npm", "node_modules", "@danypops/web-spider-daemon", "package.json"),
|
|
85
|
+
JSON.stringify({
|
|
86
|
+
name: "@danypops/web-spider-daemon",
|
|
87
|
+
version: "1.0.0",
|
|
88
|
+
dependencies: { "some-new-dep": "^1.0.0" },
|
|
89
|
+
packed: { daemonService: { binPath: "dist/cli.js", args: ["serve"] } },
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
const second = resolveDaemonServiceSpec(piHome, "npm:@danypops/web-spider-daemon");
|
|
93
|
+
expect(second.ok).toBe(true);
|
|
94
|
+
if (!second.ok) throw new Error("expected ok");
|
|
95
|
+
|
|
96
|
+
expect(second.spec.version).toBe(first.spec.version);
|
|
97
|
+
expect(second.spec.contentSignature).not.toBe(first.spec.contentSignature);
|
|
98
|
+
});
|
|
99
|
+
|
|
73
100
|
it("honors an explicit name/displayName override instead of deriving one from the npm package name", () => {
|
|
74
101
|
const piHome = fakePiHome();
|
|
75
102
|
writePackage(piHome, "@danypops/web-spider-daemon", { binPath: "dist/cli.js", name: "web-spider", displayName: "Web Spider" });
|
|
@@ -122,6 +149,7 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
122
149
|
name: "papyrus",
|
|
123
150
|
displayName: undefined,
|
|
124
151
|
version: "1.0.0",
|
|
152
|
+
contentSignature: expect.any(String) as unknown as string,
|
|
125
153
|
binPath: join(dir, "src/cli.ts"),
|
|
126
154
|
args: ["serve"],
|
|
127
155
|
handlePath: expect.stringContaining("papyrus") as unknown as string,
|
|
@@ -118,6 +118,23 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
118
118
|
expect(existsSync(versionDirectory)).toBe(false);
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
it("uses format-versioned paths so a migration never removes the currently registered executable", async () => {
|
|
122
|
+
const root = temporaryRoot();
|
|
123
|
+
const piHome = join(root, "pi");
|
|
124
|
+
const stateDirectory = join(root, "state");
|
|
125
|
+
const legacyExecutable = join(stateDirectory, "services", "daemon", "1.0.0", "pi-home", "npm", "node_modules", "daemon", "cli.ts");
|
|
126
|
+
mkdirSync(join(legacyExecutable, ".."), { recursive: true });
|
|
127
|
+
writeFileSync(legacyExecutable, "// running legacy executable\n");
|
|
128
|
+
writeInstalledPackage(piHome, "daemon", "1.0.0");
|
|
129
|
+
const materializer = new IsolatedDaemonPackageMaterializer(stateDirectory, writeFakeNpm(root));
|
|
130
|
+
|
|
131
|
+
const installed = await materializer.materialize(piHome, "npm:daemon");
|
|
132
|
+
|
|
133
|
+
expect(existsSync(legacyExecutable)).toBe(true);
|
|
134
|
+
expect(installed.piHome).toContain("1.0.0-f1");
|
|
135
|
+
installed.commit();
|
|
136
|
+
});
|
|
137
|
+
|
|
121
138
|
it("retains only the active and immediately previous committed versions", async () => {
|
|
122
139
|
const root = temporaryRoot();
|
|
123
140
|
const piHome = join(root, "pi");
|
|
@@ -130,8 +147,8 @@ describe("IsolatedDaemonPackageMaterializer", () => {
|
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
expect(readdirSync(join(stateDirectory, "services", "daemon")).filter((name) => !name.startsWith(".")).sort()).toEqual([
|
|
133
|
-
"1.1.0",
|
|
134
|
-
"1.2.0",
|
|
150
|
+
"1.1.0-f1",
|
|
151
|
+
"1.2.0-f1",
|
|
135
152
|
]);
|
|
136
153
|
});
|
|
137
154
|
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { createArmadaTestHarness } from "@danypops/armada/testing";
|
|
6
6
|
import type { ServiceDoctorDeps } from "../src/adoption/service-doctor.ts";
|
|
7
7
|
import { checkServiceUnitPaths } from "../src/adoption/service-doctor.ts";
|
|
8
|
+
import { RECONCILE_INTERVAL_DEFAULT_MS } from "../src/shared/constants.ts";
|
|
8
9
|
|
|
9
10
|
const roots: string[] = [];
|
|
10
11
|
afterEach(() => {
|
|
@@ -126,16 +127,19 @@ describe("checkServiceUnitPaths", () => {
|
|
|
126
127
|
});
|
|
127
128
|
});
|
|
128
129
|
|
|
129
|
-
it("reports a running Vehicle whose process predates its own package.json as stale code, not an error", () => {
|
|
130
|
+
it("reports a running Vehicle whose process predates its own package.json well past Armada's own reconcile cadence as stale code, not an error", () => {
|
|
130
131
|
const home = piHome(["npm:fakedaemon"]);
|
|
131
132
|
const dir = installDaemonPackage(home, "fakedaemon");
|
|
133
|
+
// Past two full reconcile cycles -- a genuinely broken operator, not a gap a healthy
|
|
134
|
+
// vehicle-reconcile pass would already have self-healed.
|
|
135
|
+
const gapMs = RECONCILE_INTERVAL_DEFAULT_MS * 2 + 60_000;
|
|
132
136
|
const deps: ServiceDoctorDeps = {
|
|
133
137
|
armadaCliPath: "/armada/cli.js",
|
|
134
138
|
fileExists: () => true,
|
|
135
139
|
getMtimeMs: () => Date.now(),
|
|
136
140
|
runCommand: (command) =>
|
|
137
141
|
command === "ps"
|
|
138
|
-
? { ok: true, output: new Date(Date.now() -
|
|
142
|
+
? { ok: true, output: new Date(Date.now() - gapMs).toString() }
|
|
139
143
|
: {
|
|
140
144
|
ok: true,
|
|
141
145
|
output: JSON.stringify({
|
|
@@ -159,6 +163,26 @@ describe("checkServiceUnitPaths", () => {
|
|
|
159
163
|
});
|
|
160
164
|
});
|
|
161
165
|
|
|
166
|
+
it("no longer flags a gap within Armada's own reconcile cadence -- a healthy self-healing operator closes that within one resync, not Doctor", () => {
|
|
167
|
+
const home = piHome(["npm:fakedaemon"]);
|
|
168
|
+
const dir = installDaemonPackage(home, "fakedaemon");
|
|
169
|
+
const deps: ServiceDoctorDeps = {
|
|
170
|
+
armadaCliPath: "/armada/cli.js",
|
|
171
|
+
fileExists: () => true,
|
|
172
|
+
getMtimeMs: () => Date.now(),
|
|
173
|
+
runCommand: (command) =>
|
|
174
|
+
command === "ps"
|
|
175
|
+
? { ok: true, output: new Date(Date.now() - 60_000).toString() }
|
|
176
|
+
: {
|
|
177
|
+
ok: true,
|
|
178
|
+
output: JSON.stringify({
|
|
179
|
+
vehicles: [{ name: "fakedaemon", executable: join(dir, "cli.ts"), nativeStatus: "running", nativePid: 4242 }],
|
|
180
|
+
}),
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
expect(checkServiceUnitPaths(home, undefined, deps)).toEqual({ ok: true, diagnostics: [], checked: 1 });
|
|
184
|
+
});
|
|
185
|
+
|
|
162
186
|
it("never flags a running Vehicle whose process started after its own package.json was last written", () => {
|
|
163
187
|
const home = piHome(["npm:fakedaemon"]);
|
|
164
188
|
const dir = installDaemonPackage(home, "fakedaemon");
|