@danypops/pi-packed 0.28.15 → 0.28.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../service/src/adoption/check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,oBAAoB,EAAqB,MAAM,YAAY,CAAC;AAE1E,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,oBAAoB,EAAE,CAAA;KAAE,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACzE;AAED,qBAAa,oBAAqB,YAAW,cAAc;IAC1D,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;CAGxE;AAyBD,QAAA,MAAM,eAAe,wDAAyD,CAAC;AA0B/E,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAoCpH;AAsCD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAIrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnE;AA4FD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;0EAI0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAYrI;AAueD,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6GxG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAwB5E"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../service/src/adoption/check.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,oBAAoB,EAAqB,MAAM,YAAY,CAAC;AAE1E,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,oBAAoB,EAAE,CAAA;KAAE,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACzE;AAED,qBAAa,oBAAqB,YAAW,cAAc;IAC1D,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;CAGxE;AAyBD,QAAA,MAAM,eAAe,wDAAyD,CAAC;AA0B/E,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAoCpH;AAsCD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAIrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnE;AA4FD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;0EAI0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAYrI;AAweD,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6GxG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAwB5E"}
@@ -1,6 +1,6 @@
1
1
  import type { ServiceInstallDeps } from "@danypops/vehicle-server/service";
2
2
  export interface ServiceUnitDiagnostic {
3
- code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE";
3
+ code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE" | "SERVICE_VERSION_MISMATCH" | "SERVICE_NOT_REGISTERED" | "SERVICE_DESCRIPTOR_DRIFT" | "SERVICE_REQUIREMENT_UNRESOLVED";
4
4
  severity: "error" | "warning";
5
5
  package: string;
6
6
  unitName: string;
@@ -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;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"}
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,GAC5G,0BAA0B,GAAG,wBAAwB,GAAG,0BAA0B,GAAG,gCAAgC,CAAC;IACzH,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,CA+GrB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAO5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.28.15",
3
+ "version": "0.28.17",
4
4
  "description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,17 +42,18 @@
42
42
  "jiti": "^2.7.0",
43
43
  "malevich-tui-components": "^0.32.1",
44
44
  "publint": "0.3.22",
45
- "semver": "^7.8.5"
45
+ "semver": "^7.8.5",
46
+ "typescript": "^5.9.3"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@danypops/pi-tui-harness": "^0.0.1",
49
- "@danypops/vehicle-client-pi": "^0.45.3",
50
- "@danypops/vehicle-conformance": "^0.4.1",
50
+ "@danypops/vehicle-client-pi": "^0.48.3",
51
+ "@danypops/vehicle-conformance": "^0.4.2",
51
52
  "@types/semver": "^7.7.1",
52
53
  "follow-redirects": "^1.16.0"
53
54
  },
