@timurproko/a1 0.1.8-dev.218 → 0.1.8-dev.224

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.
Files changed (42) hide show
  1. package/bin/cli.js +6 -0
  2. package/bin/guardian.js +3 -0
  3. package/bin/release-cleanup.js +8 -0
  4. package/bin/ui.js +4 -0
  5. package/bin/warmup.js +18 -0
  6. package/dist/features/owned-ui/run.js +2 -0
  7. package/dist/foundation/launch-guardian/main.js +2 -0
  8. package/dist/foundation/release/bootstrap.d.ts +3 -1
  9. package/dist/foundation/release/bootstrap.js +9 -0
  10. package/dist/foundation/release/cohort-state.d.ts +61 -1
  11. package/dist/foundation/release/cohort-state.js +192 -11
  12. package/dist/foundation/release/dependency-layer.d.ts +78 -0
  13. package/dist/foundation/release/dependency-layer.js +487 -0
  14. package/dist/foundation/release/endpoints.d.ts +1 -1
  15. package/dist/foundation/release/endpoints.js +8 -8
  16. package/dist/foundation/release/index.d.ts +2 -0
  17. package/dist/foundation/release/index.js +2 -0
  18. package/dist/foundation/release/release-gc.d.ts +59 -7
  19. package/dist/foundation/release/release-gc.js +533 -19
  20. package/dist/foundation/release/release-store.d.ts +3 -1
  21. package/dist/foundation/release/release-store.js +60 -10
  22. package/dist/foundation/release/release.d.ts +4 -1
  23. package/dist/foundation/release/release.js +9 -2
  24. package/dist/foundation/release/update.d.ts +10 -2
  25. package/dist/foundation/release/update.js +27 -4
  26. package/dist/foundation/release/warmup.d.ts +3 -0
  27. package/dist/foundation/release/warmup.js +35 -0
  28. package/dist/foundation/startup/index.d.ts +1 -0
  29. package/dist/foundation/startup/index.js +1 -0
  30. package/dist/foundation/startup/startup-runtime.d.ts +36 -0
  31. package/dist/foundation/startup/startup-runtime.js +140 -0
  32. package/dist/integrations/pi/engine/runtime-integration.js +5 -0
  33. package/dist/native/darwin-arm64/manifest.json +3 -3
  34. package/dist/native/darwin-arm64/process-guardian +0 -0
  35. package/dist/native/linux-x64/manifest.json +1 -1
  36. package/dist/native/win32-x64/manifest.json +2 -2
  37. package/dist/native/win32-x64/process-guardian.exe +0 -0
  38. package/dist/product-identity.d.ts +2 -2
  39. package/dist/product-identity.js +4 -1
  40. package/dist/product-identity.json +8 -1
  41. package/dist/runtime-payload-inventory.json +122328 -0
  42. package/package.json +2 -2
