@mono-agent/agent-app 0.9.0 → 0.10.0

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.
@@ -11,6 +11,7 @@ export const MANAGED_BACKGROUND_WORKER_ENV = "MONO_AGENT_MANAGED_WORKER";
11
11
  const LOCK_WAIT_TIMEOUT_MS = 30_000;
12
12
  const LOCK_STALE_AFTER_MS = 5 * 60_000;
13
13
  const LOCK_POLL_INTERVAL_MS = 200;
14
+ const PROVISIONAL_RUNTIME_INSTALLED_AT = "1970-01-01T00:00:00.000Z";
14
15
  export function defaultManagedBackgroundRuntimeDeps() {
15
16
  return {
16
17
  now: () => Date.now(),
@@ -72,20 +73,23 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
72
73
  const id = safeSegment(deps.randomId());
73
74
  const layout = runtimeLayout(home, identity, id, deps.now());
74
75
  await ensurePrivateRuntimeAncestors(home, layout.versionAbiDir);
75
- if (await verifyRuntime(layout, identity)) {
76
+ if (await verifyRuntime(layout, identity, additionalPackages)) {
77
+ await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
76
78
  return runtimeResult(layout, identity, input.nodePath);
77
79
  }
78
- const acquired = await acquireRuntimeLock(layout, identity, deps);
80
+ const acquired = await acquireRuntimeLock(layout, identity, additionalPackages, deps);
79
81
  if (!acquired) {
80
82
  // Another installer may have completed between the final lock poll and the
81
83
  // timeout boundary. Verify one last time before reporting the contention.
82
- if (await verifyRuntime(layout, identity)) {
84
+ if (await verifyRuntime(layout, identity, additionalPackages)) {
85
+ await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
83
86
  return runtimeResult(layout, identity, input.nodePath);
84
87
  }
85
88
  throw new Error(`Timed out waiting for the managed runtime installation lock at ${layout.lockDir}.`);
86
89
  }
87
90
  try {
88
- if (await verifyRuntime(layout, identity)) {
91
+ if (await verifyRuntime(layout, identity, additionalPackages)) {
92
+ await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
89
93
  return runtimeResult(layout, identity, input.nodePath);
90
94
  }
91
95
  await assertSourceClosureUnchanged(packageSource, additionalPackages, sourceClosure, "before staging");
@@ -112,12 +116,24 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
112
116
  throw new Error(`Installed ${PACKAGE_NAME}@${packageVersion} does not match the executing CLI SHA-256 ${identity.cliSha256}.`);
113
117
  }
114
118
  const closureManifestSha256 = await writeClosureManifest(staged);
119
+ const stagedPackageSource = dirname(dirname(staged.cliPath));
120
+ const stagedAdditionalPackages = additionalPackages.map(({ packageName }) => ({
121
+ packageName,
122
+ packageSource: join(staged.installRoot, "node_modules", ...packageNameSegments(packageName)),
123
+ }));
124
+ const stagedExecutionClosure = await captureExecutionClosure(stagedPackageSource, stagedAdditionalPackages, staged.installRoot);
125
+ if (stagedExecutionClosure.sourceClosureSha256 !== identity.sourceClosureSha256) {
126
+ throw new Error("The staged managed runtime execution closure does not match its source identity.");
127
+ }
115
128
  const marker = {
116
- schema: "mono-agent.managed-runtime.v3",
129
+ schema: "mono-agent.managed-runtime.v4",
117
130
  packageName: PACKAGE_NAME,
118
131
  closureManifestSha256,
132
+ executionProofSha256: stagedExecutionClosure.filesystemProofSha256,
119
133
  ...identity,
120
- installedAt: new Date(deps.now()).toISOString(),
134
+ // Provisional only. Promotion rewrites this with the post-proof launch
135
+ // boundary; a crash before that rewrite must never look finalized.
136
+ installedAt: PROVISIONAL_RUNTIME_INSTALLED_AT,
121
137
  };
122
138
  await writePrivateJson(staged.markerPath, marker);
123
139
  await chmod(layout.stagingDir, 0o700);
@@ -125,22 +141,124 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
125
141
  // runtime aside. This is essential when the executing CLI (and therefore
126
142
  // packageSource) lives inside that invalid runtime: quarantine must never
127
143
  // make the only repair source disappear halfway through installation.
128
- await quarantineInvalidRuntime(layout, identity, deps);
129
- await promoteStaging(layout, staged, identity);
144
+ await quarantineInvalidRuntime(layout, identity, additionalPackages, deps);
145
+ const promoted = await promoteStaging(layout, staged, identity, additionalPackages);
146
+ if (promoted) {
147
+ // Renaming the staging root changes that directory's ctime. Rebind the
148
+ // marker to the post-promotion resolution-path identities before any
149
+ // caller can persist or launch this runtime.
150
+ const promotedPackageSource = dirname(dirname(layout.cliPath));
151
+ const promotedAdditionalPackages = additionalPackages.map(({ packageName }) => ({
152
+ packageName,
153
+ packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
154
+ }));
155
+ const promotedClosure = await captureExecutionClosure(promotedPackageSource, promotedAdditionalPackages, layout.installRoot);
156
+ if (promotedClosure.sourceClosureSha256 !== identity.sourceClosureSha256) {
157
+ throw new Error("The promoted managed runtime execution closure does not match its source identity.");
158
+ }
159
+ await writeFinalizedRuntimeMarker(layout.markerPath, {
160
+ ...marker,
161
+ executionProofSha256: promotedClosure.filesystemProofSha256,
162
+ }, deps);
163
+ }
130
164
  }
131
165
  catch (error) {
132
166
  await rm(layout.stagingDir, { recursive: true, force: true }).catch(() => undefined);
133
167
  throw error;
134
168
  }
135
- if (!(await verifyRuntime(layout, identity))) {
169
+ if (!(await verifyRuntime(layout, identity, additionalPackages))) {
136
170
  throw new Error("The managed runtime failed verification after atomic promotion.");
137
171
  }
172
+ await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
138
173
  return runtimeResult(layout, identity, input.nodePath);
139
174
  }
140
175
  finally {
141
176
  await removeSameRuntimeLockDirectory(layout.lockDir, acquired).catch(() => undefined);
142
177
  }
143
178
  }
179
+ /**
180
+ * Prove, without installing or repairing anything, that a persisted managed
181
+ * runtime is the canonical private copy of the exact deploy-source execution
182
+ * closure (including config-selected plugin roots).
183
+ */
184
+ export async function attestManagedBackgroundRuntime(input, deps = {}) {
185
+ const currentCliPath = resolve(input.currentCliPath);
186
+ await assertRegularFile(currentCliPath, "current mono-agent CLI");
187
+ const packageSource = resolve(input.packageSource ?? packageRootForCli(currentCliPath));
188
+ const sourceDetails = await lstat(packageSource);
189
+ if (!sourceDetails.isDirectory() || sourceDetails.isSymbolicLink()) {
190
+ throw new Error(`Managed runtime package source ${packageSource} must be a real directory.`);
191
+ }
192
+ const additionalPackages = await canonicalAdditionalPackages(input.additionalPackages ?? []);
193
+ const sourceClosureInitial = await captureExecutionClosure(packageSource, additionalPackages);
194
+ const packageVersion = input.packageVersion ?? await packageVersionAt(packageSource);
195
+ if (!isExactVersion(packageVersion)) {
196
+ throw new Error(`Cannot attest a durable managed runtime for invalid package version ${JSON.stringify(packageVersion)}.`);
197
+ }
198
+ const identity = {
199
+ packageVersion,
200
+ cliSha256: sha256(await readFile(currentCliPath)),
201
+ sourceClosureSha256: sourceClosureInitial.sourceClosureSha256,
202
+ nodeAbi: input.nodeAbi ?? requiredNodeAbi(),
203
+ platform: input.platform ?? process.platform,
204
+ arch: input.arch ?? process.arch,
205
+ };
206
+ const home = resolve(input.homeDir ?? accountHomeDirectory());
207
+ const layout = runtimeLayout(home, identity, "attestation", 0);
208
+ if (resolve(input.runtimeCliPath) !== layout.cliPath) {
209
+ throw new Error("The managed runtime CLI is not at the canonical execution-closure path.");
210
+ }
211
+ if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir))) {
212
+ throw new Error("The managed runtime has an unsafe ancestor directory.");
213
+ }
214
+ const markerInitial = await verifiedRuntimeMarker(layout, identity);
215
+ if (markerInitial === undefined) {
216
+ throw new Error("The managed runtime marker or closure manifest is invalid.");
217
+ }
218
+ const runtimePackageSource = dirname(dirname(layout.cliPath));
219
+ const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
220
+ packageName,
221
+ packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
222
+ }));
223
+ const runtimeClosureInitial = await captureExecutionClosure(runtimePackageSource, runtimeAdditionalPackages, layout.installRoot);
224
+ if (runtimeClosureInitial.sourceClosureSha256 !== sourceClosureInitial.sourceClosureSha256) {
225
+ throw new Error("The managed runtime execution closure does not match the deploy source closure.");
226
+ }
227
+ if (runtimeClosureInitial.filesystemProofSha256 !== markerInitial.executionProofSha256) {
228
+ throw new Error("The managed runtime filesystem proof does not match its install-time proof.");
229
+ }
230
+ await deps.afterInitialCaptures?.();
231
+ const sourceClosureFinal = await captureExecutionClosure(packageSource, additionalPackages);
232
+ if (sourceClosureFinal.sourceClosureSha256 !== sourceClosureInitial.sourceClosureSha256
233
+ || sourceClosureFinal.sourceProofSha256 !== sourceClosureInitial.sourceProofSha256) {
234
+ throw new Error("The deploy source execution closure changed while it was attested.");
235
+ }
236
+ const runtimeClosureFinal = await captureExecutionClosure(runtimePackageSource, runtimeAdditionalPackages, layout.installRoot);
237
+ if (runtimeClosureFinal.sourceClosureSha256 !== runtimeClosureInitial.sourceClosureSha256
238
+ || runtimeClosureFinal.sourceProofSha256 !== runtimeClosureInitial.sourceProofSha256) {
239
+ throw new Error("The managed runtime execution closure changed while it was attested.");
240
+ }
241
+ if (runtimeClosureFinal.filesystemProofSha256 !== markerInitial.executionProofSha256) {
242
+ throw new Error("The managed runtime filesystem proof changed after installation.");
243
+ }
244
+ const markerFinal = await verifiedRuntimeMarker(layout, identity);
245
+ if (markerFinal === undefined || JSON.stringify(markerFinal) !== JSON.stringify(markerInitial)) {
246
+ throw new Error("The managed runtime changed while it was attested.");
247
+ }
248
+ const fingerprint = sha256(Buffer.from(JSON.stringify({
249
+ schema: "mono-agent.managed-runtime-attestation.v1",
250
+ identity,
251
+ sourceProofSha256: sourceClosureFinal.sourceProofSha256,
252
+ runtimeProofSha256: runtimeClosureFinal.sourceProofSha256,
253
+ closureManifestSha256: markerInitial.closureManifestSha256,
254
+ installedAt: markerInitial.installedAt,
255
+ }), "utf8"));
256
+ return {
257
+ schema: "mono-agent.managed-runtime-attestation.v1",
258
+ fingerprint,
259
+ installedAt: markerInitial.installedAt,
260
+ };
261
+ }
144
262
  /**
145
263
  * Copy the exact package graph the current CLI is already executing.
146
264
  *
@@ -186,7 +304,7 @@ async function materializeExactExecutionClosure(input) {
186
304
  }
187
305
  for (const node of closure) {
188
306
  const destination = requiredMapValue(destinations, node.id, "package destination");
189
- for (const [dependencyName, dependencyId] of [...node.dependencies].sort(([left], [right]) => left.localeCompare(right))) {
307
+ for (const [dependencyName, dependencyId] of [...node.dependencies].sort(([left], [right]) => compareCodeUnits(left, right))) {
190
308
  const dependencyDestination = requiredMapValue(destinations, dependencyId, "dependency destination");
191
309
  const linkPath = join(destination, "node_modules", ...packageNameSegments(dependencyName));
192
310
  await mkdir(dirname(linkPath), { recursive: true, mode: 0o700 });
@@ -204,7 +322,7 @@ async function materializeExactExecutionClosure(input) {
204
322
  return [portableRelativePath(input.stagingDir, destination), {
205
323
  name: node.metadata.name,
206
324
  version: node.metadata.version,
207
- dependencies: Object.fromEntries([...node.dependencies].sort(([left], [right]) => left.localeCompare(right)).map(([dependencyName, dependencyId]) => [
325
+ dependencies: Object.fromEntries([...node.dependencies].sort(([left], [right]) => compareCodeUnits(left, right)).map(([dependencyName, dependencyId]) => [
208
326
  dependencyName,
209
327
  requiredClosureNode(closure, dependencyId).metadata.version,
210
328
  ])),
@@ -235,12 +353,18 @@ async function materializeExactExecutionClosure(input) {
235
353
  throw new Error("The managed runtime package closure changed while it was staged.");
236
354
  }
237
355
  }
238
- async function captureExecutionClosure(packageSource, additionalPackages) {
356
+ async function captureExecutionClosure(packageSource, additionalPackages, filesystemProofRoot) {
239
357
  const nodes = [];
240
358
  const bySourceRoot = new Map();
241
359
  const proofs = new Map();
360
+ const dependencyProofs = [];
361
+ const resolutionPathProof = filesystemProofRoot === undefined
362
+ ? undefined
363
+ : await createResolutionPathProof(filesystemProofRoot);
242
364
  const visit = async (source) => {
365
+ await resolutionPathProof?.captureAncestors(source);
243
366
  const sourceRoot = await realpath(source);
367
+ await resolutionPathProof?.captureAncestors(sourceRoot);
244
368
  const existing = bySourceRoot.get(sourceRoot);
245
369
  if (existing !== undefined)
246
370
  return existing;
@@ -258,29 +382,51 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
258
382
  proofs.set(id, sourcePackage.proof);
259
383
  bySourceRoot.set(sourceRoot, id);
260
384
  for (const [dependencyName, optional] of dependencyRequirements(metadata)) {
261
- const dependencyRoot = await resolveInstalledDependencyPackageRoot(sourceRoot, dependencyName);
262
- if (dependencyRoot === undefined) {
385
+ const dependency = await resolveInstalledDependencyPackageRoot(sourceRoot, dependencyName);
386
+ if (dependency === undefined) {
263
387
  if (optional)
264
388
  continue;
265
389
  throw new Error(`Cannot preserve the executing dependency closure: ${metadata.name}@${metadata.version} ` +
266
390
  `cannot resolve required dependency ${dependencyName}. Build or install the package completely before starting it.`);
267
391
  }
268
- node.dependencies.set(dependencyName, await visit(dependencyRoot));
392
+ await resolutionPathProof?.captureAncestors(dependency.candidatePath);
393
+ const dependencyNodeId = await visit(dependency.packageRoot);
394
+ const [dependencyProofAfter, dependencyRootAfter] = await Promise.all([
395
+ captureFilesystemPathProof(dependency.candidatePath, dependencyName),
396
+ realpath(dependency.candidatePath),
397
+ ]);
398
+ if (dependencyRootAfter !== dependency.packageRoot
399
+ || !sameSourceProofEntry(dependency.proof, dependencyProofAfter)) {
400
+ throw new Error(`Installed dependency ${dependencyName} changed while its execution closure was inspected.`);
401
+ }
402
+ dependencyProofs.push({ ownerId: id, dependencyName, proof: dependencyProofAfter });
403
+ node.dependencies.set(dependencyName, dependencyNodeId);
269
404
  }
270
405
  return id;
271
406
  };
272
407
  await visit(packageSource);
273
408
  const additionalRoots = [];
409
+ const additionalProofs = [];
274
410
  for (const additional of additionalPackages) {
411
+ const additionalProof = await captureFilesystemPathProof(additional.packageSource, additional.packageName);
275
412
  const nodeId = await visit(additional.packageSource);
276
413
  const node = requiredClosureNode(nodes, nodeId);
277
414
  if (node.metadata.name !== additional.packageName) {
278
415
  throw new Error(`Additional managed runtime package ${additional.packageSource} declares ${node.metadata.name}, ` +
279
416
  `expected ${additional.packageName}.`);
280
417
  }
418
+ const [additionalProofAfter, additionalRootAfter] = await Promise.all([
419
+ captureFilesystemPathProof(additional.packageSource, additional.packageName),
420
+ realpath(additional.packageSource),
421
+ ]);
422
+ if (additionalRootAfter !== node.sourceRoot || !sameSourceProofEntry(additionalProof, additionalProofAfter)) {
423
+ throw new Error(`Additional managed runtime package ${additional.packageName} changed while its execution closure was inspected.`);
424
+ }
425
+ additionalProofs.push({ packageName: additional.packageName, proof: additionalProofAfter });
281
426
  additionalRoots.push({ packageName: additional.packageName, nodeId });
282
427
  }
283
428
  const closure = nodes;
429
+ const resolutionPaths = await resolutionPathProof?.finalize() ?? [];
284
430
  const stable = {
285
431
  schema: "mono-agent.source-execution-closure.v1",
286
432
  roots: {
@@ -292,7 +438,7 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
292
438
  name: node.metadata.name,
293
439
  version: node.metadata.version,
294
440
  dependencies: [...node.dependencies]
295
- .sort(([left], [right]) => left.localeCompare(right))
441
+ .sort(([left], [right]) => compareCodeUnits(left, right))
296
442
  .map(([name, nodeId]) => ({ name, nodeId })),
297
443
  sourcePackage: node.sourcePackage,
298
444
  })),
@@ -305,12 +451,26 @@ async function captureExecutionClosure(packageSource, additionalPackages) {
305
451
  sourceRoot: node.sourceRoot,
306
452
  entries: requiredMapValue(proofs, node.id, "source package proof"),
307
453
  })),
454
+ dependencyLinks: dependencyProofs,
455
+ additionalLinks: additionalProofs,
456
+ resolutionPaths,
457
+ };
458
+ const filesystemProof = {
459
+ schema: "mono-agent.source-filesystem-proof.v1",
460
+ packages: closure.map((node) => ({
461
+ id: node.id,
462
+ entries: requiredMapValue(proofs, node.id, "source package proof"),
463
+ })),
464
+ dependencyLinks: dependencyProofs,
465
+ additionalLinks: additionalProofs,
466
+ resolutionPaths,
308
467
  };
309
468
  return {
310
469
  nodes: closure,
311
470
  additionalRoots,
312
471
  sourceClosureSha256: sha256(Buffer.from(JSON.stringify(stable), "utf8")),
313
472
  sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
473
+ filesystemProofSha256: sha256(Buffer.from(JSON.stringify(filesystemProof), "utf8")),
314
474
  };
315
475
  }
316
476
  async function canonicalAdditionalPackages(packages) {
@@ -334,7 +494,7 @@ async function canonicalAdditionalPackages(packages) {
334
494
  byName.set(entry.packageName, canonical);
335
495
  }
336
496
  return [...byName]
337
- .sort(([left], [right]) => left.localeCompare(right))
497
+ .sort(([left], [right]) => compareCodeUnits(left, right))
338
498
  .map(([packageName, packageSource]) => ({ packageName, packageSource }));
339
499
  }
340
500
  async function assertSourceClosureUnchanged(packageSource, additionalPackages, expected, phase) {
@@ -366,7 +526,7 @@ async function captureSourcePackage(packageRoot) {
366
526
  }
367
527
  entries.push({ path: pathRelative, type: "directory", mode: fileMode(before.mode) });
368
528
  proof.push(sourceProofEntry(pathRelative, "directory", before));
369
- const childNames = (await readdir(directory)).sort((left, right) => left.localeCompare(right));
529
+ const childNames = (await readdir(directory)).sort(compareCodeUnits);
370
530
  for (const name of childNames) {
371
531
  const path = join(directory, name);
372
532
  const childRelative = sourcePackageRelativePath(root, path);
@@ -395,7 +555,7 @@ async function captureSourcePackage(packageRoot) {
395
555
  }
396
556
  const [after, childNamesAfter] = await Promise.all([
397
557
  lstat(directory, { bigint: true }),
398
- readdir(directory).then((names) => names.sort((left, right) => left.localeCompare(right))),
558
+ readdir(directory).then((names) => names.sort(compareCodeUnits)),
399
559
  ]);
400
560
  if (!sameSourceStats(before, after) || JSON.stringify(childNames) !== JSON.stringify(childNamesAfter)) {
401
561
  throw new Error(`Managed runtime source package directory ${pathRelative} changed while it was inspected.`);
@@ -405,8 +565,8 @@ async function captureSourcePackage(packageRoot) {
405
565
  if (packageJson === undefined) {
406
566
  throw new Error(`Managed runtime source package ${root} has no regular package.json.`);
407
567
  }
408
- entries.sort((left, right) => left.path.localeCompare(right.path));
409
- proof.sort((left, right) => left.path.localeCompare(right.path));
568
+ entries.sort((left, right) => compareCodeUnits(left.path, right.path));
569
+ proof.sort((left, right) => compareCodeUnits(left.path, right.path));
410
570
  return {
411
571
  manifest: { schema: "mono-agent.source-package.v1", entries },
412
572
  proof,
@@ -517,6 +677,97 @@ function sameSourceStats(left, right) {
517
677
  && left.mtimeNs === right.mtimeNs
518
678
  && left.ctimeNs === right.ctimeNs;
519
679
  }
680
+ function sameSourceProofEntry(left, right) {
681
+ return JSON.stringify(left) === JSON.stringify(right);
682
+ }
683
+ async function captureFilesystemPathProof(path, logicalPath) {
684
+ const before = await lstat(path, { bigint: true });
685
+ const type = before.isSymbolicLink()
686
+ ? "symlink"
687
+ : before.isDirectory()
688
+ ? "directory"
689
+ : before.isFile()
690
+ ? "file"
691
+ : undefined;
692
+ if (type === undefined) {
693
+ throw new Error(`Managed runtime package path ${logicalPath} has an unsupported filesystem type.`);
694
+ }
695
+ const target = type === "symlink" ? await readlink(path) : undefined;
696
+ const [after, targetAfter] = await Promise.all([
697
+ lstat(path, { bigint: true }),
698
+ type === "symlink" ? readlink(path) : Promise.resolve(undefined),
699
+ ]);
700
+ if (!sameSourceStats(before, after)
701
+ || (type === "symlink" && (!after.isSymbolicLink() || targetAfter !== target))
702
+ || (type === "directory" && (!after.isDirectory() || after.isSymbolicLink()))
703
+ || (type === "file" && (!after.isFile() || after.isSymbolicLink()))) {
704
+ throw new Error(`Managed runtime package path ${logicalPath} changed while it was inspected.`);
705
+ }
706
+ const portableTarget = target?.split(sep).join("/");
707
+ return sourceProofEntry(logicalPath, type, after, portableTarget);
708
+ }
709
+ async function createResolutionPathProof(filesystemRoot) {
710
+ const lexicalRoot = resolve(filesystemRoot);
711
+ const canonicalRoot = await realpath(lexicalRoot);
712
+ const captured = new Map();
713
+ const captureDirectory = async (path, logicalPath) => {
714
+ const current = await captureFilesystemPathProof(path, logicalPath);
715
+ if (current.type !== "directory") {
716
+ throw new Error(`Managed runtime resolution path ${logicalPath} must be a real directory.`);
717
+ }
718
+ const existing = captured.get(logicalPath);
719
+ if (existing !== undefined && !sameSourceProofEntry(existing.proof, current)) {
720
+ throw new Error(`Managed runtime resolution path ${logicalPath} changed while it was inspected.`);
721
+ }
722
+ if (existing === undefined)
723
+ captured.set(logicalPath, { path, proof: current });
724
+ };
725
+ const captureAncestors = async (path) => {
726
+ const target = resolve(path);
727
+ const root = pathInsideRoot(lexicalRoot, target)
728
+ ? lexicalRoot
729
+ : pathInsideRoot(canonicalRoot, target)
730
+ ? canonicalRoot
731
+ : undefined;
732
+ if (root === undefined) {
733
+ throw new Error("Managed runtime execution closure resolves outside its private install root.");
734
+ }
735
+ await captureDirectory(root, ".");
736
+ const parent = dirname(target);
737
+ const parentRelative = relative(root, parent);
738
+ if (parentRelative === "" || parentRelative === ".")
739
+ return;
740
+ let cursor = root;
741
+ for (const segment of parentRelative.split(sep).filter(Boolean)) {
742
+ cursor = join(cursor, segment);
743
+ await captureDirectory(cursor, portableResolutionPath(root, cursor));
744
+ }
745
+ };
746
+ await captureDirectory(lexicalRoot, ".");
747
+ return {
748
+ captureAncestors,
749
+ finalize: async () => {
750
+ const final = [];
751
+ for (const [logicalPath, initial] of [...captured].sort(([left], [right]) => compareCodeUnits(left, right))) {
752
+ const current = await captureFilesystemPathProof(initial.path, logicalPath);
753
+ if (current.type !== "directory" || !sameSourceProofEntry(initial.proof, current)) {
754
+ throw new Error(`Managed runtime resolution path ${logicalPath} changed while it was inspected.`);
755
+ }
756
+ final.push(current);
757
+ }
758
+ return final;
759
+ },
760
+ };
761
+ }
762
+ function pathInsideRoot(root, path) {
763
+ const pathRelative = relative(root, path);
764
+ return pathRelative === ""
765
+ || (pathRelative !== ".." && !pathRelative.startsWith(`..${sep}`) && !isAbsolute(pathRelative));
766
+ }
767
+ function portableResolutionPath(root, path) {
768
+ const pathRelative = relative(root, path);
769
+ return pathRelative === "" ? "." : pathRelative.split(sep).join("/");
770
+ }
520
771
  function sourcePackageRelativePath(root, path) {
521
772
  const pathRelative = relative(root, path);
522
773
  if (pathRelative === "" || pathRelative === ".")
@@ -571,7 +822,7 @@ function dependencyRequirements(metadata) {
571
822
  if (!requirements.has(name))
572
823
  requirements.set(name, metadata.optionalPeers.has(name));
573
824
  }
574
- return [...requirements].sort(([left], [right]) => left.localeCompare(right));
825
+ return [...requirements].sort(([left], [right]) => compareCodeUnits(left, right));
575
826
  }
576
827
  async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName) {
577
828
  const segments = packageNameSegments(dependencyName);
@@ -579,6 +830,7 @@ async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName
579
830
  for (;;) {
580
831
  const candidate = join(cursor, "node_modules", ...segments);
581
832
  try {
833
+ const proof = await captureFilesystemPathProof(candidate, dependencyName);
582
834
  const canonical = await realpath(candidate);
583
835
  const details = await lstat(canonical);
584
836
  if (!details.isDirectory() || details.isSymbolicLink()) {
@@ -588,7 +840,14 @@ async function resolveInstalledDependencyPackageRoot(packageRoot, dependencyName
588
840
  if (metadata.name !== dependencyName) {
589
841
  throw new Error(`Installed dependency path ${candidate} declares ${metadata.name}, expected ${dependencyName}.`);
590
842
  }
591
- return canonical;
843
+ const [proofAfter, canonicalAfter] = await Promise.all([
844
+ captureFilesystemPathProof(candidate, dependencyName),
845
+ realpath(candidate),
846
+ ]);
847
+ if (canonicalAfter !== canonical || !sameSourceProofEntry(proof, proofAfter)) {
848
+ throw new Error(`Installed dependency ${dependencyName} at ${candidate} changed while it was resolved.`);
849
+ }
850
+ return { candidatePath: candidate, packageRoot: canonical, proof: proofAfter };
592
851
  }
593
852
  catch (error) {
594
853
  if (!isErrno(error, "ENOENT") && !isErrno(error, "ENOTDIR"))
@@ -700,7 +959,7 @@ function runtimeLayoutForRoot(layout, root) {
700
959
  markerPath: join(root, ".mono-agent-runtime.json"),
701
960
  };
702
961
  }
703
- async function acquireRuntimeLock(layout, identity, deps) {
962
+ async function acquireRuntimeLock(layout, identity, additionalPackages, deps) {
704
963
  const deadline = deps.now() + LOCK_WAIT_TIMEOUT_MS;
705
964
  const incarnation = await (deps.currentProcessIncarnation ?? currentProcessIncarnation)();
706
965
  const isSameProcess = deps.isSameProcessIncarnation ?? matchesProcessIncarnation;
@@ -727,7 +986,7 @@ async function acquireRuntimeLock(layout, identity, deps) {
727
986
  if (!isErrno(error, "EEXIST"))
728
987
  throw error;
729
988
  }
730
- if (await verifyRuntime(layout, identity))
989
+ if (await verifyRuntime(layout, identity, additionalPackages))
731
990
  return undefined;
732
991
  let stale = false;
733
992
  try {
@@ -823,7 +1082,7 @@ async function removeSameRuntimeLockDirectory(path, identity) {
823
1082
  }
824
1083
  await rm(released, { recursive: true, force: true });
825
1084
  }
826
- async function quarantineInvalidRuntime(layout, identity, deps) {
1085
+ async function quarantineInvalidRuntime(layout, identity, additionalPackages, deps) {
827
1086
  try {
828
1087
  await lstat(layout.installRoot);
829
1088
  }
@@ -832,7 +1091,7 @@ async function quarantineInvalidRuntime(layout, identity, deps) {
832
1091
  return;
833
1092
  throw error;
834
1093
  }
835
- if (await verifyRuntime(layout, identity))
1094
+ if (await verifyRuntime(layout, identity, additionalPackages))
836
1095
  return;
837
1096
  await mkdir(dirname(layout.quarantineDir), { recursive: true, mode: 0o700 });
838
1097
  await rename(layout.installRoot, layout.quarantineDir);
@@ -841,28 +1100,64 @@ async function quarantineInvalidRuntime(layout, identity, deps) {
841
1100
  // are never silently overwritten, while no plist can point at this location.
842
1101
  void deps;
843
1102
  }
844
- async function promoteStaging(layout, staged, identity) {
1103
+ async function promoteStaging(layout, staged, identity, additionalPackages) {
845
1104
  try {
846
1105
  await rename(staged.installRoot, layout.installRoot);
1106
+ return true;
847
1107
  }
848
1108
  catch (error) {
849
1109
  // A concurrent installer can win after a stale-lock recovery. Its result is
850
1110
  // acceptable only if it independently verifies against the same identity.
851
- if (!(await verifyRuntime(layout, identity)))
1111
+ if (!(await verifyRuntime(layout, identity, additionalPackages)))
852
1112
  throw error;
853
1113
  await rm(staged.installRoot, { recursive: true, force: true });
1114
+ return false;
1115
+ }
1116
+ }
1117
+ async function verifyRuntime(layout, identity, additionalPackages) {
1118
+ const marker = await verifiedRuntimeMarker(layout, identity);
1119
+ if (marker === undefined)
1120
+ return false;
1121
+ try {
1122
+ const packageSource = dirname(dirname(layout.cliPath));
1123
+ const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
1124
+ packageName,
1125
+ packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
1126
+ }));
1127
+ const closure = await captureExecutionClosure(packageSource, runtimeAdditionalPackages, layout.installRoot);
1128
+ return closure.sourceClosureSha256 === identity.sourceClosureSha256
1129
+ && closure.filesystemProofSha256 === marker.executionProofSha256;
1130
+ }
1131
+ catch {
1132
+ return false;
854
1133
  }
855
1134
  }
856
- async function verifyRuntime(layout, identity) {
1135
+ async function verifiedRuntimeMarker(layout, identity) {
857
1136
  try {
858
1137
  const root = await lstat(layout.installRoot);
859
1138
  if (!root.isDirectory() || root.isSymbolicLink() || !ownedPrivately(root))
860
- return false;
1139
+ return undefined;
861
1140
  if (!(await verifyInstalledPackage(layout, identity)))
862
- return false;
1141
+ return undefined;
863
1142
  await assertRegularFile(layout.markerPath, "managed runtime marker");
864
1143
  const marker = JSON.parse(await readFile(layout.markerPath, "utf8"));
865
- return marker.schema === "mono-agent.managed-runtime.v3"
1144
+ const exactKeys = [
1145
+ "schema",
1146
+ "packageName",
1147
+ "closureManifestSha256",
1148
+ "executionProofSha256",
1149
+ "packageVersion",
1150
+ "cliSha256",
1151
+ "sourceClosureSha256",
1152
+ "nodeAbi",
1153
+ "platform",
1154
+ "arch",
1155
+ "installedAt",
1156
+ ];
1157
+ const installedAtMs = typeof marker.installedAt === "string" ? Date.parse(marker.installedAt) : Number.NaN;
1158
+ const valid = Object.keys(marker).length === exactKeys.length
1159
+ && exactKeys.every((key) => Object.hasOwn(marker, key))
1160
+ && marker.schema === "mono-agent.managed-runtime.v4"
866
1161
  && marker.packageName === PACKAGE_NAME
867
1162
  && marker.packageVersion === identity.packageVersion
868
1163
  && marker.cliSha256 === identity.cliSha256
@@ -871,10 +1166,21 @@ async function verifyRuntime(layout, identity) {
871
1166
  && marker.platform === identity.platform
872
1167
  && marker.arch === identity.arch
873
1168
  && typeof marker.closureManifestSha256 === "string"
1169
+ && /^[0-9a-f]{64}$/u.test(marker.closureManifestSha256)
1170
+ && typeof marker.executionProofSha256 === "string"
1171
+ && /^[0-9a-f]{64}$/u.test(marker.executionProofSha256)
1172
+ && typeof marker.installedAt === "string"
1173
+ && Number.isFinite(installedAtMs)
1174
+ && new Date(installedAtMs).toISOString() === marker.installedAt
1175
+ // Promotion writes this sentinel before the final filesystem proof is
1176
+ // rebound. It must remain unverifiable even when the filesystem records
1177
+ // both writes with indistinguishable timestamp precision.
1178
+ && marker.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT
874
1179
  && await verifyClosureManifest(layout, marker.closureManifestSha256);
1180
+ return valid ? marker : undefined;
875
1181
  }
876
1182
  catch {
877
- return false;
1183
+ return undefined;
878
1184
  }
879
1185
  }
880
1186
  async function verifyInstalledPackage(layout, identity) {
@@ -941,7 +1247,7 @@ async function captureRuntimeClosureManifest(layout) {
941
1247
  const excluded = new Set([resolve(layout.closureManifestPath), resolve(layout.markerPath)]);
942
1248
  const visit = async (directory) => {
943
1249
  const children = await readdir(directory, { withFileTypes: true });
944
- children.sort((left, right) => left.name.localeCompare(right.name));
1250
+ children.sort((left, right) => compareCodeUnits(left.name, right.name));
945
1251
  for (const child of children) {
946
1252
  const path = join(directory, child.name);
947
1253
  if (excluded.has(resolve(path)))
@@ -981,7 +1287,7 @@ async function captureRuntimeClosureManifest(layout) {
981
1287
  }
982
1288
  };
983
1289
  await visit(layout.installRoot);
984
- entries.sort((left, right) => left.path.localeCompare(right.path));
1290
+ entries.sort((left, right) => compareCodeUnits(left.path, right.path));
985
1291
  return { schema: "mono-agent.execution-closure.v1", entries };
986
1292
  }
987
1293
  async function fingerprintClosureFile(path, pathRelative) {
@@ -1044,6 +1350,35 @@ async function writePrivateJson(path, value) {
1044
1350
  await writeFile(path, privateJsonContents(value), { encoding: "utf8", mode: 0o600 });
1045
1351
  await chmod(path, 0o600);
1046
1352
  }
1353
+ async function writeFinalizedRuntimeMarker(path, marker, deps) {
1354
+ // Persist the post-proof boundary. If a write crosses into another second,
1355
+ // advance and rewrite so every process from the finalization window remains
1356
+ // in the marker's rejected whole-second interval.
1357
+ for (let attempt = 0; attempt < 5; attempt += 1) {
1358
+ const boundaryMs = deps.now();
1359
+ await writePrivateJson(path, {
1360
+ ...marker,
1361
+ installedAt: new Date(boundaryMs).toISOString(),
1362
+ });
1363
+ if (Math.floor(deps.now() / 1_000) === Math.floor(boundaryMs / 1_000))
1364
+ return;
1365
+ }
1366
+ throw new Error("Could not publish a stable managed runtime launch boundary.");
1367
+ }
1368
+ async function waitForManagedRuntimeLaunchBoundary(layout, identity, deps) {
1369
+ const marker = await verifiedRuntimeMarker(layout, identity);
1370
+ if (marker === undefined) {
1371
+ throw new Error("The managed runtime marker became invalid before launch.");
1372
+ }
1373
+ const boundaryMs = Date.parse(marker.installedAt);
1374
+ const safeStartMs = (Math.floor(boundaryMs / 1_000) + 1) * 1_000;
1375
+ const nowMs = deps.now();
1376
+ if (safeStartMs - nowMs > 5_000) {
1377
+ throw new Error("The managed runtime launch boundary is implausibly far in the future.");
1378
+ }
1379
+ if (nowMs < safeStartMs)
1380
+ await deps.sleep(safeStartMs - nowMs);
1381
+ }
1047
1382
  function privateJsonContents(value) {
1048
1383
  return `${JSON.stringify(value, undefined, 2)}\n`;
1049
1384
  }
@@ -1072,6 +1407,28 @@ async function ensurePrivateRuntimeAncestors(home, versionAbiDir) {
1072
1407
  }
1073
1408
  }
1074
1409
  }
1410
+ async function verifyPrivateRuntimeAncestors(home, versionAbiDir) {
1411
+ const base = join(home, ".mono-agent");
1412
+ const pathRelative = relative(base, versionAbiDir);
1413
+ if (pathRelative === "" || pathRelative === ".." || pathRelative.startsWith(`..${sep}`) || isAbsolute(pathRelative)) {
1414
+ return false;
1415
+ }
1416
+ const paths = [base];
1417
+ for (const segment of pathRelative.split(/[\\/]/u).filter(Boolean)) {
1418
+ paths.push(join(paths.at(-1), segment));
1419
+ }
1420
+ try {
1421
+ for (const path of paths) {
1422
+ const details = await lstat(path);
1423
+ if (!details.isDirectory() || details.isSymbolicLink() || !ownedPrivately(details))
1424
+ return false;
1425
+ }
1426
+ return true;
1427
+ }
1428
+ catch {
1429
+ return false;
1430
+ }
1431
+ }
1075
1432
  function ownedPrivately(details) {
1076
1433
  const owned = typeof process.getuid !== "function" || Number(details.uid) === process.getuid();
1077
1434
  return owned && (Number(details.mode) & 0o077) === 0;
@@ -1106,6 +1463,9 @@ function safeSegment(value) {
1106
1463
  function sha256(value) {
1107
1464
  return createHash("sha256").update(value).digest("hex");
1108
1465
  }
1466
+ function compareCodeUnits(left, right) {
1467
+ return left < right ? -1 : left > right ? 1 : 0;
1468
+ }
1109
1469
  function isRecord(value) {
1110
1470
  return typeof value === "object" && value !== null && !Array.isArray(value);
1111
1471
  }