54
55
  "peerDependencies": {
55
- "@danypops/vehicle-client-pi": "^0.45.0",
56
+ "@danypops/vehicle-client-pi": "^0.48.3",
56
57
  "@earendil-works/pi-coding-agent": "*",
57
58
  "@earendil-works/pi-tui": "*",
58
59
  "typebox": "*"
@@ -0,0 +1,431 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { arch, cpus, platform, tmpdir } from "node:os";
5
+ import { basename, dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { AuthenticatedRpcClient } from "@danypops/vehicle-client/rpc-client";
8
+ import { readDaemonHandle } from "@danypops/vehicle-server/paths";
9
+ import { daemonOptions } from "../src/daemon/daemon.ts";
10
+ import { probe } from "../src/daemon/client.ts";
11
+ import { runDaemonProcess } from "@danypops/vehicle-server/daemon";
12
+ import type { OperationInputs, OperationName, OperationOutputs } from "../src/daemon/service.ts";
13
+ import { writeIndex } from "../src/index/build-index.ts";
14
+ import { dbPath, openDb, replaceAll } from "../src/packages/db.ts";
15
+ import type { Pkg } from "../src/packages/package.ts";
16
+ import { HttpRegistry } from "../src/registry/registry.ts";
17
+ import { resolvePackedPaths } from "../src/shared/paths.ts";
18
+
19
+ const MAX_COMMAND_OUTPUT_BYTES = 256_000;
20
+ const MAX_ARTIFACT_BYTES = 256_000;
21
+ const SERVICE_READY_DEADLINE_MS = 15_000;
22
+ const FREQUENCY_WINDOW_DAYS = 14;
23
+ const DOCTOR_EXTENSION_COUNT = 24;
24
+ const BENCHMARK_FILE = ".artifacts/benchmarks/packed-baseline.json";
25
+
26
+ type BenchmarkMode =
27
+ | "idle-polling"
28
+ | "cached-reads-cold"
29
+ | "cached-reads-warm"
30
+ | "doctor-cold"
31
+ | "doctor-warm"
32
+ | "doctor-concurrent"
33
+ | "catalog-sync-cold"
34
+ | "catalog-sync-warm"
35
+ | "index-build-cold"
36
+ | "index-build-warm"
37
+ | "updates-project";
38
+
39
+ export interface BenchmarkScenario {
40
+ readonly name: BenchmarkMode;
41
+ readonly warmup: number;
42
+ readonly repetitions: number;
43
+ readonly concurrency: number;
44
+ readonly deadlineMs: number;
45
+ }
46
+
47
+ export const PACKED_BENCHMARK_SCENARIOS: readonly BenchmarkScenario[] = Object.freeze([
48
+ { name: "idle-polling", warmup: 0, repetitions: 1, concurrency: 1, deadlineMs: 30_000 },
49
+ { name: "cached-reads-cold", warmup: 0, repetitions: 1, concurrency: 1, deadlineMs: 30_000 },
50
+ { name: "cached-reads-warm", warmup: 1, repetitions: 5, concurrency: 1, deadlineMs: 30_000 },
51
+ { name: "doctor-cold", warmup: 0, repetitions: 1, concurrency: 1, deadlineMs: 120_000 },
52
+ { name: "doctor-warm", warmup: 1, repetitions: 3, concurrency: 1, deadlineMs: 120_000 },
53
+ { name: "doctor-concurrent", warmup: 0, repetitions: 2, concurrency: 2, deadlineMs: 120_000 },
54
+ { name: "catalog-sync-cold", warmup: 0, repetitions: 1, concurrency: 1, deadlineMs: 60_000 },
55
+ { name: "catalog-sync-warm", warmup: 1, repetitions: 3, concurrency: 1, deadlineMs: 60_000 },
56
+ { name: "index-build-cold", warmup: 0, repetitions: 1, concurrency: 1, deadlineMs: 120_000 },
57
+ { name: "index-build-warm", warmup: 1, repetitions: 3, concurrency: 1, deadlineMs: 120_000 },
58
+ { name: "updates-project", warmup: 1, repetitions: 5, concurrency: 1, deadlineMs: 30_000 },
59
+ ]);
60
+
61
+ function isBenchmarkMode(value: string | undefined): value is BenchmarkMode {
62
+ return PACKED_BENCHMARK_SCENARIOS.some((scenario) => scenario.name === value);
63
+ }
64
+
65
+ interface BenchmarkClient {
66
+ invoke(operation: string, input: Record<string, unknown>): Promise<unknown>;
67
+ }
68
+
69
+ function record(value: unknown): Record<string, unknown> {
70
+ return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
71
+ }
72
+
73
+ function digest(value: unknown): string {
74
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
75
+ }
76
+
77
+ /** Runs one bounded workload and returns only a correctness digest. */
78
+ export async function runBenchmarkWorkload(mode: BenchmarkMode, client: BenchmarkClient, projectRoot: string): Promise<string> {
79
+ if (mode === "idle-polling") {
80
+ await Bun.sleep(1_000);
81
+ return digest({ elapsed: "bounded-idle-window" });
82
+ }
83
+ if (mode === "cached-reads-cold" || mode === "cached-reads-warm") {
84
+ const catalog = record(await client.invoke("package.catalog", {}));
85
+ const index = record(await client.invoke("package.index", {}));
86
+ const updates = record(await client.invoke("package.updates", {}));
87
+ return digest({
88
+ catalogPackages: Array.isArray(catalog.packages) ? catalog.packages.length : 0,
89
+ indexPackages: Array.isArray(index.packages) ? index.packages.length : 0,
90
+ updates: Array.isArray(updates.updates) ? updates.updates.length : 0,
91
+ });
92
+ }
93
+ if (mode === "doctor-cold" || mode === "doctor-warm" || mode === "doctor-concurrent") {
94
+ const report = record(await client.invoke("doctor.run", { projectRoot }));
95
+ return digest({ ok: report.ok, scanned: report.scanned, conflicts: Array.isArray(report.conflicts) ? report.conflicts.length : 0 });
96
+ }
97
+ if (mode === "catalog-sync-cold" || mode === "catalog-sync-warm") {
98
+ const result = record(await client.invoke("package.catalog.sync", {}));
99
+ return digest({ synced: result.synced });
100
+ }
101
+ if (mode === "index-build-cold" || mode === "index-build-warm") {
102
+ const result = record(await client.invoke("package.index.build", {}));
103
+ return digest({ packages: Array.isArray(result.packages) ? result.packages.length : 0, truncated: result.truncated });
104
+ }
105
+ const result = record(await client.invoke("package.updates.project", { projectRoot }));
106
+ return digest({ updates: Array.isArray(result.updates) ? result.updates.length : 0 });
107
+ }
108
+
109
+ interface FrequencyObservation {
110
+ readonly since: string;
111
+ readonly until: string;
112
+ readonly days: number;
113
+ readonly calls: number;
114
+ }
115
+
116
+ interface AvailableProjection extends FrequencyObservation {
117
+ readonly status: "available";
118
+ readonly source: "packed metrics.query";
119
+ readonly callsPerDay: number;
120
+ readonly projectedDailyCpuMs: number;
121
+ readonly projectedDailyWallMs: number;
122
+ }
123
+
124
+ type FrequencyProjection = AvailableProjection | { readonly status: "unavailable" };
125
+
126
+ export function doctorDailyProjection(observation: FrequencyObservation, cpuMsPerCall: number, wallMsPerCall: number): AvailableProjection {
127
+ const callsPerDay = observation.calls / observation.days;
128
+ return {
129
+ status: "available",
130
+ source: "packed metrics.query",
131
+ ...observation,
132
+ callsPerDay,
133
+ projectedDailyCpuMs: callsPerDay * cpuMsPerCall,
134
+ projectedDailyWallMs: callsPerDay * wallMsPerCall,
135
+ };
136
+ }
137
+
138
+ interface ArtifactInput {
139
+ readonly generatedAt: string;
140
+ readonly environment: { readonly platform: string; readonly architecture: string; readonly runtime: string; readonly logicalCpuCount: number };
141
+ readonly frequency: FrequencyProjection;
142
+ readonly results: readonly { readonly name: string; readonly phase?: string; readonly benchmark: unknown }[];
143
+ }
144
+
145
+ export function benchmarkArtifact(input: ArtifactInput): ArtifactInput & { readonly schemaVersion: 1; readonly networkBytes: "unavailable" } {
146
+ const artifact = { schemaVersion: 1 as const, networkBytes: "unavailable" as const, ...input };
147
+ if (Buffer.byteLength(JSON.stringify(artifact)) > MAX_ARTIFACT_BYTES) throw new Error("benchmark artifact exceeds 256000 bytes");
148
+ return artifact;
149
+ }
150
+
151
+ const FIXTURE_PACKAGES: readonly Pkg[] = Object.freeze(
152
+ Array.from({ length: 24 }, (_, index) => ({
153
+ name: `bench-pi-${index}`,
154
+ version: "1.0.0",
155
+ description: `Synthetic Pi package ${index}`,
156
+ date: "2026-01-01T00:00:00.000Z",
157
+ packageEvidence: { shape: "manifest" as const, verified: false, evidence: ["synthetic benchmark fixture"] },
158
+ publication: { trustedPublisher: "unknown" as const },
159
+ })),
160
+ );
161
+
162
+ function startRegistryFixture(): { readonly baseUrl: string; close(): void } {
163
+ const server = Bun.serve({
164
+ hostname: "127.0.0.1",
165
+ port: 0,
166
+ async fetch(request) {
167
+ await Bun.sleep(10);
168
+ const url = new URL(request.url);
169
+ if (url.pathname === "/-/v1/search") {
170
+ const from = Math.max(0, Number(url.searchParams.get("from") ?? 0));
171
+ const size = Math.min(20, Math.max(1, Number(url.searchParams.get("size") ?? 5)));
172
+ const objects = FIXTURE_PACKAGES.slice(from, from + size).map((item) => ({ package: item }));
173
+ return Response.json({ total: FIXTURE_PACKAGES.length, objects });
174
+ }
175
+ const encodedName = url.pathname.replace(/^\//, "").replace(/\/latest$/, "");
176
+ const name = decodeURIComponent(encodedName);
177
+ const pkg = FIXTURE_PACKAGES.find((item) => item.name === name);
178
+ if (!pkg) return new Response("missing", { status: 404 });
179
+ if (url.pathname.endsWith("/latest")) {
180
+ return Response.json({ ...pkg, license: "MIT", keywords: ["pi-package"], pi: { extensions: ["extension/index.ts"] } });
181
+ }
182
+ return Response.json({ modified: pkg.date, readme: "Synthetic benchmark package." });
183
+ },
184
+ });
185
+ return { baseUrl: `http://127.0.0.1:${server.port}`, close: () => server.stop(true) };
186
+ }
187
+
188
+ function isolatedEnvironment(root: string, idlePolling: boolean): Record<string, string> {
189
+ return {
190
+ ...(process.env as Record<string, string>),
191
+ HOME: root,
192
+ PI_PACKED_HOME: join(root, "state"),
193
+ PI_PACKED_PI_HOME: join(root, "pi-home"),
194
+ PI_PACKED_WATCH_SECS: idlePolling ? "0.1" : "0",
195
+ PI_PACKED_CATALOG_SECS: "0",
196
+ PI_PACKED_INDEX_SECS: "0",
197
+ PI_PACKED_RECONCILE_SECS: "0",
198
+ PI_PACKED_IDLE_SECS: "600",
199
+ PI_PACKED_PI_BIN: "/usr/bin/false",
200
+ PI_OFFLINE: "1",
201
+ };
202
+ }
203
+
204
+ async function initializeSyntheticHome(root: string, includeIndex: boolean): Promise<void> {
205
+ const state = join(root, "state");
206
+ const piHome = join(root, "pi-home");
207
+ const projectHome = join(root, "project", ".pi");
208
+ await mkdir(state, { recursive: true });
209
+ await mkdir(piHome, { recursive: true });
210
+ await mkdir(projectHome, { recursive: true });
211
+ const configured = FIXTURE_PACKAGES.slice(0, DOCTOR_EXTENSION_COUNT).map((pkg) => `npm:${pkg.name}@0.1.0`);
212
+ await writeFile(join(piHome, "settings.json"), JSON.stringify({ packages: configured }));
213
+ await writeFile(join(projectHome, "settings.json"), JSON.stringify({ packages: [] }));
214
+ for (const [index, pkg] of FIXTURE_PACKAGES.slice(0, DOCTOR_EXTENSION_COUNT).entries()) {
215
+ const directory = join(piHome, "npm", "node_modules", pkg.name);
216
+ await mkdir(join(directory, "extension"), { recursive: true });
217
+ await writeFile(join(directory, "package.json"), JSON.stringify({ name: pkg.name, version: pkg.version, pi: { extensions: ["extension/index.ts"] } }));
218
+ await writeFile(join(directory, "extension", "index.ts"), `export default function (pi: any) { pi.registerTool({ name: "bench_tool_${index}" }); }\n`);
219
+ }
220
+ const database = openDb(dbPath(state));
221
+ try {
222
+ replaceAll(database, [...FIXTURE_PACKAGES], "synthetic:localhost");
223
+ } finally {
224
+ database.close();
225
+ }
226
+ await writeFile(join(state, "updates.json"), JSON.stringify({ checkedAt: "2026-01-01T00:00:00.000Z", updates: [] }));
227
+ if (includeIndex) await writeIndex(join(state, "index.json"), { generatedAt: new Date().toISOString(), packages: [], truncated: false });
228
+ }
229
+
230
+ function commandResult(
231
+ command: string,
232
+ arguments_: readonly string[],
233
+ environment: Record<string, string> = process.env as Record<string, string>,
234
+ ): Promise<{ readonly ok: boolean; readonly stdout: string; readonly stderr: string }> {
235
+ return new Promise((resolveCommand) => {
236
+ execFile(
237
+ command,
238
+ [...arguments_],
239
+ { env: environment, encoding: "utf8", maxBuffer: MAX_COMMAND_OUTPUT_BYTES, timeout: 240_000 },
240
+ (error, stdout, stderr) => {
241
+ resolveCommand({ ok: !error, stdout, stderr });
242
+ },
243
+ );
244
+ });
245
+ }
246
+
247
+ async function command(commandName: string, arguments_: readonly string[], environment?: Record<string, string>): Promise<string> {
248
+ const result = await commandResult(commandName, arguments_, environment);
249
+ if (!result.ok) throw new Error(`benchmark command failed: ${basename(commandName)}`);
250
+ return result.stdout;
251
+ }
252
+
253
+ async function waitForService(root: string, environment: Record<string, string>): Promise<void> {
254
+ const paths = resolvePackedPaths({ env: environment, home: root, uid: process.getuid?.() ?? 0 });
255
+ const deadline = Date.now() + SERVICE_READY_DEADLINE_MS;
256
+ while (Date.now() < deadline) {
257
+ if (readDaemonHandle(paths.handle)) return;
258
+ await Bun.sleep(50);
259
+ }
260
+ throw new Error("benchmark service readiness deadline exceeded");
261
+ }
262
+
263
+ async function stopUnit(unit: string): Promise<void> {
264
+ try {
265
+ await command("systemctl", ["--user", "stop", unit]);
266
+ } catch {
267
+ // Cleanup remains best-effort after a failed transient unit.
268
+ }
269
+ }
270
+
271
+ async function runScenario(scenario: BenchmarkScenario, registryBase: string, index: number): Promise<{ readonly name: string; readonly phase: string; readonly benchmark: unknown }> {
272
+ const root = await mkdtemp(join(tmpdir(), "packed-benchmark-"));
273
+ const includeIndex = !scenario.name.startsWith("index-build");
274
+ await initializeSyntheticHome(root, includeIndex);
275
+ const environment = isolatedEnvironment(root, scenario.name === "idle-polling");
276
+ const vehicle = `packed-bench-${index}`;
277
+ const unit = `armada-${vehicle}.service`;
278
+ const benchmarkFile = fileURLToPath(import.meta.url);
279
+ const manifestPath = join(root, "armada.json");
280
+ const paths = resolvePackedPaths({ env: environment, home: root, uid: process.getuid?.() ?? 0 });
281
+ await writeFile(
282
+ manifestPath,
283
+ JSON.stringify({ schemaVersion: 1, vehicles: [{ name: vehicle, version: "0.0.0", executable: process.execPath, arguments: [benchmarkFile, "serve", root, registryBase], handlePath: paths.handle, restart: { policy: "never" }, readiness: { timeoutMs: SERVICE_READY_DEADLINE_MS, pollIntervalMs: 50 } }] }),
284
+ );
285
+ try {
286
+ const names = ["HOME", "PI_PACKED_HOME", "PI_PACKED_PI_HOME", "PI_PACKED_WATCH_SECS", "PI_PACKED_CATALOG_SECS", "PI_PACKED_INDEX_SECS", "PI_PACKED_RECONCILE_SECS", "PI_PACKED_IDLE_SECS", "PI_PACKED_PI_BIN", "PI_OFFLINE"];
287
+ await command("systemd-run", [
288
+ "--user",
289
+ `--unit=${unit}`,
290
+ "--collect",
291
+ "--property=Type=exec",
292
+ "--property=Restart=no",
293
+ ...names.map((name) => `--setenv=${name}=${environment[name] ?? ""}`),
294
+ process.execPath,
295
+ benchmarkFile,
296
+ "serve",
297
+ root,
298
+ registryBase,
299
+ ]);
300
+ await waitForService(root, environment);
301
+ const armadaArguments = [
302
+ "benchmark", vehicle, "--manifest", manifestPath,
303
+ "--exec", process.execPath,
304
+ "--arg", benchmarkFile, "--arg", "workload", "--arg", scenario.name, "--arg", root,
305
+ "--warmup", String(scenario.warmup), "--repetitions", String(scenario.repetitions), "--concurrency", String(scenario.concurrency),
306
+ "--deadline-ms", String(scenario.deadlineMs), "--sample-ms", "25", "--max-output-bytes", "1024", "--json",
307
+ ];
308
+ const armadaCli = process.env.ARMADA_CLI;
309
+ const armadaResult = armadaCli
310
+ ? await commandResult(process.execPath, [armadaCli, ...armadaArguments])
311
+ : await commandResult(process.env.ARMADA_BIN ?? "armada", armadaArguments);
312
+ if (armadaResult.stdout.trim().length === 0) {
313
+ throw new Error(`${scenario.name} benchmark produced no JSON: ${armadaResult.stderr.trim().slice(0, 500) || "no diagnostic"}`);
314
+ }
315
+ const output = JSON.parse(armadaResult.stdout) as {
316
+ benchmark?: { workload?: { failureCodes?: Record<string, number> } };
317
+ diagnostics?: Array<{ code?: string }>;
318
+ };
319
+ if (!armadaResult.ok) {
320
+ const failureCode = Object.keys(output.benchmark?.workload?.failureCodes ?? {})[0] ?? output.diagnostics?.[0]?.code ?? "unknown";
321
+ throw new Error(`${scenario.name} benchmark failed: ${failureCode}`);
322
+ }
323
+ if (!output.benchmark) throw new Error("benchmark result missing");
324
+ const phase = scenario.name.startsWith("catalog-sync") || scenario.name.startsWith("index-build") ? "localhost-registry-wait-and-local-compute" : scenario.name === "doctor-cold" || scenario.name === "doctor-warm" || scenario.name === "doctor-concurrent" ? "sandboxed-subprocess-and-local-compute" : "local-compute";
325
+ return { name: scenario.name, phase, benchmark: output.benchmark };
326
+ } finally {
327
+ await stopUnit(unit);
328
+ await rm(root, { recursive: true, force: true });
329
+ }
330
+ }
331
+
332
+ async function connectBenchmarkClient(root: string): Promise<BenchmarkClient> {
333
+ const environment = isolatedEnvironment(root, false);
334
+ const paths = resolvePackedPaths({ env: environment, home: root, uid: process.getuid?.() ?? 0 });
335
+ const handle = readDaemonHandle(paths.handle);
336
+ if (!handle) throw new Error("benchmark daemon handle unavailable");
337
+ const token = (await readFile(paths.token, "utf8")).trim();
338
+ const rpc = new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>(`http://${handle.host}:${handle.port}`, token, { label: "Packed benchmark" });
339
+ return { invoke: (operation, input) => rpc.call(operation as OperationName, input as OperationInputs[OperationName]) };
340
+ }
341
+
342
+ async function workloadMain(args: readonly string[]): Promise<void> {
343
+ const [mode, root] = args;
344
+ if (!isBenchmarkMode(mode) || !root) throw new Error("invalid benchmark workload input");
345
+ const client = await connectBenchmarkClient(root);
346
+ const correctnessDigest = await runBenchmarkWorkload(mode, client, join(root, "project"));
347
+ process.stdout.write(`${correctnessDigest}\n`);
348
+ }
349
+
350
+ async function serveMain(args: readonly string[]): Promise<void> {
351
+ const [root, registryBase] = args;
352
+ if (!root || !registryBase) throw new Error("invalid benchmark service input");
353
+ const environment = isolatedEnvironment(root, process.env.PI_PACKED_WATCH_SECS === "0.1");
354
+ const paths = resolvePackedPaths({ env: environment, home: root, uid: process.getuid?.() ?? 0 });
355
+ const registry = new HttpRegistry(registryBase, 5, 0, 1, registryBase);
356
+ runDaemonProcess({ ...daemonOptions({ paths, reg: registry, piHome: join(root, "pi-home"), env: environment, migrateLegacy: false }), onListen: () => {} });
357
+ }
358
+
359
+ function configuredDoctorFrequency(): FrequencyObservation | undefined {
360
+ const calls = Number(process.env.PACKED_BENCHMARK_DOCTOR_CALLS);
361
+ const days = Number(process.env.PACKED_BENCHMARK_OBSERVATION_DAYS ?? FREQUENCY_WINDOW_DAYS);
362
+ if (!Number.isSafeInteger(calls) || calls < 0 || !Number.isSafeInteger(days) || days < 1 || days > 31) return undefined;
363
+ const untilDate = new Date();
364
+ untilDate.setUTCHours(0, 0, 0, 0);
365
+ const sinceDate = new Date(untilDate.getTime() - days * 86_400_000);
366
+ return { since: sinceDate.toISOString().slice(0, 10), until: untilDate.toISOString().slice(0, 10), days, calls };
367
+ }
368
+
369
+ async function observeDoctorFrequency(): Promise<FrequencyObservation | undefined> {
370
+ try {
371
+ const handle = await probe();
372
+ if (!handle) return configuredDoctorFrequency();
373
+ const untilDate = new Date();
374
+ untilDate.setUTCHours(0, 0, 0, 0);
375
+ const sinceDate = new Date(untilDate.getTime() - FREQUENCY_WINDOW_DAYS * 86_400_000);
376
+ type MetricsName = "metrics.query";
377
+ type MetricsInputs = { "metrics.query": { since: number; until: number; source: "server"; toolName: string; groupBy: string[] } };
378
+ type MetricsOutputs = { "metrics.query": Array<{ count?: number }> };
379
+ const rpc = new AuthenticatedRpcClient<MetricsName, MetricsInputs, MetricsOutputs>(handle.base, handle.token, { label: "Packed metrics" });
380
+ const rows = await rpc.call("metrics.query", { since: sinceDate.getTime(), until: untilDate.getTime(), source: "server", toolName: "doctor.run", groupBy: ["day"] });
381
+ const calls = rows.slice(0, 31).reduce((sum, row) => sum + (Number.isFinite(row.count) ? Math.max(0, Number(row.count)) : 0), 0);
382
+ return { since: sinceDate.toISOString().slice(0, 10), until: untilDate.toISOString().slice(0, 10), days: FREQUENCY_WINDOW_DAYS, calls };
383
+ } catch {
384
+ return configuredDoctorFrequency();
385
+ }
386
+ }
387
+
388
+ function workloadMetrics(result: { readonly name: string; readonly benchmark: unknown }, mode: string): { cpuMsPerCall: number; wallMsPerCall: number } | undefined {
389
+ if (result.name !== mode) return undefined;
390
+ const workload = record(record(result.benchmark).workload);
391
+ const invocations = Number(workload.invocations);
392
+ const cpuMs = Number(workload.idleAdjustedCpuMs ?? workload.cpuMs);
393
+ const wallMs = Number(workload.wallMs);
394
+ if (!(invocations > 0) || !Number.isFinite(cpuMs) || !Number.isFinite(wallMs)) return undefined;
395
+ return { cpuMsPerCall: cpuMs / invocations, wallMsPerCall: wallMs / invocations };
396
+ }
397
+
398
+ async function baselineMain(): Promise<void> {
399
+ if (platform() !== "linux") throw new Error("Packed cgroup benchmarks require Linux");
400
+ const observation = await observeDoctorFrequency();
401
+ const fixture = startRegistryFixture();
402
+ try {
403
+ const results = [];
404
+ for (let index = 0; index < PACKED_BENCHMARK_SCENARIOS.length; index++) {
405
+ const scenario = PACKED_BENCHMARK_SCENARIOS[index];
406
+ if (scenario) results.push(await runScenario(scenario, fixture.baseUrl, index));
407
+ }
408
+ const doctor = results.map((result) => workloadMetrics(result, "doctor-warm")).find((value) => value !== undefined);
409
+ const frequency = observation && doctor ? doctorDailyProjection(observation, doctor.cpuMsPerCall, doctor.wallMsPerCall) : { status: "unavailable" as const };
410
+ const artifact = benchmarkArtifact({ generatedAt: new Date().toISOString(), environment: { platform: platform(), architecture: arch(), runtime: basename(process.execPath), logicalCpuCount: cpus().length }, frequency, results });
411
+ const destination = join(fileURLToPath(new URL("../../../../", import.meta.url)), BENCHMARK_FILE);
412
+ await mkdir(dirname(destination), { recursive: true });
413
+ const temporary = `${destination}.tmp`;
414
+ await writeFile(temporary, `${JSON.stringify(artifact, null, 2)}\n`);
415
+ await rename(temporary, destination);
416
+ process.stdout.write(`Packed benchmark: ${results.length} workloads; artifact ${BENCHMARK_FILE}\n`);
417
+ } finally {
418
+ fixture.close();
419
+ }
420
+ }
421
+
422
+ if (import.meta.main) {
423
+ try {
424
+ if (process.argv[2] === "workload") await workloadMain(process.argv.slice(3));
425
+ else if (process.argv[2] === "serve") await serveMain(process.argv.slice(3));
426
+ else await baselineMain();
427
+ } catch (error) {
428
+ process.stderr.write(`Packed benchmark failed: ${error instanceof Error ? error.message : String(error)}\n`);
429
+ process.exitCode = 1;
430
+ }
431
+ }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, lstatSync, opendirSync, readFileSync, realpathSync } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import * as ts from "typescript";
3
4
  import { publint } from "publint";
4
5
  import { formatMessage } from "publint/utils";
5
6
  import { type ExtensionSmokeResult, runExtensionSmoke } from "./smoke.ts";
@@ -510,27 +511,28 @@ function shippedSourceFiles(context: Context): string[] {
510
511
  );
511
512
  }
512
513
 
513
- /**
514
- * Strips line and block comments before the import-specifier regex below runs
515
- * -- a comment can contain an arbitrary quoted "from ..." substring the regex
516
- * would otherwise match as if it were real code (confirmed live against a
517
- * JSDoc comment describing a bug fix in prose that happened to contain
518
- * exactly that shape). Not a full lexer: a string literal that itself
519
- * contains a comment delimiter could still confuse this, a pre-existing
520
- * limit of a regex-based scan, not a regression this introduces.
521
- */
522
- function stripComments(source: string): string {
523
- return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, "");
524
- }
525
-
526
514
  /** Every import/require specifier literal in one file, bounded by
527
515
  * MAX_SOURCE_BYTES -- undefined when the file is too large to read (the
528
516
  * caller's own bound, never silently truncated mid-scan). */