@@ -1,9 +1,10 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { chmod, lstat, mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
2
+ import { chmod, lstat, mkdir, readFile, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
- import { PRODUCT_PACKAGE_NAME, createReleaseIdentity, digestManifestFiles, discoverReleasePayload, releaseFileIdentity, resolveWithin, } from "./release.js";
4
+ import { PRODUCT_PACKAGE_NAME, createReleaseIdentity, discoverReleasePayload, releaseFileIdentity, resolveWithin, } from "./release.js";
5
5
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
6
6
  import { mapWithConcurrency } from "./concurrency.js";
7
+ import { dependencyReference, materializeDependencyLayer, readCertifiedDependencyLayer, selectPublishedDependencyRuntimePayload, verifyDependencyLayer, } from "./dependency-layer.js";
7
8
  const RELEASE_FILE_IO_CONCURRENCY = 32;
8
9
  const certificationReady = new WeakSet();
9
10
  export const RELEASE_MANIFEST_FILENAME = PRODUCT_IDENTITY.manifest.releaseFilename;
@@ -13,15 +14,32 @@ export async function materializeRelease(packageRoot, dataDir, options = {}) {
13
14
  });
14
15
  const storeRoot = resolve(dataDir, "releases");
15
16
  await mkdir(storeRoot, { recursive: true, mode: 0o700 });
16
- options.onProgress?.({ phase: "copying", fileCount: payload.paths.length });
17
+ const dependencyPaths = payload.paths.filter(path => path.startsWith("node_modules/"));
18
+ const productPaths = payload.paths.filter(path => !path.startsWith("node_modules/"));
19
+ const selectedDependencies = await selectPublishedDependencyRuntimePayload(payload.packageRoot, dependencyPaths);
20
+ options.onRuntimeInventory?.(selectedDependencies.inventory);
21
+ const layerOperations = [];
22
+ const layer = await materializeDependencyLayer(payload.packageRoot, dataDir, selectedDependencies.paths, {
23
+ inventory: selectedDependencies.inventory,
24
+ cachedFiles: payload.cachedFiles,
25
+ onOperation: event => layerOperations.push(event),
26
+ ...(options.writeCandidateFile === undefined ? {} : { writeCandidateFile: options.writeCandidateFile }),
27
+ });
28
+ const layerReferences = layer === null ? [] : [dependencyReference(layer)];
29
+ options.onProgress?.({
30
+ phase: "copying",
31
+ fileCount: productPaths.length + (layer?.reused === false ? layer.files.length : 0),
32
+ });
33
+ for (const event of layerOperations)
34
+ options.onOperation?.(event);
17
35
  const candidate = resolveWithin(storeRoot, `.candidate-${randomUUID()}`);
18
36
  await mkdir(candidate, { recursive: false, mode: 0o700 });
19
37
  try {
20
- const directories = [...new Set(payload.paths.map(path => dirname(resolveWithin(candidate, path))))];
38
+ const directories = [...new Set(productPaths.map(path => dirname(resolveWithin(candidate, path))))];
21
39
  await mapWithConcurrency(directories, RELEASE_FILE_IO_CONCURRENCY, async (directory) => {
22
40
  await mkdir(directory, { recursive: true, mode: 0o700 });
23
41
  });
24
- const files = await mapWithConcurrency(payload.paths, RELEASE_FILE_IO_CONCURRENCY, async (path) => {
42
+ const files = await mapWithConcurrency(productPaths, RELEASE_FILE_IO_CONCURRENCY, async (path) => {
25
43
  const source = resolveWithin(payload.packageRoot, path);
26
44
  const destination = resolveWithin(candidate, path);
27
45
  const metadata = await lstat(source);
@@ -41,7 +59,12 @@ export async function materializeRelease(packageRoot, dataDir, options = {}) {
41
59
  options.onOperation?.({ operation: "candidate-write", path, bytes: bytes.length });
42
60
  return releaseFileIdentity(path, bytes, (metadata.mode & 0o111) !== 0);
43
61
  });
44
- const identity = createReleaseIdentity(payload.packageRoot, payload.packageVersion, files);
62
+ const identity = createReleaseIdentity(payload.packageRoot, payload.packageVersion, files, layerReferences);
63
+ if (layer !== null) {
64
+ const binding = resolveWithin(candidate, "node_modules");
65
+ const target = resolveWithin(layer.layerRoot, "node_modules");
66
+ await symlink(target, binding, process.platform === "win32" ? "junction" : "dir");
67
+ }
45
68
  const releaseRoot = resolveWithin(storeRoot, identity.releaseId);
46
69
  if (await lstat(releaseRoot).catch(() => null)) {
47
70
  await rm(candidate, { recursive: true, force: true });
@@ -84,7 +107,8 @@ export async function readMaterializedRelease(releaseRoot) {
84
107
  */
85
108
  export async function readCertifiedReleaseManifest(record, selectedStoreRoot) {
86
109
  const canonical = await realpath(record.releaseRoot);
87
- assertContained(await realpath(selectedStoreRoot), canonical, "release root is outside the selected release store");
110
+ const canonicalStoreRoot = await realpath(selectedStoreRoot);
111
+ assertContained(canonicalStoreRoot, canonical, "release root is outside the selected release store");
88
112
  const manifest = JSON.parse(await readFile(resolveWithin(canonical, RELEASE_MANIFEST_FILENAME), "utf8"));
89
113
  validateManifest(manifest);
90
114
  if (manifest.releaseId !== record.releaseId || manifest.contentDigest !== record.contentDigest
@@ -93,12 +117,13 @@ export async function readCertifiedReleaseManifest(record, selectedStoreRoot) {
93
117
  }
94
118
  if (canonical.split(sep).at(-1) !== manifest.releaseId)
95
119
  throw new Error(`release directory does not match identity ${manifest.releaseId}`);
120
+ await verifyReleaseDependencies(canonical, canonicalStoreRoot, manifest.dependencyLayers ?? [], false);
96
121
  return { ...manifest, releaseRoot: canonical };
97
122
  }
98
123
  export async function verifyMaterializedRelease(releaseRoot, expected, selectedStoreRoot, options = {}) {
99
124
  const canonical = await realpath(releaseRoot);
100
- if (selectedStoreRoot)
101
- assertContained(await realpath(selectedStoreRoot), canonical, "release root is outside the selected release store");
125
+ const canonicalStoreRoot = selectedStoreRoot ? await realpath(selectedStoreRoot) : await realpath(dirname(canonical));
126
+ assertContained(canonicalStoreRoot, canonical, "release root is outside the selected release store");
102
127
  const manifestPath = resolveWithin(canonical, RELEASE_MANIFEST_FILENAME);
103
128
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
104
129
  validateManifest(manifest);
@@ -111,9 +136,10 @@ export async function verifyMaterializedRelease(releaseRoot, expected, selectedS
111
136
  await mapWithConcurrency(manifest.files, RELEASE_FILE_IO_CONCURRENCY, async (file) => {
112
137
  await verifyFile(canonical, file, options);
113
138
  });
114
- const recomputed = digestManifestFiles(manifest.files);
139
+ const recomputed = createReleaseIdentity(manifest.packageRoot, manifest.packageVersion, manifest.files, manifest.dependencyLayers ?? []).contentDigest;
115
140
  if (recomputed !== manifest.contentDigest)
116
141
  throw new Error(`release content digest mismatch for ${manifest.releaseId}`);
142
+ await verifyReleaseDependencies(canonical, canonicalStoreRoot, manifest.dependencyLayers ?? [], true, options);
117
143
  return { ...manifest, releaseRoot: canonical };
118
144
  }
119
145
  export async function assertImmutableExecutionRoot(release, dataDir) {
@@ -135,6 +161,25 @@ export async function resolveReleaseEntryPoint(release, entryPoint) {
135
161
  assertContained(release.releaseRoot, canonical, "entry point resolves outside the selected release root");
136
162
  return canonical;
137
163
  }
164
+ async function verifyReleaseDependencies(releaseRoot, storeRoot, references, fullVerification, options = {}) {
165
+ if (references.length === 0)
166
+ return;
167
+ if (references.length !== 1)
168
+ throw new Error("release currently supports exactly one dependency layer");
169
+ const dataDir = dirname(storeRoot);
170
+ const reference = references[0];
171
+ const layer = fullVerification
172
+ ? await verifyDependencyLayer(dataDir, reference, event => options.onOperation?.({ operation: event.operation, path: event.path, bytes: event.bytes }))
173
+ : await readCertifiedDependencyLayer(dataDir, reference.layerId, reference);
174
+ const binding = resolveWithin(releaseRoot, reference.binding);
175
+ const metadata = await lstat(binding);
176
+ if (!metadata.isSymbolicLink())
177
+ throw new Error(`release dependency binding is not managed: ${binding}`);
178
+ const target = await realpath(binding);
179
+ const expected = await realpath(resolveWithin(layer.layerRoot, "node_modules"));
180
+ if (target !== expected)
181
+ throw new Error(`release dependency binding targets unexpected content: ${target}`);
182
+ }
138
183
  async function verifyFile(root, file, options) {
139
184
  const path = resolveWithin(root, file.path);
140
185
  const metadata = await lstat(path).catch(() => null);
@@ -159,6 +204,11 @@ function validateManifest(value) {
159
204
  throw new Error(PRODUCT_TEXT.diagnostic("release identity is invalid"));
160
205
  if (!Array.isArray(value.files) || value.files.length === 0)
161
206
  throw new Error("release manifest contains no files");
207
+ if (value.dependencyLayers !== undefined && (!Array.isArray(value.dependencyLayers) || value.dependencyLayers.length === 0
208
+ || value.dependencyLayers.some(layer => !/^dependencies-[a-f0-9]{32}$/.test(layer.layerId)
209
+ || !/^[a-f0-9]{64}$/.test(layer.contentDigest) || layer.binding !== "node_modules"))) {
210
+ throw new Error("release dependency-layer references are invalid");
211
+ }
162
212
  for (const file of value.files) {
163
213
  if (typeof file.path !== "string" || file.path.length === 0 || file.path.includes("\\") || file.path.startsWith("/") || file.path.split("/").includes("..")) {
164
214
  throw new Error(`invalid release manifest path: ${String(file.path)}`);
@@ -1,3 +1,4 @@
1
+ import type { DependencyLayerReference } from "./dependency-layer.js";
1
2
  export declare const PRODUCT_PACKAGE_NAME: string;
2
3
  export interface ReleaseFileIdentity {
3
4
  readonly path: string;
@@ -12,6 +13,8 @@ export interface ReleaseIdentity {
12
13
  readonly releaseId: string;
13
14
  readonly packageRoot: string;
14
15
  readonly files: readonly ReleaseFileIdentity[];
16
+ /** Absent for legacy full-copy releases. */
17
+ readonly dependencyLayers?: readonly DependencyLayerReference[];
15
18
  }
16
19
  export interface DiscoveredReleasePayload {
17
20
  readonly packageRoot: string;
@@ -31,7 +34,7 @@ export declare function discoverReleasePayload(packageRoot: string, options?: Di
31
34
  * content.
32
35
  */
33
36
  export declare function deriveReleaseIdentity(packageRoot: string): Promise<ReleaseIdentity>;
34
- export declare function createReleaseIdentity(packageRoot: string, packageVersion: string, files: readonly ReleaseFileIdentity[]): ReleaseIdentity;
37
+ export declare function createReleaseIdentity(packageRoot: string, packageVersion: string, files: readonly ReleaseFileIdentity[], dependencyLayers?: readonly DependencyLayerReference[]): ReleaseIdentity;
35
38
  export declare function releaseFileIdentity(path: string, bytes: Uint8Array, executable: boolean): ReleaseFileIdentity;
36
39
  export declare function digestManifestFiles(files: readonly ReleaseFileIdentity[]): string;
37
40
  export declare function resolveWithin(root: string, candidate: string): string;
@@ -51,8 +51,14 @@ export async function deriveReleaseIdentity(packageRoot) {
51
51
  });
52
52
  return createReleaseIdentity(payload.packageRoot, payload.packageVersion, files);
53
53
  }
54
- export function createReleaseIdentity(packageRoot, packageVersion, files) {
55
- const contentDigest = digestManifestFiles(files);
54
+ export function createReleaseIdentity(packageRoot, packageVersion, files, dependencyLayers = []) {
55
+ const productDigest = digestManifestFiles(files);
56
+ const contentDigest = dependencyLayers.length === 0
57
+ ? productDigest
58
+ : createHash("sha256")
59
+ .update(`product\0${productDigest}\n`)
60
+ .update(dependencyLayers.map(layer => `${layer.layerId}\0${layer.contentDigest}\0${layer.binding}\n`).join(""))
61
+ .digest("hex");
56
62
  return {
57
63
  packageName: PRODUCT_PACKAGE_NAME,
58
64
  packageVersion,
@@ -60,6 +66,7 @@ export function createReleaseIdentity(packageRoot, packageVersion, files) {
60
66
  releaseId: `${packageVersion}-${contentDigest.slice(0, 20)}`,
61
67
  packageRoot,
62
68
  files: [...files].sort(compareReleaseFiles),
69
+ ...(dependencyLayers.length === 0 ? {} : { dependencyLayers: [...dependencyLayers] }),
63
70
  };
64
71
  }
65
72
  export function releaseFileIdentity(path, bytes, executable) {
@@ -18,7 +18,7 @@ export interface UpdateOutput {
18
18
  stdout(message: string): void;
19
19
  stderr(message: string): void;
20
20
  }
21
- export type UpdateMeasuredPhase = "package-version" | "target-resolution" | "global-root" | "ownership-release" | "npm-install" | "materialized" | "certified" | "active-reference-committed" | "supervisor-verified" | "transaction-complete";
21
+ export type UpdateMeasuredPhase = "package-version" | "target-resolution" | "global-root" | "ownership-release" | "npm-install" | "materialized" | "certified" | "active-reference-committed" | "warmup" | "supervisor-verified" | "transaction-complete";
22
22
  export interface UpdatePhaseTimingEvent {
23
23
  readonly phase: UpdateMeasuredPhase;
24
24
  readonly durationMs: number;
@@ -28,6 +28,12 @@ export interface UpdatePerformanceEvidence {
28
28
  readonly sourceReads: number;
29
29
  readonly candidateWrites: number;
30
30
  readonly verificationReads: number;
31
+ readonly layerWrites?: number;
32
+ readonly layerReusedFiles?: number;
33
+ readonly layerReusedBytes?: number;
34
+ readonly payloadExcludedFiles?: number;
35
+ readonly payloadExcludedBytes?: number;
36
+ readonly warmupDurationMs?: number;
31
37
  readonly postNpmDurationMs: number;
32
38
  }
33
39
  export interface SelfUpdateOptions {
@@ -41,6 +47,8 @@ export interface SelfUpdateOptions {
41
47
  runner?: UpdateProcessRunner;
42
48
  lifecycle?: UpdateLifecycleCoordinator;
43
49
  transactionStore?: UpdateTransactionJournal;
50
+ /** Test or embedding seam for post-activation release maintenance. */
51
+ maintenance?: () => Promise<void>;
44
52
  progress?: boolean;
45
53
  onPhaseTiming?: (event: UpdatePhaseTimingEvent) => void;
46
54
  now?: () => number;
@@ -61,7 +69,7 @@ export interface UpdateLifecycleCoordinator {
61
69
  priorActiveVersion: string | null;
62
70
  }>;
63
71
  verifyPackageUnlocked(packageRoot: string): Promise<void>;
64
- activateInstalled(packageRoot: string, targetVersion: string, phase: (phase: UpdateActivationPhase) => Promise<void>, onMaterializing?: (progress: UpdateMaterializationProgress) => void): Promise<void>;
72
+ activateInstalled(packageRoot: string, targetVersion: string, phase: (phase: UpdateActivationPhase) => Promise<void>, onMaterializing?: (progress: UpdateMaterializationProgress) => void, onWarmup?: (state: "started" | "completed") => void): Promise<void>;
65
73
  }
66
74
  export interface UpdateTransactionJournal {
67
75
  readonly path: string;
@@ -11,6 +11,8 @@ import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
11
11
  import { CohortStateStore } from "./cohort-state.js";
12
12
  import { cleanupVerifiedOwner, processIsAlive } from "./process-cleanup.js";
13
13
  import { materializeRelease, readMaterializedRelease } from "./release-store.js";
14
+ import { scheduleReleaseCleanup } from "./release-gc.js";
15
+ import { warmMaterializedRelease } from "./warmup.js";
14
16
  import { UpdateTransactionStore } from "./update-transaction.js";
15
17
  export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
16
18
  const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
@@ -138,7 +140,7 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
138
140
  throw new Error(PRODUCT_TEXT.diagnostic(`package remains locked after verified shutdown: ${errorMessage(error)}`));
139
141
  }
140
142
  },
141
- async activateInstalled(packageRoot, targetVersion, phase, onMaterializing) {
143
+ async activateInstalled(packageRoot, targetVersion, phase, onMaterializing, onWarmup) {
142
144
  let total = 0;
143
145
  let completed = 0;
144
146
  const candidate = await materializeRelease(packageRoot, paths.dataDir, {
@@ -147,7 +149,7 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
147
149
  onMaterializing?.({ completed, total });
148
150
  },
149
151
  onOperation: event => {
150
- if (event.operation !== "candidate-write")
152
+ if (event.operation !== "candidate-write" && event.operation !== "layer-write")
151
153
  return;
152
154
  completed += 1;
153
155
  onMaterializing?.({ completed, total });
@@ -162,6 +164,9 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
162
164
  await phase("certified");
163
165
  await stateStore.activate(candidate.releaseId);
164
166
  await phase("active-reference-committed");
167
+ onWarmup?.("started");
168
+ await warmMaterializedRelease(candidate, environment);
169
+ onWarmup?.("completed");
165
170
  await startSupervisor(candidate, environment);
166
171
  await waitForVerifiedEndpoint(resolveCohortEndpoint(paths, candidate.releaseId, environment).endpointMetadataPath, candidate, 8_000);
167
172
  },
@@ -364,6 +369,9 @@ export async function runSelfUpdate(options) {
364
369
  const paths = resolveProductPaths(environment);
365
370
  const lifecycle = options.lifecycle ?? createUpdateLifecycleCoordinator(environment, fileSystem, output);
366
371
  const transactionStore = options.transactionStore ?? new UpdateTransactionStore(paths.dataDir);
372
+ const maintenance = options.maintenance ?? (options.lifecycle
373
+ ? async () => { }
374
+ : async () => await scheduleReleaseCleanup(paths.dataDir, paths));
367
375
  let transaction = await transactionStore.read();
368
376
  try {
369
377
  if (await lifecycle.targetIsActive(targetVersion)) {
@@ -371,6 +379,7 @@ export async function runSelfUpdate(options) {
371
379
  await transactionStore.advance("supervisor-verified");
372
380
  await transactionStore.finish("completed");
373
381
  }
382
+ await maintenance();
374
383
  await transactionStore.clearCompleted();
375
384
  output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
376
385
  return 0;
@@ -432,11 +441,24 @@ export async function runSelfUpdate(options) {
432
441
  const span = MATERIALIZE_PROGRESS.to - MATERIALIZE_PROGRESS.from;
433
442
  const done = total > 0 ? Math.min(1, completed / total) : 0;
434
443
  progress.set(MATERIALIZE_PROGRESS.from + span * done);
444
+ }, state => {
445
+ if (state === "started") {
446
+ activationPhaseStartedAt = now();
447
+ progress.set(98, 99);
448
+ }
449
+ else {
450
+ options.onPhaseTiming?.({ phase: "warmup", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
451
+ activationPhaseStartedAt = now();
452
+ progress.set(99);
453
+ }
435
454
  });
436
455
  options.onPhaseTiming?.({ phase: "supervisor-verified", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
437
456
  const transactionStartedAt = now();
438
457
  await transactionStore.advance("supervisor-verified");
439
458
  await transactionStore.finish("completed");
459
+ // Invariant: successful output follows the durable cleanup disposition. Slow recursive
460
+ // removal belongs to the detached worker started by this maintenance coordinator.
461
+ await maintenance();
440
462
  await transactionStore.clearCompleted();
441
463
  options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
442
464
  progress.finish();
@@ -461,8 +483,9 @@ export function assertUpdatePerformanceBudget(evidence, maximumPostNpmDurationMs
461
483
  failures.push("fixture contains no payload files");
462
484
  if (evidence.sourceReads !== evidence.fileCount)
463
485
  failures.push(`source payload read count is ${evidence.sourceReads} for ${evidence.fileCount} files`);
464
- if (evidence.candidateWrites !== evidence.fileCount)
465
- failures.push(`candidate payload write count is ${evidence.candidateWrites} for ${evidence.fileCount} files`);
486
+ const totalWrites = evidence.candidateWrites + (evidence.layerWrites ?? 0);
487
+ if (totalWrites !== evidence.fileCount)
488
+ failures.push(`runtime payload write count is ${totalWrites} for ${evidence.fileCount} files`);
466
489
  if (evidence.verificationReads > 0)
467
490
  failures.push(`fresh certification reread ${evidence.verificationReads} candidate files`);
468
491
  if (evidence.postNpmDurationMs > maximumPostNpmDurationMs)
@@ -0,0 +1,3 @@
1
+ import type { MaterializedRelease } from "./release-store.js";
2
+ /** Import the exact immutable startup graph in a terminal-free bounded child process. */
3
+ export declare function warmMaterializedRelease(release: MaterializedRelease, environment: NodeJS.ProcessEnv, timeoutMs?: number): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { spawn } from "node:child_process";
2
+ import { resolve } from "node:path";
3
+ import { releaseEnvironment } from "./bootstrap.js";
4
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
5
+ /** Import the exact immutable startup graph in a terminal-free bounded child process. */
6
+ export async function warmMaterializedRelease(release, environment, timeoutMs = 15_000) {
7
+ const entry = resolve(release.releaseRoot, "bin", "warmup.js");
8
+ await new Promise((resolvePromise, rejectPromise) => {
9
+ const child = spawn(process.execPath, [entry], {
10
+ env: { ...releaseEnvironment(environment, release), [PRODUCT_IDENTITY.environment.immutableWarmup]: "1" },
11
+ stdio: "ignore",
12
+ windowsHide: true,
13
+ });
14
+ let settled = false;
15
+ const finish = (error) => {
16
+ if (settled)
17
+ return;
18
+ settled = true;
19
+ clearTimeout(timer);
20
+ if (error)
21
+ rejectPromise(error);
22
+ else
23
+ resolvePromise();
24
+ };
25
+ child.once("error", error => finish(error));
26
+ child.once("close", (code, signal) => finish(code === 0
27
+ ? undefined
28
+ : new Error(`immutable startup warmup exited with ${code === null ? signal ?? "unknown status" : `status ${code}`}`)));
29
+ const timer = setTimeout(() => {
30
+ child.kill();
31
+ finish(new Error(`immutable startup warmup exceeded ${timeoutMs}ms`));
32
+ }, timeoutMs);
33
+ timer.unref?.();
34
+ });
35
+ }
@@ -0,0 +1 @@
1
+ export * from "./startup-runtime.js";
@@ -0,0 +1 @@
1
+ export * from "./startup-runtime.js";
@@ -0,0 +1,36 @@
1
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
2
+ export type StartupPhase = "command-invoked" | "bootstrap-start" | "bootstrap-selected" | "guardian-start" | "guardian-connected" | "ui-entry" | "ui-modules-loaded" | "pi-services" | "resource-discovery" | "session-created" | "settings-loaded" | "first-input-ready-render";
3
+ export interface StartupPerformanceEvidence {
4
+ readonly profileId: "a1" | "pi";
5
+ readonly launchKind: "post-update" | "warm";
6
+ readonly events: readonly StartupTraceEvent[];
7
+ }
8
+ export interface StartupTraceEvent {
9
+ readonly schema: typeof PRODUCT_IDENTITY.evidence.startupTraceSchema;
10
+ readonly traceId: string;
11
+ readonly phase: StartupPhase;
12
+ readonly elapsedMs: number;
13
+ readonly processId: number;
14
+ readonly profileId: string;
15
+ readonly releaseId: string | null;
16
+ readonly dependencyLayerIds: readonly string[];
17
+ readonly nodeVersion: string;
18
+ readonly fileReadOperations: number;
19
+ }
20
+ /** Initialize opt-in startup tracing from a caller-provided evidence path. */
21
+ export declare function initializeStartupTrace(environment: NodeJS.ProcessEnv, profileId: string, startedAtMs?: number): void;
22
+ /** Append one redacted phase event; ordinary launches without trace context perform no I/O. */
23
+ export declare function markStartupPhase(environment: NodeJS.ProcessEnv, phase: StartupPhase): Promise<void>;
24
+ export declare function assertImmutableWarmupEnvironment(environment: NodeJS.ProcessEnv): void;
25
+ /** Enable the compile cache directly from launch environment without loading profile services. */
26
+ export declare function enableEnvironmentCompileCache(environment: NodeJS.ProcessEnv): string | null;
27
+ /** Enable Node's supported persistent compile cache in an immutable-identity namespace. */
28
+ export declare function enableStartupCompileCache(dataDir: string, releaseId: string | null, dependencyLayerIds: readonly string[]): string | null;
29
+ export declare function startupCompileCachePath(dataDir: string, releaseId: string | null, dependencyLayerIds: readonly string[]): string;
30
+ /** Retain current compile namespaces plus a bounded number of recent fallbacks. */
31
+ export declare function collectCompileCaches(dataDir: string, protectedPaths: readonly string[], keepRecent?: number): Promise<void>;
32
+ export declare function assertStartupPerformanceBudget(evidence: StartupPerformanceEvidence, budgets?: {
33
+ readonly postUpdateMs: number;
34
+ readonly warmMs: number;
35
+ }): void;
36
+ export declare function parseStartupTrace(source: string): readonly StartupTraceEvent[];
@@ -0,0 +1,140 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { appendFile, mkdir, readdir, rm } from "node:fs/promises";
3
+ import { constants as moduleConstants, enableCompileCache } from "node:module";
4
+ import { dirname, resolve } from "node:path";
5
+ import { homedir, platform } from "node:os";
6
+ import { performance } from "node:perf_hooks";
7
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
8
+ /** Initialize opt-in startup tracing from a caller-provided evidence path. */
9
+ export function initializeStartupTrace(environment, profileId, startedAtMs = performance.timeOrigin + performance.now()) {
10
+ const configured = environment[PRODUCT_IDENTITY.environment.startupTrace];
11
+ if (!configured || parseContext(configured) !== null)
12
+ return;
13
+ const context = {
14
+ path: resolve(configured),
15
+ traceId: randomUUID(),
16
+ startedAtMs,
17
+ profileId,
18
+ releaseId: environment[PRODUCT_IDENTITY.environment.releaseId] ?? null,
19
+ dependencyLayerIds: parseLayerIds(environment[PRODUCT_IDENTITY.environment.releaseLayers]),
20
+ };
21
+ environment[PRODUCT_IDENTITY.environment.startupTrace] = JSON.stringify(context);
22
+ }
23
+ /** Append one redacted phase event; ordinary launches without trace context perform no I/O. */
24
+ export async function markStartupPhase(environment, phase) {
25
+ const context = parseContext(environment[PRODUCT_IDENTITY.environment.startupTrace]);
26
+ if (context === null)
27
+ return;
28
+ const event = {
29
+ schema: PRODUCT_IDENTITY.evidence.startupTraceSchema,
30
+ traceId: context.traceId,
31
+ phase,
32
+ elapsedMs: Math.max(0, performance.timeOrigin + performance.now() - context.startedAtMs),
33
+ processId: process.pid,
34
+ profileId: context.profileId,
35
+ releaseId: environment[PRODUCT_IDENTITY.environment.releaseId] ?? context.releaseId,
36
+ dependencyLayerIds: parseLayerIds(environment[PRODUCT_IDENTITY.environment.releaseLayers]).length > 0
37
+ ? parseLayerIds(environment[PRODUCT_IDENTITY.environment.releaseLayers])
38
+ : context.dependencyLayerIds,
39
+ nodeVersion: process.version,
40
+ fileReadOperations: process.resourceUsage().fsRead,
41
+ };
42
+ await mkdir(dirname(context.path), { recursive: true, mode: 0o700 });
43
+ await appendFile(context.path, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600 });
44
+ }
45
+ export function assertImmutableWarmupEnvironment(environment) {
46
+ if (environment[PRODUCT_IDENTITY.environment.immutableWarmup] !== "1") {
47
+ throw new Error("warmup entry is private to verified update activation");
48
+ }
49
+ }
50
+ /** Enable the compile cache directly from launch environment without loading profile services. */
51
+ export function enableEnvironmentCompileCache(environment) {
52
+ const home = environment.HOME ?? environment.USERPROFILE ?? homedir();
53
+ const dataDir = resolve(environment[PRODUCT_IDENTITY.environment.dataDir]
54
+ ?? (platform() === "win32"
55
+ ? resolve(environment.LOCALAPPDATA ?? home, PRODUCT_IDENTITY.state.windowsControlDirectory)
56
+ : resolve(environment.XDG_DATA_HOME ?? resolve(home, ".local", "share"), PRODUCT_IDENTITY.state.unixControlDirectory)));
57
+ return enableStartupCompileCache(dataDir, environment[PRODUCT_IDENTITY.environment.releaseId] ?? null, parseLayerIds(environment[PRODUCT_IDENTITY.environment.releaseLayers]));
58
+ }
59
+ /** Enable Node's supported persistent compile cache in an immutable-identity namespace. */
60
+ export function enableStartupCompileCache(dataDir, releaseId, dependencyLayerIds) {
61
+ try {
62
+ const path = startupCompileCachePath(dataDir, releaseId, dependencyLayerIds);
63
+ const result = enableCompileCache(path);
64
+ return result.status === moduleConstants.compileCacheStatus.FAILED ? null : result.directory ?? path;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ export function startupCompileCachePath(dataDir, releaseId, dependencyLayerIds) {
71
+ const layers = createHash("sha256").update(dependencyLayerIds.join("\0")).digest("hex").slice(0, 20);
72
+ const content = dependencyLayerIds.length > 0
73
+ ? `layers-${layers}`
74
+ : `release-${(releaseId ?? "mutable").replace(/[^0-9A-Za-z.+_-]/g, "_")}`;
75
+ // Performance: stable layer paths share compiled dependency entries across product releases;
76
+ // Node's own cache key still isolates each release-specific module path and source bytes.
77
+ return resolve(dataDir, "cache", "compile", `${process.versions.modules ?? "node"}-${process.version.replace(/[^0-9A-Za-z.-]/g, "_")}-${content}`);
78
+ }
79
+ /** Retain current compile namespaces plus a bounded number of recent fallbacks. */
80
+ export async function collectCompileCaches(dataDir, protectedPaths, keepRecent = 2) {
81
+ const root = resolve(dataDir, "cache", "compile");
82
+ const protectedSet = new Set(protectedPaths.map(path => resolve(path)));
83
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
84
+ const candidates = await Promise.all(entries.filter(entry => entry.isDirectory()).map(async (entry) => {
85
+ const path = resolve(root, entry.name);
86
+ const metadata = await import("node:fs/promises").then(fs => fs.stat(path));
87
+ return { path, mtimeMs: metadata.mtimeMs };
88
+ }));
89
+ const retainedRecent = new Set(candidates.filter(item => !protectedSet.has(item.path))
90
+ .sort((left, right) => right.mtimeMs - left.mtimeMs)
91
+ .slice(0, keepRecent)
92
+ .map(item => item.path));
93
+ for (const candidate of candidates) {
94
+ if (!protectedSet.has(candidate.path) && !retainedRecent.has(candidate.path))
95
+ await rm(candidate.path, { recursive: true, force: true });
96
+ }
97
+ }
98
+ export function assertStartupPerformanceBudget(evidence, budgets = { postUpdateMs: 5_000, warmMs: 3_000 }) {
99
+ const events = [...evidence.events].sort((left, right) => left.elapsedMs - right.elapsedMs);
100
+ const ready = events.findLast(event => event.phase === "first-input-ready-render");
101
+ if (!ready)
102
+ throw new Error(`startup budget failed for ${evidence.profileId}: first input-ready render was not recorded`);
103
+ const budget = evidence.launchKind === "post-update" ? budgets.postUpdateMs : budgets.warmMs;
104
+ if (ready.elapsedMs <= budget)
105
+ return;
106
+ const intervals = events.map((event, index) => ({
107
+ phase: event.phase,
108
+ durationMs: event.elapsedMs - (events[index - 1]?.elapsedMs ?? 0),
109
+ })).sort((left, right) => right.durationMs - left.durationMs);
110
+ throw new Error(`startup budget failed for ${evidence.profileId} ${evidence.launchKind}: ${Math.round(ready.elapsedMs)}ms exceeds ${budget}ms; dominant phases: ${intervals.slice(0, 3).map(item => `${item.phase} ${Math.round(item.durationMs)}ms`).join(", ")}`);
111
+ }
112
+ export function parseStartupTrace(source) {
113
+ const events = source.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
114
+ for (const event of events) {
115
+ if (event.schema !== PRODUCT_IDENTITY.evidence.startupTraceSchema || typeof event.traceId !== "string"
116
+ || typeof event.phase !== "string" || !Number.isFinite(event.elapsedMs) || !Array.isArray(event.dependencyLayerIds)) {
117
+ throw new Error("startup trace contains an invalid event");
118
+ }
119
+ }
120
+ return events.sort((left, right) => left.elapsedMs - right.elapsedMs);
121
+ }
122
+ function parseContext(value) {
123
+ if (!value?.startsWith("{"))
124
+ return null;
125
+ try {
126
+ const context = JSON.parse(value);
127
+ if (typeof context.path !== "string" || typeof context.traceId !== "string" || !Number.isFinite(context.startedAtMs)
128
+ || typeof context.profileId !== "string" || !Array.isArray(context.dependencyLayerIds))
129
+ return null;
130
+ return context;
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ function parseLayerIds(value) {
137
+ if (!value)
138
+ return [];
139
+ return [...new Set(value.split(",").filter(id => /^dependencies-[a-f0-9]{32}$/.test(id)))].sort();
140
+ }
@@ -1,5 +1,6 @@
1
1
  import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
2
2
  import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
3
+ import { markStartupPhase } from "../../../foundation/startup/index.js";
3
4
  /**
4
5
  * Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
5
6
  * into a scoped model list, keep the resolver's warnings (e.g. "No models match
@@ -41,7 +42,10 @@ export async function createPiRuntimeServicesAfterTrust(options) {
41
42
  ...(options.projectTrustPrompt === undefined ? {} : { prompt: options.projectTrustPrompt }),
42
43
  });
43
44
  const settingsManager = createSettingsManager(options.cwd, options.agentDir, trust.trusted);
45
+ await markStartupPhase(process.env, "settings-loaded");
44
46
  const services = await createServices({ cwd: options.cwd, agentDir: options.agentDir, settingsManager });
47
+ await markStartupPhase(process.env, "pi-services");
48
+ await markStartupPhase(process.env, "resource-discovery");
45
49
  return { services, trust };
46
50
  }
47
51
  export async function createPiRuntimeIntegration(options) {
@@ -66,6 +70,7 @@ export async function createPiRuntimeIntegration(options) {
66
70
  ...(modelScope.thinkingLevel && !hasExistingSession ? { thinkingLevel: modelScope.thinkingLevel } : {}),
67
71
  ...(modelScope.scopedModels.length > 0 ? { scopedModels: [...modelScope.scopedModels] } : {}),
68
72
  });
73
+ await markStartupPhase(process.env, "session-created");
69
74
  return {
70
75
  ...created,
71
76
  services,
@@ -5,11 +5,11 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-09-02T18:03:00.638Z",
8
+ "builtAt": "2026-09-03T16:39:37.425Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
- "sha256": "d9159fe44b7eab5ce46ebdc4da5c4bc90f090d211ed52856ffa1d5bff362e430",
12
- "size": 356784
11
+ "sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
12
+ "size": 356368
13
13
  },
14
14
  "provenance": {
15
15
  "language": "Rust",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-02T18:03:04.391Z",
8
+ "builtAt": "2026-09-03T16:39:27.399Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-02T18:03:45.052Z",
8
+ "builtAt": "2026-09-03T16:40:47.022Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "261e3a4a54d967849c35479b09cac3d4dc2e20c3e5099050380137539b7b19c2",
11
+ "sha256": "2fe463dcdeaedfbc9df817ac61e973f27500e9527d05bd781553da89d6767926",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -1,10 +1,10 @@
1
- declare const ENVIRONMENT_KEYS: readonly ["certificationTarball", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "fixture", "fixtureInput", "fixtureToken", "inputAcknowledgement", "internalPackaging", "launchProfile", "nativePi", "paneId", "piParityIntentionalMutation", "piPortRoot", "piSourceLedgerPath", "piSourceScanRoot", "probeTrace", "processGuardianPath", "profileHome", "protocolVersion", "releaseDigest", "releaseId", "releaseRoot", "releaseRunnerLabel", "runtimeDir", "structuredFlowLimits", "terminalArgumentsJson", "terminalExecutable", "terminalSessionId"];
1
+ declare const ENVIRONMENT_KEYS: readonly ["certificationTarball", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "fixture", "fixtureInput", "fixtureToken", "inputAcknowledgement", "immutableWarmup", "internalPackaging", "launchProfile", "nativePi", "paneId", "piParityIntentionalMutation", "piPortRoot", "piSourceLedgerPath", "piSourceScanRoot", "probeTrace", "processGuardianPath", "profileHome", "protocolVersion", "releaseDigest", "releaseId", "releaseLayers", "releaseRoot", "releaseRunnerLabel", "runtimeDir", "structuredFlowLimits", "startupTrace", "terminalArgumentsJson", "terminalExecutable", "terminalSessionId"];
2
2
  declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
3
3
  declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
4
4
  declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
5
5
  declare const MANIFEST_KEYS: readonly ["releaseFilename", "packageFilename"];
6
6
  declare const PROTOCOL_KEYS: readonly ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "releaseCohortSchema", "updateJournalSchema"];
7
- declare const EVIDENCE_KEYS: readonly ["nativeSpikeSchema", "terminalProvenanceSchema", "terminalProofSchema", "stableReleaseSchema", "previewReleaseSchema", "releaseCertificationSchema", "previewPlatformVerdictSchema", "piSourceLedgerSchema", "piComponentParitySchema", "piEventFrameParitySchema"];
7
+ declare const EVIDENCE_KEYS: readonly ["nativeSpikeSchema", "terminalProvenanceSchema", "terminalProofSchema", "stableReleaseSchema", "previewReleaseSchema", "releaseCertificationSchema", "previewPlatformVerdictSchema", "piSourceLedgerSchema", "piComponentParitySchema", "piEventFrameParitySchema", "startupTraceSchema", "dependencyLayerSchema", "dependencyLayerCertificationSchema", "runtimePayloadSchema"];
8
8
  declare const ARTIFACT_KEYS: readonly ["cliEntry", "supervisorEntry", "guardianEntry", "uiEntry", "nativeExecutable", "nativeCrate", "processGuardianExecutable", "releaseTarballStem", "diagnosticStem"];
9
9
  type StringRecord<Keys extends readonly string[]> = {
10
10
  readonly [Key in Keys[number]]: string;