529
517
  function importSpecifiersIn(context: Context, file: string): string[] | undefined {
530
518
  const absolute = join(context.root, file);
531
519
  if (lstatSync(absolute).size > MAX_SOURCE_BYTES) return undefined;
532
- const source = stripComments(readFileSync(absolute, "utf8"));
533
- return [...source.matchAll(/(?:from\s*|import\s*\(|require\s*\()\s*["']([^"']+)["']/g)].map((match) => match[1]!);
520
+ const source = ts.createSourceFile(file, readFileSync(absolute, "utf8"), ts.ScriptTarget.Latest, false);
521
+ const specifiers: string[] = [];
522
+ const add = (node: ts.Node | undefined): void => {
523
+ if (node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))) specifiers.push(node.text);
524
+ };
525
+ const visit = (node: ts.Node): void => {
526
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) add(node.moduleSpecifier);
527
+ else if (ts.isExternalModuleReference(node)) add(node.expression);
528
+ else if (ts.isCallExpression(node) && (
529
+ node.expression.kind === ts.SyntaxKind.ImportKeyword ||
530
+ (ts.isIdentifier(node.expression) && node.expression.text === "require")
531
+ )) add(node.arguments[0]);
532
+ ts.forEachChild(node, visit);
533
+ };
534
+ visit(source);
535
+ return specifiers;
534
536
  }
535
537
 
536
538
  function dependencyCheck(context: Context): void {
@@ -5,7 +5,8 @@ import { resolveDaemonServiceSpec } from "../daemon/daemon-service.ts";
5
5
  import { RECONCILE_INTERVAL_DEFAULT_MS } from "../shared/constants.ts";
6
6
 
7
7
  export interface ServiceUnitDiagnostic {
8
- code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE";
8
+ code: "SERVICE_EXEC_PATH_MISSING" | "SERVICE_NOT_RUNNING" | "SERVICE_STATUS_UNAVAILABLE" | "SERVICE_STALE_CODE"
9
+ | "SERVICE_VERSION_MISMATCH" | "SERVICE_NOT_REGISTERED" | "SERVICE_DESCRIPTOR_DRIFT" | "SERVICE_REQUIREMENT_UNRESOLVED";
9
10
  severity: "error" | "warning";
10
11
  package: string;
11
12
  unitName: string;
@@ -25,7 +26,7 @@ export interface ServiceDoctorDeps extends ServiceInstallDeps {
25
26
  }
26
27
 
27
28
  interface ArmadaStatus {
28
- vehicles?: Array<{ name?: string; executable?: string; nativeStatus?: string; ready?: boolean; nativePid?: number }>;
29
+ vehicles?: Array<{ name?: string; version?: string; descriptorDrift?: boolean; executable?: string; nativeStatus?: string; ready?: boolean; nativePid?: number }>;
29
30
  }
30
31
 
31
32
  /**
@@ -51,11 +52,16 @@ export function checkServiceUnitPaths(
51
52
  projectRoot: string | undefined,
52
53
  deps: ServiceDoctorDeps,
53
54
  ): ServiceDoctorReport {
55
+ const resolutionDiagnostics: ServiceUnitDiagnostic[] = [];
54
56
  const managedPackages = readInstalledPackagesAcrossScopes(piHome, projectRoot).flatMap((pkg) => {
55
57
  const resolved = resolveDaemonServiceSpec(piHome, `npm:${pkg.name}`);
58
+ if (!resolved.ok && !resolved.notADaemon) resolutionDiagnostics.push({
59
+ code: "SERVICE_REQUIREMENT_UNRESOLVED", severity: "error", package: pkg.name,
60
+ unitName: pkg.name, message: resolved.reason,
61
+ });
56
62
  return resolved.ok ? [{ packageName: pkg.name, spec: resolved.spec }] : [];
57
63
  });
58
- if (managedPackages.length === 0) return { ok: true, diagnostics: [], checked: 0 };
64
+ if (managedPackages.length === 0) return { ok: resolutionDiagnostics.length === 0, diagnostics: resolutionDiagnostics, checked: 0 };
59
65
 
60
66
  const statusResult = deps.runCommand(process.execPath, [deps.armadaCliPath, "status", "--json"]);
61
67
  if (!statusResult.ok) {
@@ -94,12 +100,33 @@ export function checkServiceUnitPaths(
94
100
  }
95
101
 
96
102
  const vehicles = new Map((status.vehicles ?? []).flatMap((vehicle) => (vehicle.name ? [[vehicle.name, vehicle]] : [])));
97
- const diagnostics: ServiceUnitDiagnostic[] = [];
103
+ const diagnostics: ServiceUnitDiagnostic[] = [...resolutionDiagnostics];
98
104
  let checked = 0;
99
105
  for (const managedPackage of managedPackages) {
100
106
  const vehicle = vehicles.get(managedPackage.spec.name);
101
- if (!vehicle) continue;
102
107
  checked++;
108
+ if (!vehicle) {
109
+ diagnostics.push({
110
+ code: "SERVICE_NOT_REGISTERED", severity: "error", package: managedPackage.packageName,
111
+ unitName: managedPackage.spec.name,
112
+ message: `${managedPackage.spec.name} has no Armada registration; install its persistent service through Packed`,
113
+ });
114
+ continue;
115
+ }
116
+ if (vehicle.version !== undefined && vehicle.version !== managedPackage.spec.version) {
117
+ diagnostics.push({
118
+ code: "SERVICE_VERSION_MISMATCH", severity: "error", package: managedPackage.packageName,
119
+ unitName: managedPackage.spec.name,
120
+ message: `${managedPackage.spec.name} resolves to ${managedPackage.spec.version}, but Armada declares ${vehicle.version}; reconcile this package's service through Packed, then verify readiness; session reload alone cannot adopt a daemon`,
121
+ });
122
+ }
123
+ if (vehicle.descriptorDrift === true) {
124
+ diagnostics.push({
125
+ code: "SERVICE_DESCRIPTOR_DRIFT", severity: "error", package: managedPackage.packageName,
126
+ unitName: managedPackage.spec.name,
127
+ message: `${managedPackage.spec.name}'s native descriptor differs from Armada's declaration; reconcile the service through Armada`,
128
+ });
129
+ }
103
130
  const executable = vehicle.executable ?? managedPackage.spec.binPath;
104
131
  if (!deps.fileExists(executable)) {
105
132
  diagnostics.push({
@@ -30,6 +30,7 @@ import { createHash } from "node:crypto";
30
30
  import { readdirSync, readFileSync } from "node:fs";
31
31
  import { join } from "node:path";
32
32
  import { fileURLToPath } from "node:url";
33
+ import { satisfies, validRange } from "semver";
33
34
  import { createVehicleRegistrar, type VehicleRegistrar, type VehicleRegistrationOutcome, type VehicleSpec } from "@danypops/armada";
34
35
  import { resolveDaemonPaths } from "@danypops/vehicle-server/paths";
35
36
  import {
@@ -208,12 +209,18 @@ function resolveDependencyNameCandidate(
208
209
  packageDir: string,
209
210
  depName: string,
210
211
  hoistedNodeModulesDir: string | undefined,
212
+ requiredRange: string,
211
213
  ): ResolvedDaemonEntrypoint | undefined {
212
214
  const candidateDirs = [join(packageDir, "node_modules", depName), ...(hoistedNodeModulesDir ? [join(hoistedNodeModulesDir, depName)] : [])];
213
- const resolved = candidateDirs
215
+ const installed = candidateDirs
214
216
  .map((depDir) => resolveDependencyCandidate(depDir, depName))
215
217
  .filter((entry): entry is ResolvedDaemonEntrypoint => entry !== undefined);
216
- if (resolved.length === 0) return undefined;
218
+ if (installed.length === 0) return undefined;
219
+ if (!validRange(requiredRange)) throw new Error(`cannot verify daemon requirement ${depName}@${requiredRange}`);
220
+ const resolved = installed.filter((entry) => satisfies(entry.version, requiredRange));
221
+ if (resolved.length === 0) {
222
+ throw new Error(`daemon ${depName} requires ${requiredRange}; installed candidates: ${installed.map((entry) => entry.version).join(", ")}`);
223
+ }
217
224
  if (resolved.length === 1) return resolved[0];
218
225
  // Nested and hoisted both resolved for the same dependency (the real jittor incident:
219
226
  // a stale nested copy always won just because it's checked first). Prefer the newer
@@ -255,8 +262,8 @@ export function detectVehicleDaemonService(
255
262
  const byName = unscopedName(own.name ?? fallbackName);
256
263
  const wantedNamesake = namesakeDaemonName(byName);
257
264
  const candidates: ResolvedDaemonEntrypoint[] = [];
258
- for (const depName of Object.keys(own.dependencies ?? {})) {
259
- const resolved = resolveDependencyNameCandidate(packageDir, depName, hoistedNodeModulesDir);
265
+ for (const [depName, requiredRange] of Object.entries(own.dependencies ?? {})) {
266
+ const resolved = resolveDependencyNameCandidate(packageDir, depName, hoistedNodeModulesDir, requiredRange);
260
267
  if (!resolved) continue;
261
268
  if (wantedNamesake !== undefined && resolved.name === wantedNamesake) return resolved;
262
269
  candidates.push(resolved);
@@ -335,8 +342,12 @@ export function resolveDaemonServiceSpec(piHome: string, source: string): Resolv
335
342
  };
336
343
  }
337
344
 
338
- const detected = detectVehicleDaemonService(packageDir, packageName, join(piHome, "npm", "node_modules"));
339
- if (detected) return { ok: true, spec: buildSpec(detected) };
345
+ try {
346
+ const detected = detectVehicleDaemonService(packageDir, packageName, join(piHome, "npm", "node_modules"));
347
+ if (detected) return { ok: true, spec: buildSpec(detected) };
348
+ } catch (error) {
349
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
350
+ }
340
351
 
341
352
  return {
342
353
  ok: false,
@@ -164,7 +164,10 @@ interface MutationResponse {
164
164
  ok: boolean;
165
165
  output: string;
166
166
  }
167
- interface UpdateMutationResponse extends MutationResponse, Partial<Omit<UpdateOutcome, "output">> {}
167
+ interface UpdateMutationResponse extends MutationResponse, Partial<Omit<UpdateOutcome, "output">> {
168
+ packageUpdated?: boolean;
169
+ serviceReconciled?: boolean;
170
+ }
168
171
  interface UpdateAllMutationResponse extends MutationResponse {
169
172
  results: Array<{ source: string; ok: boolean; output: string; serviceReconciled?: boolean } & Partial<Omit<UpdateOutcome, "output">>>;
170
173
  reresolveError?: string;
@@ -598,9 +601,15 @@ export function createApp(deps: Deps): { fetch: (req: Request) => Promise<Respon
598
601
  const service = await daemonServiceInstaller.restart(piHomeForServiceInstall, restartSource);
599
602
  if (!service.ok) {
600
603
  if (service.notADaemon) return json({ ok: true, source, ...outcome });
601
- return json({ ok: false, source, ...outcome, output: `${outcome.output}\n${service.reason}` });
604
+ return json({ ok: false, source, ...outcome, packageUpdated: !outcome.alreadyUpToDate,
605
+ serviceReconciled: false,
606
+ output: `${outcome.output}\nDaemon adoption required: ${service.reason}. Session reload alone is insufficient.` });
602
607
  }
603
- return json({ ok: true, source, ...outcome, serviceReconciled: service.restarted });
608
+ if (!service.restarted) return json({
609
+ ok: false, source, ...outcome, packageUpdated: !outcome.alreadyUpToDate, serviceReconciled: false,
610
+ output: `${outcome.output}\nDaemon adoption required for ${service.spec.name}: ${service.reason ?? "reconciliation incomplete"}. Use Packed's targeted install-service operation; session reload alone is insufficient.`,
611
+ });
612
+ return json({ ok: true, source, ...outcome, serviceReconciled: true });
604
613
  } catch (error) {
605
614
  return json({ ok: false, source, output: error instanceof Error ? error.message : String(error), reloadRequired: false });
606
615
  }
@@ -0,0 +1,98 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ benchmarkArtifact,
4
+ doctorDailyProjection,
5
+ runBenchmarkWorkload,
6
+ PACKED_BENCHMARK_SCENARIOS,
7
+ } from "../benchmarks/baseline.ts";
8
+
9
+ class FakeClient {
10
+ readonly calls: Array<{ operation: string; input: Record<string, unknown> }> = [];
11
+
12
+ async invoke(operation: string, input: Record<string, unknown>): Promise<unknown> {
13
+ this.calls.push({ operation, input });
14
+ if (operation === "package.catalog") return { packages: [{ name: "pi-one" }], sha256: "catalog-digest" };
15
+ if (operation === "package.index") return { packages: [{ name: "pi-one" }], truncated: false };
16
+ if (operation === "package.updates") return { updates: [] };
17
+ if (operation === "package.catalog.sync") return { synced: 12 };
18
+ if (operation === "package.index.build") return { packages: [{ name: "pi-one" }], truncated: false };
19
+ if (operation === "package.updates.project") return { updates: [{ name: "pi-one" }] };
20
+ if (operation === "doctor.run") return { ok: true, scanned: 4, extensions: [], conflicts: [] };
21
+ throw new Error(`unexpected operation ${operation}`);
22
+ }
23
+ }
24
+
25
+ describe("Packed benchmark scenarios", () => {
26
+ it("covers all bounded workload classes", () => {
27
+ expect(PACKED_BENCHMARK_SCENARIOS.map((scenario) => scenario.name)).toEqual([
28
+ "idle-polling",
29
+ "cached-reads-cold",
30
+ "cached-reads-warm",
31
+ "doctor-cold",
32
+ "doctor-warm",
33
+ "doctor-concurrent",
34
+ "catalog-sync-cold",
35
+ "catalog-sync-warm",
36
+ "index-build-cold",
37
+ "index-build-warm",
38
+ "updates-project",
39
+ ]);
40
+ for (const scenario of PACKED_BENCHMARK_SCENARIOS) {
41
+ expect(scenario.repetitions).toBeGreaterThan(0);
42
+ expect(scenario.repetitions).toBeLessThanOrEqual(5);
43
+ expect(scenario.concurrency).toBeLessThanOrEqual(2);
44
+ expect(scenario.deadlineMs).toBeLessThanOrEqual(120_000);
45
+ }
46
+ });
47
+
48
+ it("uses bounded inputs and digest output", async () => {
49
+ const client = new FakeClient();
50
+ for (const mode of [
51
+ "cached-reads-warm",
52
+ "doctor-warm",
53
+ "catalog-sync-warm",
54
+ "index-build-warm",
55
+ "updates-project",
56
+ ] as const) {
57
+ expect(await runBenchmarkWorkload(mode, client, "/synthetic/project")).toMatch(/^[a-f0-9]{64}$/);
58
+ }
59
+ expect(client.calls.map((call) => call.operation)).toEqual([
60
+ "package.catalog",
61
+ "package.index",
62
+ "package.updates",
63
+ "doctor.run",
64
+ "package.catalog.sync",
65
+ "package.index.build",
66
+ "package.updates.project",
67
+ ]);
68
+ });
69
+
70
+ it("projects Doctor cost from complete days", () => {
71
+ expect(doctorDailyProjection({ since: "2026-08-20", until: "2026-09-03", days: 14, calls: 28_000 }, 12, 20)).toEqual({
72
+ status: "available",
73
+ source: "packed metrics.query",
74
+ since: "2026-08-20",
75
+ until: "2026-09-03",
76
+ days: 14,
77
+ calls: 28_000,
78
+ callsPerDay: 2_000,
79
+ projectedDailyCpuMs: 24_000,
80
+ projectedDailyWallMs: 40_000,
81
+ });
82
+ });
83
+
84
+ it("bounds artifacts and marks unavailable counters", () => {
85
+ const artifact = benchmarkArtifact({
86
+ generatedAt: "2026-01-01T00:00:00.000Z",
87
+ environment: { platform: "linux", architecture: "x64", runtime: "bun", logicalCpuCount: 8 },
88
+ frequency: { status: "unavailable" },
89
+ results: [{ name: "doctor-cold", benchmark: { workload: { outputDigest: "a".repeat(64) } } }],
90
+ });
91
+ const serialized = JSON.stringify(artifact);
92
+ expect(serialized.length).toBeLessThan(256_000);
93
+ expect(serialized).not.toContain("/home/");
94
+ expect(serialized).not.toContain("settings.json");
95
+ expect(artifact.schemaVersion).toBe(1);
96
+ expect(artifact.networkBytes).toBe("unavailable");
97
+ });
98
+ });
@@ -34,6 +34,30 @@ const base = {
34
34
  peerDependencies: { "@earendil-works/pi-coding-agent": "*" },
35
35
  };
36
36
 
37
+ describe("syntax-aware dependency discovery", () => {
38
+ it("ignores import-shaped strings while retaining real missing dependencies", async () => {
39
+ const root = fixture(base, { "extensions/index.ts": `
40
+ const from = Math.max(0, Number(url.searchParams.get("from") ?? 0));
41
+ const size = Math.min(20, Math.max(1, Number(url.searchParams.get("size") ?? 5)));
42
+ const text = "import('fake-string')";
43
+ const template = \`require('fake-template')\`;
44
+ // import 'fake-comment';
45
+ import 'missing-side-effect';
46
+ export { value } from 'missing-export';
47
+ import value from 'missing-static';
48
+ const dynamic = import('missing-dynamic');
49
+ const common = require('missing-common');
50
+ import legacy = require('missing-legacy');
51
+ const liveTemplate = \`value: \${require('missing-template-expression')}\`;
52
+ ` });
53
+ const report = await checkPackage(root);
54
+ const missing = report.diagnostics.filter((item) => item.code === "RUNTIME_DEPENDENCY_MISSING");
55
+ expect(missing.map((item) => item.message.split(" is imported")[0]).sort()).toEqual([
56
+ "missing-common", "missing-dynamic", "missing-export", "missing-legacy", "missing-side-effect", "missing-static", "missing-template-expression",
57
+ ]);
58
+ });
59
+ });
60
+
37
61
  function codes(diagnostics: Diagnostic[]): string[] {
38
62
  return diagnostics.map((diagnostic) => diagnostic.code);
39
63
  }
@@ -44,6 +44,37 @@ function writeRawPackage(dir: string, pkg: Record<string, unknown>): void {
44
44
  }
45
45
 
46
46
  describe("resolveDaemonServiceSpec", () => {
47
+ it("selects a daemon satisfying the extension range rather than a newer incompatible hoist", () => {
48
+ const home = fakePiHome();
49
+ const extension = join(home, "npm/node_modules/@scope/pi-lector");
50
+ writeRawPackage(extension, { name: "@scope/pi-lector", version: "0.17.2", dependencies: { "@scope/lector": "^0.23.1" } });
51
+ writeRawPackage(join(extension, "node_modules/@scope/lector"), {
52
+ name: "@scope/lector", version: "0.23.1", packed: { daemonService: { binPath: "cli.ts" } },
53
+ });
54
+ writeRawPackage(join(home, "npm/node_modules/@scope/lector"), {
55
+ name: "@scope/lector", version: "0.24.0", packed: { daemonService: { binPath: "cli.ts" } },
56
+ });
57
+ const result = resolveDaemonServiceSpec(home, "npm:@scope/pi-lector");
58
+ expect(result.ok).toBe(true);
59
+ if (!result.ok) throw new Error(result.reason);
60
+ expect(result.spec.version).toBe("0.23.1");
61
+ });
62
+
63
+ it("fails visibly when the only daemon installation violates the extension range", () => {
64
+ const home = fakePiHome();
65
+ writeRawPackage(join(home, "npm/node_modules/@scope/pi-lector"), {
66
+ name: "@scope/pi-lector", version: "0.17.2", dependencies: { "@scope/lector": "^0.23.1" },
67
+ });
68
+ writeRawPackage(join(home, "npm/node_modules/@scope/lector"), {
69
+ name: "@scope/lector", version: "0.21.0", packed: { daemonService: { binPath: "cli.ts" } },
70
+ });
71
+ const result = resolveDaemonServiceSpec(home, "npm:@scope/pi-lector");
72
+ expect(result.ok).toBe(false);
73
+ if (result.ok) throw new Error("incompatible daemon was accepted");
74
+ expect(result.notADaemon).not.toBe(true);
75
+ expect(result.reason).toContain("^0.23.1");
76
+ });
77
+
47
78
  it("builds a ServiceSpec from a package's packed.daemonService manifest", () => {
48
79
  const piHome = fakePiHome();
49
80
  writePackage(piHome, "@danypops/web-spider-daemon", {
@@ -275,12 +306,10 @@ describe("resolveDaemonServiceSpec", () => {
275
306
  expect(result.spec.restartSec).toBe(2);
276
307
  });
277
308
 
278
- it("prefers the higher-semver candidate when BOTH a nested and hoisted copy of the same dependency resolve via bin+dependency convention -- the real jittor incident: a stale nested copy must never win over a correctly-versioned hoisted one just because it's checked first", () => {
309
+ it("prefers the higher-semver candidate when nested and hoisted copies both satisfy the declared range", () => {
279
310
  const piHome = fakePiHome();
280
311
  const extDir = join(piHome, "npm", "node_modules", "@danypops/pi-papyrus");
281
- writeRawPackage(extDir, { name: "@danypops/pi-papyrus", version: "1.0.0", dependencies: { "@danypops/jittor": "^0.14.0" } });
282
- // A stale nested copy pi-papyrus's own outdated floor forced npm to keep private, instead of
283
- // deduping to the shared, correctly-versioned, hoisted copy every other consumer already uses.
312
+ writeRawPackage(extDir, { name: "@danypops/pi-papyrus", version: "1.0.0", dependencies: { "@danypops/jittor": ">=0.14.0 <0.19.0" } });
284
313
  const nestedDir = join(extDir, "node_modules", "@danypops/jittor");
285
314
  writeRawPackage(nestedDir, {
286
315
  name: "@danypops/jittor",
@@ -44,6 +44,47 @@ function fakeDeps(status: unknown, fileExists = true): ServiceDoctorDeps {
44
44
  }
45
45
 
46
46
  describe("checkServiceUnitPaths", () => {
47
+ it("reports version drift even after the old daemon has restarted", () => {
48
+ const home = piHome(["npm:fakedaemon"]);
49
+ const dir = installDaemonPackage(home, "fakedaemon");
50
+ const report = checkServiceUnitPaths(home, undefined, fakeDeps({ vehicles: [{
51
+ name: "fakedaemon", executable: join(dir, "cli.ts"), version: "0.21.0",
52
+ nativeStatus: "running", ready: true,
53
+ }] }));
54
+ expect(report.ok).toBe(false);
55
+ expect(report.diagnostics).toContainEqual(expect.objectContaining({
56
+ code: "SERVICE_VERSION_MISMATCH", severity: "error",
57
+ message: expect.stringContaining("0.21.0"),
58
+ }));
59
+ });
60
+
61
+ it("accepts an isolated service at the resolved version", () => {
62
+ const home = piHome(["npm:fakedaemon"]);
63
+ installDaemonPackage(home, "fakedaemon");
64
+ const report = checkServiceUnitPaths(home, undefined, fakeDeps({ vehicles: [{
65
+ name: "fakedaemon", executable: "/managed/isolated/cli.ts", version: "1.0.0",
66
+ nativeStatus: "running", ready: true,
67
+ }] }));
68
+ expect(report).toEqual({ ok: true, diagnostics: [], checked: 1 });
69
+ });
70
+
71
+ it("reports missing registration rather than claiming a healthy deployment", () => {
72
+ const home = piHome(["npm:fakedaemon"]);
73
+ installDaemonPackage(home, "fakedaemon");
74
+ const report = checkServiceUnitPaths(home, undefined, fakeDeps({ vehicles: [] }));
75
+ expect(report.ok).toBe(false);
76
+ expect(report.diagnostics[0]?.code).toBe("SERVICE_NOT_REGISTERED");
77
+ });
78
+
79
+ it("reports native descriptor drift independently of version equality", () => {
80
+ const home = piHome(["npm:fakedaemon"]);
81
+ installDaemonPackage(home, "fakedaemon");
82
+ const report = checkServiceUnitPaths(home, undefined, fakeDeps({ vehicles: [{
83
+ name: "fakedaemon", executable: "/managed/cli.ts", version: "1.0.0", descriptorDrift: true,
84
+ }] }));
85
+ expect(report.diagnostics).toContainEqual(expect.objectContaining({ code: "SERVICE_DESCRIPTOR_DRIFT" }));
86
+ });
87
+
47
88
  it("skips packages with no Armada-managed Vehicle", () => {
48
89
  const home = piHome(["npm:not-a-daemon"]);
49
90
  const dir = join(home, "npm", "node_modules", "not-a-daemon");
@@ -531,6 +531,21 @@ describe("service app", () => {
531
531
  expect(inst.updated).toBe("npm:pi-lsp");
532
532
  });
533
533
 
534
+ it("POST /update reports package success separately from pending daemon adoption", async () => {
535
+ const svc = new FakeDaemonServiceInstaller();
536
+ svc.restarted = false;
537
+ svc.restartReason = "no persistent service is registered for probe";
538
+ const app = createApp(deps({ daemonServiceInstaller: svc }));
539
+ const response = await app.fetch(new Request("http://x/update", {
540
+ method: "POST", headers: { ...auth, "content-type": "application/json" },
541
+ body: JSON.stringify({ source: "npm:probe", approved: true }),
542
+ }));
543
+ expect(await response.json()).toMatchObject({
544
+ ok: false, packageUpdated: true, serviceReconciled: false,
545
+ output: expect.stringContaining("Daemon adoption required"),
546
+ });
547
+ });
548
+
534
549
  it("POST /update reconciles an installed Vehicle after a real package change", async () => {
535
550
  const svc = new FakeDaemonServiceInstaller();
536
551
  const piHome = track(mkdtempSync(join(tmpdir(), "packed-update-vehicle-")));