@mono-agent/agent-app 0.12.0 → 0.13.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.
- package/README.md +33 -7
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +126 -34
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-controller-lifecycle.d.ts.map +1 -1
- package/dist/app-controller-lifecycle.js +2 -6
- package/dist/app-controller-lifecycle.js.map +1 -1
- package/dist/app-controller-maintenance.d.ts.map +1 -1
- package/dist/app-controller-maintenance.js +2 -1
- package/dist/app-controller-maintenance.js.map +1 -1
- package/dist/app-controller-memory-health.d.ts +1 -1
- package/dist/app-controller-memory-health.d.ts.map +1 -1
- package/dist/app-controller-memory-health.js +5 -1
- package/dist/app-controller-memory-health.js.map +1 -1
- package/dist/app-controller-responder.d.ts.map +1 -1
- package/dist/app-controller-responder.js +8 -3
- package/dist/app-controller-responder.js.map +1 -1
- package/dist/app-controller-traceability.d.ts.map +1 -1
- package/dist/app-controller-traceability.js +15 -1
- package/dist/app-controller-traceability.js.map +1 -1
- package/dist/app-controller.d.ts +14 -1
- package/dist/app-controller.d.ts.map +1 -1
- package/dist/app-controller.js +61 -9
- package/dist/app-controller.js.map +1 -1
- package/dist/audit-runs.d.ts.map +1 -1
- package/dist/audit-runs.js +12 -2
- package/dist/audit-runs.js.map +1 -1
- package/dist/background-runtime.d.ts +26 -0
- package/dist/background-runtime.d.ts.map +1 -1
- package/dist/background-runtime.js +874 -46
- package/dist/background-runtime.js.map +1 -1
- package/dist/background.d.ts +10 -2
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +193 -38
- package/dist/background.js.map +1 -1
- package/dist/channel-drivers/live.d.ts +3 -2
- package/dist/channel-drivers/live.d.ts.map +1 -1
- package/dist/channel-drivers/live.js +3 -2
- package/dist/channel-drivers/live.js.map +1 -1
- package/dist/channel-drivers/slack.d.ts.map +1 -1
- package/dist/channel-drivers/slack.js +6 -0
- package/dist/channel-drivers/slack.js.map +1 -1
- package/dist/channel-drivers/telegram.d.ts.map +1 -1
- package/dist/channel-drivers/telegram.js +20 -4
- package/dist/channel-drivers/telegram.js.map +1 -1
- package/dist/channel-runtime-controls.d.ts +23 -0
- package/dist/channel-runtime-controls.d.ts.map +1 -0
- package/dist/channel-runtime-controls.js +63 -0
- package/dist/channel-runtime-controls.js.map +1 -0
- package/dist/channel-status-display.d.ts +15 -0
- package/dist/channel-status-display.d.ts.map +1 -0
- package/dist/channel-status-display.js +48 -0
- package/dist/channel-status-display.js.map +1 -0
- package/dist/cli-args.d.ts +17 -22
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +107 -75
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-background-command.d.ts +1 -0
- package/dist/cli-background-command.d.ts.map +1 -1
- package/dist/cli-background-command.js +72 -16
- package/dist/cli-background-command.js.map +1 -1
- package/dist/cli-commands.d.ts +2 -2
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +35 -53
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli-help.d.ts +19 -1
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +209 -67
- package/dist/cli-help.js.map +1 -1
- package/dist/cli-init-command.d.ts.map +1 -1
- package/dist/cli-init-command.js +4 -18
- package/dist/cli-init-command.js.map +1 -1
- package/dist/cli-install-skill-command.d.ts.map +1 -1
- package/dist/cli-install-skill-command.js +32 -3
- package/dist/cli-install-skill-command.js.map +1 -1
- package/dist/cli-runs-command.d.ts +13 -0
- package/dist/cli-runs-command.d.ts.map +1 -0
- package/dist/cli-runs-command.js +70 -0
- package/dist/cli-runs-command.js.map +1 -0
- package/dist/cli-sandbox-command.d.ts +1 -1
- package/dist/cli-sandbox-command.d.ts.map +1 -1
- package/dist/cli-sandbox-command.js +14 -2
- package/dist/cli-sandbox-command.js.map +1 -1
- package/dist/cli-validate-config-command.d.ts +24 -5
- package/dist/cli-validate-config-command.d.ts.map +1 -1
- package/dist/cli-validate-config-command.js +120 -41
- package/dist/cli-validate-config-command.js.map +1 -1
- package/dist/config-reference.js +1 -1
- package/dist/docs-mcp-pairing.d.ts +35 -0
- package/dist/docs-mcp-pairing.d.ts.map +1 -0
- package/dist/docs-mcp-pairing.js +311 -0
- package/dist/docs-mcp-pairing.js.map +1 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +3 -3
- package/dist/doctor.js.map +1 -1
- package/dist/install-skill.d.ts +2 -0
- package/dist/install-skill.d.ts.map +1 -1
- package/dist/install-skill.js +10 -4
- package/dist/install-skill.js.map +1 -1
- package/dist/interaction-bridge.d.ts +2 -0
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +46 -1
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/launchd.d.ts +2 -0
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +2 -0
- package/dist/launchd.js.map +1 -1
- package/dist/managed-runtime-publication.d.ts +22 -0
- package/dist/managed-runtime-publication.d.ts.map +1 -0
- package/dist/managed-runtime-publication.js +91 -0
- package/dist/managed-runtime-publication.js.map +1 -0
- package/dist/managed-startup.d.ts +8 -0
- package/dist/managed-startup.d.ts.map +1 -0
- package/dist/managed-startup.js +12 -0
- package/dist/managed-startup.js.map +1 -0
- package/dist/memory-retrieval.d.ts.map +1 -1
- package/dist/memory-retrieval.js +31 -18
- package/dist/memory-retrieval.js.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +12 -2
- package/dist/metrics.js.map +1 -1
- package/dist/posted-reply-history.d.ts.map +1 -1
- package/dist/posted-reply-history.js +7 -0
- package/dist/posted-reply-history.js.map +1 -1
- package/dist/request-model-override.d.ts +2 -2
- package/dist/request-model-override.js +8 -4
- package/dist/request-model-override.js.map +1 -1
- package/dist/run-history.d.ts.map +1 -1
- package/dist/run-history.js +35 -22
- package/dist/run-history.js.map +1 -1
- package/dist/runtime-provenance.d.ts.map +1 -1
- package/dist/runtime-provenance.js +10 -5
- package/dist/runtime-provenance.js.map +1 -1
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +2 -1
- package/dist/tui-command.js.map +1 -1
- package/dist/wizard/from-flags.d.ts +0 -1
- package/dist/wizard/from-flags.d.ts.map +1 -1
- package/dist/wizard/from-flags.js +1 -8
- package/dist/wizard/from-flags.js.map +1 -1
- package/dist/wizard/index.d.ts +1 -1
- package/dist/wizard/index.d.ts.map +1 -1
- package/dist/wizard/index.js +1 -1
- package/dist/wizard/index.js.map +1 -1
- package/dist/wizard/presets.d.ts +3 -12
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +3 -15
- package/dist/wizard/presets.js.map +1 -1
- package/package.json +15 -16
- package/schema/mono-agent.config.schema.json +2 -2
- package/skills/mono-agent-composer/SKILL.md +13 -5
- package/skills/mono-agent-composer/agents/openai.yaml +1 -1
- package/skills/mono-agent-composer/references/config-blueprint.md +17 -9
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -5
- package/skills/mono-agent-composer/references/feature-coverage.md +9 -8
- package/skills/mono-agent-composer/references/package-map.md +10 -4
- package/skills/mono-agent-composer/references/playbooks.md +5 -5
- package/skills/mono-agent-composer/references/validation.md +4 -2
|
@@ -14,6 +14,11 @@ const LOCK_STALE_AFTER_MS = 5 * 60_000;
|
|
|
14
14
|
const LOCK_POLL_INTERVAL_MS = 200;
|
|
15
15
|
const PROVISIONAL_RUNTIME_INSTALLED_AT = "1970-01-01T00:00:00.000Z";
|
|
16
16
|
const MAX_RUNTIME_MARKER_BYTES = 16 * 1024;
|
|
17
|
+
const MAX_RUNTIME_REUSE_PROOF_BYTES = 64 * 1024 * 1024;
|
|
18
|
+
const RUNTIME_REUSE_PROOF_SCHEMA = "mono-agent.managed-runtime-reuse-proof.v1";
|
|
19
|
+
const RUNTIME_REUSE_STAT_CONCURRENCY = 64;
|
|
20
|
+
const MANAGED_RUNTIME_LAUNCH_PROOF_SCHEMA = "mono-agent.managed-runtime-launch.v1";
|
|
21
|
+
const MAX_MANAGED_RUNTIME_LAUNCH_PROOF_BYTES = 2 * 1024;
|
|
17
22
|
export function defaultManagedBackgroundRuntimeDeps() {
|
|
18
23
|
return {
|
|
19
24
|
now: () => Date.now(),
|
|
@@ -52,47 +57,77 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
52
57
|
await assertRegularFile(currentCliPath, "current mono-agent CLI");
|
|
53
58
|
await assertRegularFile(resolve(input.nodePath), "managed runtime Node executable");
|
|
54
59
|
const currentCli = await readFile(currentCliPath);
|
|
55
|
-
const
|
|
56
|
-
const sourceDetails = await lstat(
|
|
60
|
+
const packageSourceLexical = resolve(input.packageSource ?? packageRootForCli(currentCliPath));
|
|
61
|
+
const sourceDetails = await lstat(packageSourceLexical);
|
|
57
62
|
if (!sourceDetails.isDirectory() || sourceDetails.isSymbolicLink()) {
|
|
58
|
-
throw new Error(`Managed runtime package source ${
|
|
63
|
+
throw new Error(`Managed runtime package source ${packageSourceLexical} must be a real directory.`);
|
|
59
64
|
}
|
|
65
|
+
const packageSource = await realpath(packageSourceLexical);
|
|
60
66
|
const additionalPackages = await canonicalAdditionalPackages(input.additionalPackages ?? []);
|
|
61
|
-
const sourceClosure = await captureExecutionClosure(packageSource, additionalPackages);
|
|
62
67
|
const packageVersion = input.packageVersion ?? await packageVersionAt(packageSource);
|
|
63
68
|
if (!isExactVersion(packageVersion)) {
|
|
64
69
|
throw new Error(`Cannot install a durable managed runtime for invalid package version ${JSON.stringify(packageVersion)}.`);
|
|
65
70
|
}
|
|
71
|
+
const cliSha256 = sha256(currentCli);
|
|
72
|
+
const nodeAbi = input.nodeAbi ?? requiredNodeAbi();
|
|
73
|
+
const platform = input.platform ?? process.platform;
|
|
74
|
+
const arch = input.arch ?? process.arch;
|
|
75
|
+
const home = resolve(input.homeDir ?? accountHomeDirectory());
|
|
76
|
+
const versionAbiDir = runtimeVersionAbiDir(home, packageVersion, nodeAbi, platform, arch);
|
|
77
|
+
await ensurePrivateRuntimeAncestors(home, versionAbiDir);
|
|
78
|
+
const fastRuntime = await findFastReusableRuntime({
|
|
79
|
+
home,
|
|
80
|
+
versionAbiDir,
|
|
81
|
+
packageVersion,
|
|
82
|
+
cliSha256,
|
|
83
|
+
nodeAbi,
|
|
84
|
+
platform,
|
|
85
|
+
arch,
|
|
86
|
+
packageSource,
|
|
87
|
+
additionalPackages,
|
|
88
|
+
});
|
|
89
|
+
if (fastRuntime !== undefined) {
|
|
90
|
+
await waitForManagedRuntimeLaunchBoundary(fastRuntime.layout, fastRuntime.identity, deps);
|
|
91
|
+
return await runtimeResult(fastRuntime.layout, fastRuntime.identity, input.nodePath, "fast-reuse");
|
|
92
|
+
}
|
|
93
|
+
const sourceClosure = await captureExecutionClosure(packageSource, additionalPackages);
|
|
66
94
|
const identity = {
|
|
67
95
|
packageVersion,
|
|
68
|
-
cliSha256
|
|
96
|
+
cliSha256,
|
|
69
97
|
sourceClosureSha256: sourceClosure.sourceClosureSha256,
|
|
70
|
-
nodeAbi
|
|
71
|
-
platform
|
|
72
|
-
arch
|
|
98
|
+
nodeAbi,
|
|
99
|
+
platform,
|
|
100
|
+
arch,
|
|
73
101
|
};
|
|
74
|
-
const home = resolve(input.homeDir ?? accountHomeDirectory());
|
|
75
102
|
const id = safeSegment(deps.randomId());
|
|
76
103
|
const layout = runtimeLayout(home, identity, id, deps.now());
|
|
77
|
-
await
|
|
78
|
-
|
|
79
|
-
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
80
|
-
return runtimeResult(layout, identity, input.nodePath);
|
|
81
|
-
}
|
|
82
|
-
const acquired = await acquireRuntimeLock(layout, identity, additionalPackages, deps);
|
|
104
|
+
const runtimeWasPresent = await pathExists(layout.installRoot);
|
|
105
|
+
const acquired = await acquireRuntimeLock(layout, identity, additionalPackages, deps, () => verifyRuntimeFast(layout, identity, packageSource, additionalPackages));
|
|
83
106
|
if (!acquired) {
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
if (await verifyRuntimeFast(layout, identity, packageSource, additionalPackages)) {
|
|
108
|
+
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
109
|
+
return await runtimeResult(layout, identity, input.nodePath, "fast-reuse");
|
|
110
|
+
}
|
|
111
|
+
// Preserve compatibility with a finalized v4 runtime when another process
|
|
112
|
+
// released the lock before it could publish the v5 warm proof.
|
|
86
113
|
if (await verifyRuntime(layout, identity, additionalPackages)) {
|
|
87
114
|
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
88
|
-
return runtimeResult(layout, identity, input.nodePath);
|
|
115
|
+
return await runtimeResult(layout, identity, input.nodePath, "full-reuse");
|
|
89
116
|
}
|
|
90
117
|
throw new Error(`Timed out waiting for the managed runtime installation lock at ${layout.lockDir}.`);
|
|
91
118
|
}
|
|
92
119
|
try {
|
|
93
120
|
if (await verifyRuntime(layout, identity, additionalPackages)) {
|
|
121
|
+
const marker = await readRuntimeMarker(layout.markerPath, identity);
|
|
122
|
+
if (marker === undefined)
|
|
123
|
+
throw new Error("The managed runtime marker changed before warm-proof publication.");
|
|
124
|
+
const sourceReuseProof = await currentSourceReuseProof(packageSource, additionalPackages, sourceClosure);
|
|
125
|
+
await publishRuntimeReuseProof(layout, identity, packageSource, additionalPackages, sourceReuseProof, marker, deps);
|
|
126
|
+
if (!(await verifyRuntimeFast(layout, identity, packageSource, additionalPackages))) {
|
|
127
|
+
throw new Error("The managed runtime warm-reuse proof failed verification after publication.");
|
|
128
|
+
}
|
|
94
129
|
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
95
|
-
return runtimeResult(layout, identity, input.nodePath);
|
|
130
|
+
return await runtimeResult(layout, identity, input.nodePath, "full-reuse");
|
|
96
131
|
}
|
|
97
132
|
await assertSourceClosureUnchanged(packageSource, additionalPackages, sourceClosure, "before staging");
|
|
98
133
|
await rm(layout.stagingDir, { recursive: true, force: true });
|
|
@@ -158,10 +193,11 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
158
193
|
if (promotedClosure.sourceClosureSha256 !== identity.sourceClosureSha256) {
|
|
159
194
|
throw new Error("The promoted managed runtime execution closure does not match its source identity.");
|
|
160
195
|
}
|
|
161
|
-
await
|
|
196
|
+
const sourceReuseProof = await currentSourceReuseProof(packageSource, additionalPackages, sourceClosure);
|
|
197
|
+
await publishRuntimeReuseProof(layout, identity, packageSource, additionalPackages, sourceReuseProof, {
|
|
162
198
|
...marker,
|
|
163
199
|
executionProofSha256: promotedClosure.filesystemProofSha256,
|
|
164
|
-
}, deps);
|
|
200
|
+
}, deps, true);
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
203
|
catch (error) {
|
|
@@ -171,13 +207,139 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
171
207
|
if (!(await verifyRuntime(layout, identity, additionalPackages))) {
|
|
172
208
|
throw new Error("The managed runtime failed verification after atomic promotion.");
|
|
173
209
|
}
|
|
210
|
+
if (!(await verifyRuntimeFast(layout, identity, packageSource, additionalPackages))) {
|
|
211
|
+
const marker = await readRuntimeMarker(layout.markerPath, identity);
|
|
212
|
+
if (marker === undefined)
|
|
213
|
+
throw new Error("The managed runtime marker changed before warm-proof publication.");
|
|
214
|
+
const sourceReuseProof = await currentSourceReuseProof(packageSource, additionalPackages, sourceClosure);
|
|
215
|
+
await publishRuntimeReuseProof(layout, identity, packageSource, additionalPackages, sourceReuseProof, marker, deps);
|
|
216
|
+
}
|
|
217
|
+
if (!(await verifyRuntimeFast(layout, identity, packageSource, additionalPackages))) {
|
|
218
|
+
throw new Error("The managed runtime warm-reuse proof failed verification after atomic promotion.");
|
|
219
|
+
}
|
|
174
220
|
await waitForManagedRuntimeLaunchBoundary(layout, identity, deps);
|
|
175
|
-
return runtimeResult(layout, identity, input.nodePath);
|
|
221
|
+
return await runtimeResult(layout, identity, input.nodePath, runtimeWasPresent ? "repaired" : "installed");
|
|
176
222
|
}
|
|
177
223
|
finally {
|
|
178
224
|
await acquired.release().catch(() => undefined);
|
|
179
225
|
}
|
|
180
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Validate the opaque proof carried by a managed LaunchAgent against the exact
|
|
229
|
+
* CLI that is about to start. This is intentionally lighter than controller
|
|
230
|
+
* attestation: it rechecks the private layout, marker, CLI, and stored closure
|
|
231
|
+
* manifest fingerprints without traversing the entire installed dependency
|
|
232
|
+
* tree on every KeepAlive restart.
|
|
233
|
+
*/
|
|
234
|
+
export async function verifyManagedRuntimeLaunch(input) {
|
|
235
|
+
const proof = decodeManagedRuntimeLaunchProof(input.launchProof);
|
|
236
|
+
const currentCliPath = resolve(input.currentCliPath);
|
|
237
|
+
const packageRoot = packageRootForCli(currentCliPath);
|
|
238
|
+
const installRoot = dirname(dirname(dirname(packageRoot)));
|
|
239
|
+
const home = resolve(input.homeDir ?? accountHomeDirectory());
|
|
240
|
+
const initialInstallRoot = await lstat(installRoot, { bigint: true });
|
|
241
|
+
if (!privateRuntimeDirectory(initialInstallRoot)) {
|
|
242
|
+
throw new Error("The managed runtime closure root is not a real owner-private directory.");
|
|
243
|
+
}
|
|
244
|
+
const markerPath = join(installRoot, ".mono-agent-runtime.json");
|
|
245
|
+
const initialBytes = await readPrivateRuntimeMarkerBytes(markerPath);
|
|
246
|
+
if (initialBytes === undefined)
|
|
247
|
+
throw new Error("The managed runtime marker is unavailable or unsafe.");
|
|
248
|
+
const markerValue = parseJson(initialBytes, "managed runtime marker");
|
|
249
|
+
const identity = runtimeIdentityFromMarkerJson(markerValue);
|
|
250
|
+
if (identity === undefined)
|
|
251
|
+
throw new Error("The managed runtime marker has an invalid identity.");
|
|
252
|
+
const marker = runtimeMarkerFromJson(markerValue, identity);
|
|
253
|
+
if (marker === undefined)
|
|
254
|
+
throw new Error("The managed runtime marker is provisional or malformed.");
|
|
255
|
+
const layout = runtimeLayout(home, identity, "launch", 0);
|
|
256
|
+
if (layout.installRoot !== installRoot || layout.cliPath !== currentCliPath) {
|
|
257
|
+
throw new Error("The managed worker CLI is outside its canonical runtime closure.");
|
|
258
|
+
}
|
|
259
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir))) {
|
|
260
|
+
throw new Error("The managed runtime has an unsafe private ancestry.");
|
|
261
|
+
}
|
|
262
|
+
if (sha256(initialBytes) !== proof.markerSha256 || marker.installedAt !== proof.installedAt) {
|
|
263
|
+
throw new Error("The managed runtime marker does not match its LaunchAgent proof.");
|
|
264
|
+
}
|
|
265
|
+
if (!(await verifyInstalledPackage(layout, identity))) {
|
|
266
|
+
throw new Error("The managed runtime CLI or package identity failed verification.");
|
|
267
|
+
}
|
|
268
|
+
if (!(await verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256))) {
|
|
269
|
+
throw new Error("The managed runtime CLI fingerprint is unstable.");
|
|
270
|
+
}
|
|
271
|
+
if (!(await verifyClosureManifestFingerprint(layout, marker.closureManifestSha256))) {
|
|
272
|
+
throw new Error("The managed runtime closure manifest fingerprint is invalid.");
|
|
273
|
+
}
|
|
274
|
+
await waitForLaunchBoundary(marker.installedAt, input.now ?? Date.now, input.sleep ?? sleep);
|
|
275
|
+
const finalBytes = await readPrivateRuntimeMarkerBytes(markerPath);
|
|
276
|
+
if (finalBytes === undefined || sha256(finalBytes) !== proof.markerSha256 || !finalBytes.equals(initialBytes)) {
|
|
277
|
+
throw new Error("The managed runtime marker changed during launch verification.");
|
|
278
|
+
}
|
|
279
|
+
if (!(await verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256))) {
|
|
280
|
+
throw new Error("The managed runtime CLI changed during launch verification.");
|
|
281
|
+
}
|
|
282
|
+
if (!(await verifyClosureManifestFingerprint(layout, marker.closureManifestSha256))) {
|
|
283
|
+
throw new Error("The managed runtime closure manifest changed during launch verification.");
|
|
284
|
+
}
|
|
285
|
+
const finalInstallRoot = await lstat(installRoot, { bigint: true });
|
|
286
|
+
if (!privateRuntimeDirectory(finalInstallRoot) || !sameSourceStats(initialInstallRoot, finalInstallRoot)) {
|
|
287
|
+
throw new Error("The managed runtime closure root changed during launch verification.");
|
|
288
|
+
}
|
|
289
|
+
const closureId = `${identity.cliSha256}-${identity.sourceClosureSha256}`;
|
|
290
|
+
return {
|
|
291
|
+
installRoot,
|
|
292
|
+
provenanceDetail: `Runtime provenance: managed closure ${closureId} (`
|
|
293
|
+
+ `${PACKAGE_NAME} ${identity.packageVersion}; ${identity.platform}-${identity.arch}; `
|
|
294
|
+
+ `Node ABI ${identity.nodeAbi}; installed ${marker.installedAt}).`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function encodeManagedRuntimeLaunchProof(proof) {
|
|
298
|
+
return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
|
|
299
|
+
}
|
|
300
|
+
function decodeManagedRuntimeLaunchProof(encoded) {
|
|
301
|
+
if (typeof encoded !== "string"
|
|
302
|
+
|| encoded.length === 0
|
|
303
|
+
|| encoded.length > MAX_MANAGED_RUNTIME_LAUNCH_PROOF_BYTES * 2
|
|
304
|
+
|| !/^[0-9A-Za-z_-]+$/u.test(encoded)) {
|
|
305
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
306
|
+
}
|
|
307
|
+
let bytes;
|
|
308
|
+
try {
|
|
309
|
+
bytes = Buffer.from(encoded, "base64url");
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
313
|
+
}
|
|
314
|
+
if (bytes.length === 0
|
|
315
|
+
|| bytes.length > MAX_MANAGED_RUNTIME_LAUNCH_PROOF_BYTES
|
|
316
|
+
|| bytes.toString("base64url") !== encoded) {
|
|
317
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
318
|
+
}
|
|
319
|
+
const value = parseJson(bytes, "managed runtime launch proof");
|
|
320
|
+
if (!isRecord(value) || Object.keys(value).length !== 3) {
|
|
321
|
+
throw new Error("The managed runtime launch proof has an invalid schema.");
|
|
322
|
+
}
|
|
323
|
+
const installedAtMs = typeof value.installedAt === "string" ? Date.parse(value.installedAt) : Number.NaN;
|
|
324
|
+
if (value.schema !== MANAGED_RUNTIME_LAUNCH_PROOF_SCHEMA
|
|
325
|
+
|| typeof value.markerSha256 !== "string"
|
|
326
|
+
|| !/^[0-9a-f]{64}$/u.test(value.markerSha256)
|
|
327
|
+
|| typeof value.installedAt !== "string"
|
|
328
|
+
|| !Number.isFinite(installedAtMs)
|
|
329
|
+
|| new Date(installedAtMs).toISOString() !== value.installedAt
|
|
330
|
+
|| value.installedAt === PROVISIONAL_RUNTIME_INSTALLED_AT) {
|
|
331
|
+
throw new Error("The managed runtime launch proof has an invalid schema.");
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
function parseJson(bytes, label) {
|
|
336
|
+
try {
|
|
337
|
+
return JSON.parse(bytes.toString("utf8"));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
throw new Error(`The ${label} is not valid JSON.`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
181
343
|
/**
|
|
182
344
|
* Canonically verify the installed package, freshly recomputed execution
|
|
183
345
|
* closure/filesystem proof, and coherent current manifest behind a provenance
|
|
@@ -212,16 +374,8 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
212
374
|
return false;
|
|
213
375
|
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir)))
|
|
214
376
|
return false;
|
|
215
|
-
const expectedMarker = {
|
|
216
|
-
schema: "mono-agent.managed-runtime.v4",
|
|
217
|
-
packageName: PACKAGE_NAME,
|
|
218
|
-
closureManifestSha256: input.closureManifestSha256,
|
|
219
|
-
executionProofSha256: input.executionProofSha256,
|
|
220
|
-
...identity,
|
|
221
|
-
installedAt: input.installedAt,
|
|
222
|
-
};
|
|
223
377
|
const markerInitial = await verifiedRuntimeMarker(layout, identity);
|
|
224
|
-
if (markerInitial === undefined || !
|
|
378
|
+
if (markerInitial === undefined || !runtimeMarkerMatchesProvenance(markerInitial, input))
|
|
225
379
|
return false;
|
|
226
380
|
const additionalPackages = await managedRuntimeAdditionalPackages(layout);
|
|
227
381
|
if (additionalPackages === undefined)
|
|
@@ -242,7 +396,7 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
242
396
|
const markerFinal = await readRuntimeMarker(layout.markerPath, identity);
|
|
243
397
|
if (markerFinal === undefined
|
|
244
398
|
|| !sameRuntimeMarker(markerFinal, markerInitial)
|
|
245
|
-
|| !
|
|
399
|
+
|| !runtimeMarkerMatchesProvenance(markerFinal, input)
|
|
246
400
|
|| !(await verifyClosureManifest(layout, markerFinal.closureManifestSha256, deps.beforeFinalManifestProof === undefined
|
|
247
401
|
? {}
|
|
248
402
|
: { beforeFinalProof: deps.beforeFinalManifestProof }))) {
|
|
@@ -251,7 +405,7 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
251
405
|
const markerAfterManifest = await readRuntimeMarker(layout.markerPath, identity);
|
|
252
406
|
return markerAfterManifest !== undefined
|
|
253
407
|
&& sameRuntimeMarker(markerAfterManifest, markerInitial)
|
|
254
|
-
&&
|
|
408
|
+
&& runtimeMarkerMatchesProvenance(markerAfterManifest, input);
|
|
255
409
|
}
|
|
256
410
|
catch {
|
|
257
411
|
return false;
|
|
@@ -569,6 +723,15 @@ async function captureExecutionClosure(packageSource, additionalPackages, filesy
|
|
|
569
723
|
sourceClosureSha256: sha256(Buffer.from(JSON.stringify(stable), "utf8")),
|
|
570
724
|
sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
|
|
571
725
|
filesystemProofSha256: sha256(Buffer.from(JSON.stringify(filesystemProof), "utf8")),
|
|
726
|
+
reuseProof: {
|
|
727
|
+
sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
|
|
728
|
+
packages: closure.map((node) => ({
|
|
729
|
+
id: node.id,
|
|
730
|
+
sourceRoot: node.sourceRoot,
|
|
731
|
+
entries: requiredMapValue(proofs, node.id, "source package proof"),
|
|
732
|
+
})),
|
|
733
|
+
links: [...dependencyProofPaths, ...additionalProofPaths],
|
|
734
|
+
},
|
|
572
735
|
};
|
|
573
736
|
}
|
|
574
737
|
/**
|
|
@@ -626,6 +789,15 @@ async function assertSourceClosureUnchanged(packageSource, additionalPackages, e
|
|
|
626
789
|
throw new Error(`The managed runtime package closure changed ${phase}; no staged runtime was promoted.`);
|
|
627
790
|
}
|
|
628
791
|
}
|
|
792
|
+
async function currentSourceReuseProof(packageSource, additionalPackages, expected) {
|
|
793
|
+
if (await verifySourceReuseProofStats(expected.reuseProof))
|
|
794
|
+
return expected.reuseProof;
|
|
795
|
+
const current = await captureExecutionClosure(packageSource, additionalPackages);
|
|
796
|
+
if (current.sourceClosureSha256 !== expected.sourceClosureSha256) {
|
|
797
|
+
throw new Error("The managed runtime source closure changed before warm-proof publication.");
|
|
798
|
+
}
|
|
799
|
+
return current.reuseProof;
|
|
800
|
+
}
|
|
629
801
|
/**
|
|
630
802
|
* Capture exactly the package-owned files copied by `materializeExactExecutionClosure`.
|
|
631
803
|
* `node_modules`, VCS metadata, and dotenv files are deliberately excluded.
|
|
@@ -1054,8 +1226,7 @@ async function packageVersionAt(packageRoot) {
|
|
|
1054
1226
|
return record.version;
|
|
1055
1227
|
}
|
|
1056
1228
|
function runtimeLayout(home, identity, id, nowMs) {
|
|
1057
|
-
const
|
|
1058
|
-
const versionAbiDir = join(home, ".mono-agent", "runtimes", "agent-app", identity.packageVersion, platformAbi);
|
|
1229
|
+
const versionAbiDir = runtimeVersionAbiDir(home, identity.packageVersion, identity.nodeAbi, identity.platform, identity.arch);
|
|
1059
1230
|
const closureId = `${identity.cliSha256}-${identity.sourceClosureSha256}`;
|
|
1060
1231
|
const installRoot = join(versionAbiDir, closureId);
|
|
1061
1232
|
const suffix = `${Math.max(0, Math.floor(nowMs))}-${id}`;
|
|
@@ -1067,11 +1238,17 @@ function runtimeLayout(home, identity, id, nowMs) {
|
|
|
1067
1238
|
packageLockPath: join(installRoot, "package-lock.json"),
|
|
1068
1239
|
closureManifestPath: join(installRoot, ".mono-agent-closure.json"),
|
|
1069
1240
|
markerPath: join(installRoot, ".mono-agent-runtime.json"),
|
|
1241
|
+
reuseProofDir: join(versionAbiDir, ".reuse-proofs"),
|
|
1242
|
+
reuseProofPath: join(versionAbiDir, ".reuse-proofs", `${closureId}.json`),
|
|
1070
1243
|
lockDir: join(versionAbiDir, `.${closureId}.lock`),
|
|
1071
1244
|
stagingDir: join(versionAbiDir, `.${closureId}.staging-${suffix}`),
|
|
1072
1245
|
quarantineDir: join(versionAbiDir, "quarantine", `${closureId}-${suffix}`),
|
|
1073
1246
|
};
|
|
1074
1247
|
}
|
|
1248
|
+
function runtimeVersionAbiDir(home, packageVersion, nodeAbi, platform, arch) {
|
|
1249
|
+
const platformAbi = `${safeSegment(platform)}-${safeSegment(arch)}-abi-${safeSegment(nodeAbi)}`;
|
|
1250
|
+
return join(home, ".mono-agent", "runtimes", "agent-app", packageVersion, platformAbi);
|
|
1251
|
+
}
|
|
1075
1252
|
function runtimeLayoutForRoot(layout, root) {
|
|
1076
1253
|
return {
|
|
1077
1254
|
...layout,
|
|
@@ -1083,7 +1260,552 @@ function runtimeLayoutForRoot(layout, root) {
|
|
|
1083
1260
|
markerPath: join(root, ".mono-agent-runtime.json"),
|
|
1084
1261
|
};
|
|
1085
1262
|
}
|
|
1086
|
-
async function
|
|
1263
|
+
async function findFastReusableRuntime(input) {
|
|
1264
|
+
let entries;
|
|
1265
|
+
try {
|
|
1266
|
+
entries = await readdir(input.versionAbiDir, { withFileTypes: true });
|
|
1267
|
+
}
|
|
1268
|
+
catch {
|
|
1269
|
+
return undefined;
|
|
1270
|
+
}
|
|
1271
|
+
const prefix = `${input.cliSha256}-`;
|
|
1272
|
+
for (const entry of entries.sort((left, right) => compareCodeUnits(left.name, right.name))) {
|
|
1273
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
1274
|
+
continue;
|
|
1275
|
+
const sourceClosureSha256 = entry.name.slice(prefix.length);
|
|
1276
|
+
if (!/^[0-9a-f]{64}$/u.test(sourceClosureSha256))
|
|
1277
|
+
continue;
|
|
1278
|
+
const identity = {
|
|
1279
|
+
packageVersion: input.packageVersion,
|
|
1280
|
+
cliSha256: input.cliSha256,
|
|
1281
|
+
sourceClosureSha256,
|
|
1282
|
+
nodeAbi: input.nodeAbi,
|
|
1283
|
+
platform: input.platform,
|
|
1284
|
+
arch: input.arch,
|
|
1285
|
+
};
|
|
1286
|
+
const layout = runtimeLayout(input.home, identity, "fast-reuse", 0);
|
|
1287
|
+
if (await verifyRuntimeFast(layout, identity, input.packageSource, input.additionalPackages)) {
|
|
1288
|
+
return { layout, identity };
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return undefined;
|
|
1292
|
+
}
|
|
1293
|
+
async function verifyRuntimeFast(layout, identity, packageSource, additionalPackages) {
|
|
1294
|
+
try {
|
|
1295
|
+
const home = dirname(dirname(dirname(dirname(dirname(layout.versionAbiDir)))));
|
|
1296
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir)))
|
|
1297
|
+
return false;
|
|
1298
|
+
const [initialRoot, initialProofDir] = await Promise.all([
|
|
1299
|
+
lstat(layout.installRoot, { bigint: true }),
|
|
1300
|
+
lstat(layout.reuseProofDir, { bigint: true }),
|
|
1301
|
+
]);
|
|
1302
|
+
if (!privateRuntimeDirectory(initialRoot) || !privateRuntimeDirectory(initialProofDir))
|
|
1303
|
+
return false;
|
|
1304
|
+
const [markerBytes, proofBytes] = await Promise.all([
|
|
1305
|
+
readPrivateRuntimeMarkerBytes(layout.markerPath),
|
|
1306
|
+
readPrivateRuntimeReuseProofBytes(layout.reuseProofPath),
|
|
1307
|
+
]);
|
|
1308
|
+
if (markerBytes === undefined || proofBytes === undefined)
|
|
1309
|
+
return false;
|
|
1310
|
+
const marker = runtimeMarkerFromJson(parseJson(markerBytes, "managed runtime marker"), identity);
|
|
1311
|
+
if (marker?.schema !== "mono-agent.managed-runtime.v5"
|
|
1312
|
+
|| marker.reuseProofSha256 !== sha256(proofBytes)) {
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
const proof = runtimeReuseProofFromJson(parseJson(proofBytes, "managed runtime reuse proof"), identity, packageSource, additionalPackages, marker);
|
|
1316
|
+
if (proof === undefined)
|
|
1317
|
+
return false;
|
|
1318
|
+
const [installed, cliStable, manifestStable] = await Promise.all([
|
|
1319
|
+
verifyInstalledPackage(layout, identity),
|
|
1320
|
+
verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256),
|
|
1321
|
+
verifyClosureManifestFingerprint(layout, marker.closureManifestSha256),
|
|
1322
|
+
]);
|
|
1323
|
+
if (!installed
|
|
1324
|
+
|| !cliStable
|
|
1325
|
+
|| !manifestStable
|
|
1326
|
+
|| !(await runtimeReuseProofMatchesManifest(layout, proof.runtime))) {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
if (!(await verifyReuseProofStats(layout, proof)))
|
|
1330
|
+
return false;
|
|
1331
|
+
if (!(await verifyReuseProofStats(layout, proof)))
|
|
1332
|
+
return false;
|
|
1333
|
+
const [markerBytesAfter, proofBytesAfter, finalRoot, finalProofDir] = await Promise.all([
|
|
1334
|
+
readPrivateRuntimeMarkerBytes(layout.markerPath),
|
|
1335
|
+
readPrivateRuntimeReuseProofBytes(layout.reuseProofPath),
|
|
1336
|
+
lstat(layout.installRoot, { bigint: true }),
|
|
1337
|
+
lstat(layout.reuseProofDir, { bigint: true }),
|
|
1338
|
+
]);
|
|
1339
|
+
return markerBytesAfter !== undefined
|
|
1340
|
+
&& proofBytesAfter !== undefined
|
|
1341
|
+
&& markerBytesAfter.equals(markerBytes)
|
|
1342
|
+
&& proofBytesAfter.equals(proofBytes)
|
|
1343
|
+
&& privateRuntimeDirectory(finalRoot)
|
|
1344
|
+
&& privateRuntimeDirectory(finalProofDir)
|
|
1345
|
+
&& sameSourceStats(initialRoot, finalRoot)
|
|
1346
|
+
&& sameSourceStats(initialProofDir, finalProofDir);
|
|
1347
|
+
}
|
|
1348
|
+
catch {
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
async function publishRuntimeReuseProof(layout, identity, packageSource, additionalPackages, source, marker, deps, finalizeLaunchBoundary = false) {
|
|
1353
|
+
if (!(await verifySourceReuseProofStats(source))) {
|
|
1354
|
+
throw new Error("The managed runtime source closure changed before warm-proof publication.");
|
|
1355
|
+
}
|
|
1356
|
+
const runtime = await captureRuntimeClosureReuseProof(layout, marker);
|
|
1357
|
+
const proof = {
|
|
1358
|
+
schema: RUNTIME_REUSE_PROOF_SCHEMA,
|
|
1359
|
+
identity,
|
|
1360
|
+
packageSource,
|
|
1361
|
+
additionalPackages,
|
|
1362
|
+
source,
|
|
1363
|
+
runtime,
|
|
1364
|
+
};
|
|
1365
|
+
const contents = privateJsonContents(proof);
|
|
1366
|
+
const bytes = Buffer.from(contents, "utf8");
|
|
1367
|
+
if (bytes.length > MAX_RUNTIME_REUSE_PROOF_BYTES) {
|
|
1368
|
+
throw new Error("The managed runtime warm-reuse proof exceeds its safe size limit.");
|
|
1369
|
+
}
|
|
1370
|
+
await ensurePrivateReuseProofDirectory(layout.reuseProofDir);
|
|
1371
|
+
await writeFile(layout.reuseProofPath, contents, { encoding: "utf8", mode: 0o600 });
|
|
1372
|
+
await chmod(layout.reuseProofPath, 0o600);
|
|
1373
|
+
const stored = await readPrivateRuntimeReuseProofBytes(layout.reuseProofPath);
|
|
1374
|
+
if (stored === undefined || !stored.equals(bytes)) {
|
|
1375
|
+
throw new Error("The managed runtime warm-reuse proof changed while it was published.");
|
|
1376
|
+
}
|
|
1377
|
+
const nextMarker = {
|
|
1378
|
+
schema: "mono-agent.managed-runtime.v5",
|
|
1379
|
+
packageName: PACKAGE_NAME,
|
|
1380
|
+
packageVersion: identity.packageVersion,
|
|
1381
|
+
cliSha256: identity.cliSha256,
|
|
1382
|
+
sourceClosureSha256: identity.sourceClosureSha256,
|
|
1383
|
+
nodeAbi: identity.nodeAbi,
|
|
1384
|
+
platform: identity.platform,
|
|
1385
|
+
arch: identity.arch,
|
|
1386
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1387
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1388
|
+
reuseProofSha256: sha256(bytes),
|
|
1389
|
+
installedAt: marker.installedAt,
|
|
1390
|
+
};
|
|
1391
|
+
if (finalizeLaunchBoundary) {
|
|
1392
|
+
await writeFinalizedRuntimeMarker(layout.markerPath, nextMarker, deps);
|
|
1393
|
+
}
|
|
1394
|
+
else {
|
|
1395
|
+
await writePrivateJson(layout.markerPath, nextMarker);
|
|
1396
|
+
}
|
|
1397
|
+
if (!(await verifySourceReuseProofStats(source))) {
|
|
1398
|
+
throw new Error("The managed runtime source closure changed during warm-proof publication.");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async function captureRuntimeClosureReuseProof(layout, marker) {
|
|
1402
|
+
const stored = await fingerprintClosureFile(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
1403
|
+
if (stored.sha256 !== marker.closureManifestSha256) {
|
|
1404
|
+
throw new Error("The managed runtime closure manifest changed before warm-proof capture.");
|
|
1405
|
+
}
|
|
1406
|
+
const manifest = runtimeClosureManifestFromJson(parseJson(stored.bytes, "managed runtime closure manifest"));
|
|
1407
|
+
if (manifest === undefined)
|
|
1408
|
+
throw new Error("The managed runtime closure manifest is malformed.");
|
|
1409
|
+
const excludedRootEntries = new Set([".mono-agent-closure.json", ".mono-agent-runtime.json"]);
|
|
1410
|
+
const rootEntries = (await readdir(layout.installRoot))
|
|
1411
|
+
.filter((name) => !excludedRootEntries.has(name))
|
|
1412
|
+
.sort(compareCodeUnits);
|
|
1413
|
+
const entries = await mapWithConcurrency(manifest.entries, RUNTIME_REUSE_STAT_CONCURRENCY, async (entry) => ({
|
|
1414
|
+
path: entry.path,
|
|
1415
|
+
proof: await captureFilesystemPathProof(runtimePathFromPortableRelative(layout.installRoot, entry.path), entry.path),
|
|
1416
|
+
}));
|
|
1417
|
+
const result = {
|
|
1418
|
+
rootEntries,
|
|
1419
|
+
entries,
|
|
1420
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1421
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1422
|
+
};
|
|
1423
|
+
if (!(await verifyRuntimeReuseProofStats(layout, result))) {
|
|
1424
|
+
throw new Error("The managed runtime closure changed during warm-proof capture.");
|
|
1425
|
+
}
|
|
1426
|
+
return result;
|
|
1427
|
+
}
|
|
1428
|
+
async function verifyReuseProofStats(layout, proof) {
|
|
1429
|
+
const [sourceValid, runtimeValid] = await Promise.all([
|
|
1430
|
+
verifySourceReuseProofStats(proof.source),
|
|
1431
|
+
verifyRuntimeReuseProofStats(layout, proof.runtime),
|
|
1432
|
+
]);
|
|
1433
|
+
return sourceValid && runtimeValid;
|
|
1434
|
+
}
|
|
1435
|
+
async function verifySourceReuseProofStats(proof) {
|
|
1436
|
+
try {
|
|
1437
|
+
const targets = [
|
|
1438
|
+
...proof.packages.flatMap((pkg) => pkg.entries.map((entry) => ({
|
|
1439
|
+
path: entry.path === "."
|
|
1440
|
+
? pkg.sourceRoot
|
|
1441
|
+
: runtimePathFromPortableRelative(pkg.sourceRoot, entry.path),
|
|
1442
|
+
logicalPath: entry.path,
|
|
1443
|
+
proof: entry,
|
|
1444
|
+
}))),
|
|
1445
|
+
...proof.links,
|
|
1446
|
+
];
|
|
1447
|
+
const matches = await mapWithConcurrency(targets, RUNTIME_REUSE_STAT_CONCURRENCY, async (target) => {
|
|
1448
|
+
const current = await captureFilesystemPathProof(target.path, target.logicalPath);
|
|
1449
|
+
return sameSourceProofEntry(target.proof, current);
|
|
1450
|
+
});
|
|
1451
|
+
return matches.every(Boolean);
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
async function verifyRuntimeReuseProofStats(layout, proof) {
|
|
1458
|
+
try {
|
|
1459
|
+
const excludedRootEntries = new Set([".mono-agent-closure.json", ".mono-agent-runtime.json"]);
|
|
1460
|
+
const rootEntries = (await readdir(layout.installRoot))
|
|
1461
|
+
.filter((name) => !excludedRootEntries.has(name))
|
|
1462
|
+
.sort(compareCodeUnits);
|
|
1463
|
+
if (JSON.stringify(rootEntries) !== JSON.stringify(proof.rootEntries))
|
|
1464
|
+
return false;
|
|
1465
|
+
const matches = await mapWithConcurrency(proof.entries, RUNTIME_REUSE_STAT_CONCURRENCY, async (entry) => {
|
|
1466
|
+
const current = await captureFilesystemPathProof(runtimePathFromPortableRelative(layout.installRoot, entry.path), entry.path);
|
|
1467
|
+
return sameSourceProofEntry(entry.proof, current);
|
|
1468
|
+
});
|
|
1469
|
+
return matches.every(Boolean);
|
|
1470
|
+
}
|
|
1471
|
+
catch {
|
|
1472
|
+
return false;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
async function runtimeReuseProofMatchesManifest(layout, proof) {
|
|
1476
|
+
try {
|
|
1477
|
+
const bytes = await readFile(layout.closureManifestPath);
|
|
1478
|
+
if (sha256(bytes) !== proof.closureManifestSha256)
|
|
1479
|
+
return false;
|
|
1480
|
+
const manifest = runtimeClosureManifestFromJson(parseJson(bytes, "managed runtime closure manifest"));
|
|
1481
|
+
if (manifest === undefined || manifest.entries.length !== proof.entries.length)
|
|
1482
|
+
return false;
|
|
1483
|
+
return manifest.entries.every((entry, index) => {
|
|
1484
|
+
const proven = proof.entries[index];
|
|
1485
|
+
return proven !== undefined
|
|
1486
|
+
&& proven.path === entry.path
|
|
1487
|
+
&& proven.proof.type === entry.type
|
|
1488
|
+
&& fileMode(BigInt(proven.proof.mode)) === entry.mode
|
|
1489
|
+
&& (entry.type !== "symlink" || proven.proof.target === entry.target);
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
catch {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async function ensurePrivateReuseProofDirectory(path) {
|
|
1497
|
+
await mkdir(path, { recursive: false, mode: 0o700 }).catch((error) => {
|
|
1498
|
+
if (!isErrno(error, "EEXIST"))
|
|
1499
|
+
throw error;
|
|
1500
|
+
});
|
|
1501
|
+
const details = await lstat(path, { bigint: true });
|
|
1502
|
+
if (!privateRuntimeDirectory(details)) {
|
|
1503
|
+
throw new Error(`Managed runtime reuse-proof directory ${path} must be owner-private.`);
|
|
1504
|
+
}
|
|
1505
|
+
await chmod(path, 0o700);
|
|
1506
|
+
}
|
|
1507
|
+
async function readPrivateRuntimeReuseProofBytes(path) {
|
|
1508
|
+
let handle;
|
|
1509
|
+
try {
|
|
1510
|
+
handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
1511
|
+
const before = await handle.stat({ bigint: true });
|
|
1512
|
+
if (!privateRuntimeReuseProofFile(before))
|
|
1513
|
+
return undefined;
|
|
1514
|
+
const bytes = await handle.readFile();
|
|
1515
|
+
const [after, named] = await Promise.all([
|
|
1516
|
+
handle.stat({ bigint: true }),
|
|
1517
|
+
lstat(path, { bigint: true }),
|
|
1518
|
+
]);
|
|
1519
|
+
if (!privateRuntimeReuseProofFile(after)
|
|
1520
|
+
|| !privateRuntimeReuseProofFile(named)
|
|
1521
|
+
|| !sameSourceStats(before, after)
|
|
1522
|
+
|| !sameSourceStats(before, named)) {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
return bytes;
|
|
1526
|
+
}
|
|
1527
|
+
catch {
|
|
1528
|
+
return undefined;
|
|
1529
|
+
}
|
|
1530
|
+
finally {
|
|
1531
|
+
await handle?.close();
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function privateRuntimeReuseProofFile(details) {
|
|
1535
|
+
const owned = typeof process.getuid !== "function" || details.uid === BigInt(process.getuid());
|
|
1536
|
+
return details.isFile()
|
|
1537
|
+
&& !details.isSymbolicLink()
|
|
1538
|
+
&& details.nlink === 1n
|
|
1539
|
+
&& details.size <= BigInt(MAX_RUNTIME_REUSE_PROOF_BYTES)
|
|
1540
|
+
&& (details.mode & 63n) === 0n
|
|
1541
|
+
&& owned;
|
|
1542
|
+
}
|
|
1543
|
+
function runtimeReuseProofFromJson(value, identity, packageSource, additionalPackages, marker) {
|
|
1544
|
+
if (!hasExactKeys(value, ["schema", "identity", "packageSource", "additionalPackages", "source", "runtime"])) {
|
|
1545
|
+
return undefined;
|
|
1546
|
+
}
|
|
1547
|
+
if (value.schema !== RUNTIME_REUSE_PROOF_SCHEMA
|
|
1548
|
+
|| !sameRuntimeIdentityJson(value.identity, identity)
|
|
1549
|
+
|| value.packageSource !== packageSource
|
|
1550
|
+
|| !Array.isArray(value.additionalPackages)
|
|
1551
|
+
|| JSON.stringify(value.additionalPackages) !== JSON.stringify(additionalPackages)) {
|
|
1552
|
+
return undefined;
|
|
1553
|
+
}
|
|
1554
|
+
const source = sourceClosureReuseProofFromJson(value.source, packageSource);
|
|
1555
|
+
const runtime = runtimeClosureReuseProofFromJson(value.runtime, marker);
|
|
1556
|
+
return source === undefined || runtime === undefined
|
|
1557
|
+
? undefined
|
|
1558
|
+
: {
|
|
1559
|
+
schema: RUNTIME_REUSE_PROOF_SCHEMA,
|
|
1560
|
+
identity,
|
|
1561
|
+
packageSource,
|
|
1562
|
+
additionalPackages,
|
|
1563
|
+
source,
|
|
1564
|
+
runtime,
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function sourceClosureReuseProofFromJson(value, packageSource) {
|
|
1568
|
+
if (!hasExactKeys(value, ["sourceProofSha256", "packages", "links"]))
|
|
1569
|
+
return undefined;
|
|
1570
|
+
if (typeof value.sourceProofSha256 !== "string"
|
|
1571
|
+
|| !/^[0-9a-f]{64}$/u.test(value.sourceProofSha256)
|
|
1572
|
+
|| !Array.isArray(value.packages)
|
|
1573
|
+
|| !Array.isArray(value.links)) {
|
|
1574
|
+
return undefined;
|
|
1575
|
+
}
|
|
1576
|
+
const packages = [];
|
|
1577
|
+
const roots = new Set();
|
|
1578
|
+
for (const [index, rawPackage] of value.packages.entries()) {
|
|
1579
|
+
if (!hasExactKeys(rawPackage, ["id", "sourceRoot", "entries"]))
|
|
1580
|
+
return undefined;
|
|
1581
|
+
if (rawPackage.id !== index
|
|
1582
|
+
|| typeof rawPackage.sourceRoot !== "string"
|
|
1583
|
+
|| !isCanonicalAbsolutePath(rawPackage.sourceRoot)
|
|
1584
|
+
|| roots.has(rawPackage.sourceRoot)
|
|
1585
|
+
|| !Array.isArray(rawPackage.entries)) {
|
|
1586
|
+
return undefined;
|
|
1587
|
+
}
|
|
1588
|
+
const entries = proofEntriesFromJson(rawPackage.entries, true);
|
|
1589
|
+
if (entries === undefined || entries[0]?.path !== ".")
|
|
1590
|
+
return undefined;
|
|
1591
|
+
roots.add(rawPackage.sourceRoot);
|
|
1592
|
+
packages.push({ id: index, sourceRoot: rawPackage.sourceRoot, entries });
|
|
1593
|
+
}
|
|
1594
|
+
if (packages[0]?.sourceRoot !== packageSource)
|
|
1595
|
+
return undefined;
|
|
1596
|
+
const links = [];
|
|
1597
|
+
const linkPaths = new Set();
|
|
1598
|
+
for (const rawLink of value.links) {
|
|
1599
|
+
if (!hasExactKeys(rawLink, ["path", "logicalPath", "proof"]))
|
|
1600
|
+
return undefined;
|
|
1601
|
+
if (typeof rawLink.path !== "string"
|
|
1602
|
+
|| !isCanonicalAbsolutePath(rawLink.path)
|
|
1603
|
+
|| linkPaths.has(rawLink.path)
|
|
1604
|
+
|| typeof rawLink.logicalPath !== "string"
|
|
1605
|
+
|| rawLink.logicalPath.length === 0) {
|
|
1606
|
+
return undefined;
|
|
1607
|
+
}
|
|
1608
|
+
const proof = sourceProofEntryFromJson(rawLink.proof);
|
|
1609
|
+
if (proof === undefined || proof.path !== rawLink.logicalPath)
|
|
1610
|
+
return undefined;
|
|
1611
|
+
linkPaths.add(rawLink.path);
|
|
1612
|
+
links.push({ path: rawLink.path, logicalPath: rawLink.logicalPath, proof });
|
|
1613
|
+
}
|
|
1614
|
+
return { sourceProofSha256: value.sourceProofSha256, packages, links };
|
|
1615
|
+
}
|
|
1616
|
+
function runtimeClosureReuseProofFromJson(value, marker) {
|
|
1617
|
+
if (!hasExactKeys(value, [
|
|
1618
|
+
"rootEntries",
|
|
1619
|
+
"entries",
|
|
1620
|
+
"closureManifestSha256",
|
|
1621
|
+
"executionProofSha256",
|
|
1622
|
+
]))
|
|
1623
|
+
return undefined;
|
|
1624
|
+
if (value.closureManifestSha256 !== marker.closureManifestSha256
|
|
1625
|
+
|| value.executionProofSha256 !== marker.executionProofSha256
|
|
1626
|
+
|| !Array.isArray(value.rootEntries)
|
|
1627
|
+
|| !Array.isArray(value.entries)) {
|
|
1628
|
+
return undefined;
|
|
1629
|
+
}
|
|
1630
|
+
const rootEntries = [];
|
|
1631
|
+
const rootNames = new Set();
|
|
1632
|
+
for (const name of value.rootEntries) {
|
|
1633
|
+
if (typeof name !== "string"
|
|
1634
|
+
|| name.length === 0
|
|
1635
|
+
|| name === "."
|
|
1636
|
+
|| name === ".."
|
|
1637
|
+
|| name.includes("/")
|
|
1638
|
+
|| name.includes("\\")
|
|
1639
|
+
|| name.includes("\0")
|
|
1640
|
+
|| rootNames.has(name)
|
|
1641
|
+
|| name === ".mono-agent-closure.json"
|
|
1642
|
+
|| name === ".mono-agent-runtime.json") {
|
|
1643
|
+
return undefined;
|
|
1644
|
+
}
|
|
1645
|
+
rootNames.add(name);
|
|
1646
|
+
rootEntries.push(name);
|
|
1647
|
+
}
|
|
1648
|
+
if (JSON.stringify(rootEntries) !== JSON.stringify([...rootEntries].sort(compareCodeUnits)))
|
|
1649
|
+
return undefined;
|
|
1650
|
+
const entries = [];
|
|
1651
|
+
const entryPaths = new Set();
|
|
1652
|
+
for (const rawEntry of value.entries) {
|
|
1653
|
+
if (!hasExactKeys(rawEntry, ["path", "proof"]))
|
|
1654
|
+
return undefined;
|
|
1655
|
+
if (typeof rawEntry.path !== "string"
|
|
1656
|
+
|| !isSafePortableRelativePath(rawEntry.path)
|
|
1657
|
+
|| entryPaths.has(rawEntry.path)) {
|
|
1658
|
+
return undefined;
|
|
1659
|
+
}
|
|
1660
|
+
const proof = sourceProofEntryFromJson(rawEntry.proof);
|
|
1661
|
+
if (proof === undefined || proof.path !== rawEntry.path)
|
|
1662
|
+
return undefined;
|
|
1663
|
+
entryPaths.add(rawEntry.path);
|
|
1664
|
+
entries.push({ path: rawEntry.path, proof });
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
rootEntries,
|
|
1668
|
+
entries,
|
|
1669
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1670
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function runtimeClosureManifestFromJson(value) {
|
|
1674
|
+
if (!hasExactKeys(value, ["schema", "entries"]) || value.schema !== "mono-agent.execution-closure.v1") {
|
|
1675
|
+
return undefined;
|
|
1676
|
+
}
|
|
1677
|
+
if (!Array.isArray(value.entries))
|
|
1678
|
+
return undefined;
|
|
1679
|
+
const entries = [];
|
|
1680
|
+
const paths = new Set();
|
|
1681
|
+
for (const rawEntry of value.entries) {
|
|
1682
|
+
if (!isRecord(rawEntry) || typeof rawEntry.type !== "string")
|
|
1683
|
+
return undefined;
|
|
1684
|
+
const expectedKeys = rawEntry.type === "file"
|
|
1685
|
+
? ["path", "type", "mode", "sha256"]
|
|
1686
|
+
: rawEntry.type === "symlink"
|
|
1687
|
+
? ["path", "type", "mode", "target"]
|
|
1688
|
+
: ["path", "type", "mode"];
|
|
1689
|
+
if (!hasExactKeys(rawEntry, expectedKeys)
|
|
1690
|
+
|| typeof rawEntry.path !== "string"
|
|
1691
|
+
|| !isSafePortableRelativePath(rawEntry.path)
|
|
1692
|
+
|| paths.has(rawEntry.path)
|
|
1693
|
+
|| typeof rawEntry.mode !== "string"
|
|
1694
|
+
|| !/^0[0-7]{3}$/u.test(rawEntry.mode)) {
|
|
1695
|
+
return undefined;
|
|
1696
|
+
}
|
|
1697
|
+
if (rawEntry.type === "file") {
|
|
1698
|
+
if (typeof rawEntry.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(rawEntry.sha256))
|
|
1699
|
+
return undefined;
|
|
1700
|
+
entries.push({ path: rawEntry.path, type: "file", mode: rawEntry.mode, sha256: rawEntry.sha256 });
|
|
1701
|
+
}
|
|
1702
|
+
else if (rawEntry.type === "symlink") {
|
|
1703
|
+
if (typeof rawEntry.target !== "string" || rawEntry.target.includes("\0"))
|
|
1704
|
+
return undefined;
|
|
1705
|
+
entries.push({ path: rawEntry.path, type: "symlink", mode: rawEntry.mode, target: rawEntry.target });
|
|
1706
|
+
}
|
|
1707
|
+
else if (rawEntry.type === "directory") {
|
|
1708
|
+
entries.push({ path: rawEntry.path, type: "directory", mode: rawEntry.mode });
|
|
1709
|
+
}
|
|
1710
|
+
else {
|
|
1711
|
+
return undefined;
|
|
1712
|
+
}
|
|
1713
|
+
paths.add(rawEntry.path);
|
|
1714
|
+
}
|
|
1715
|
+
return { schema: "mono-agent.execution-closure.v1", entries };
|
|
1716
|
+
}
|
|
1717
|
+
function proofEntriesFromJson(values, allowRoot) {
|
|
1718
|
+
const entries = [];
|
|
1719
|
+
const paths = new Set();
|
|
1720
|
+
for (const value of values) {
|
|
1721
|
+
const entry = sourceProofEntryFromJson(value);
|
|
1722
|
+
if (entry === undefined
|
|
1723
|
+
|| (entry.path !== "." && !isSafePortableRelativePath(entry.path))
|
|
1724
|
+
|| (!allowRoot && entry.path === ".")
|
|
1725
|
+
|| paths.has(entry.path)) {
|
|
1726
|
+
return undefined;
|
|
1727
|
+
}
|
|
1728
|
+
paths.add(entry.path);
|
|
1729
|
+
entries.push(entry);
|
|
1730
|
+
}
|
|
1731
|
+
return entries;
|
|
1732
|
+
}
|
|
1733
|
+
function sourceProofEntryFromJson(value) {
|
|
1734
|
+
if (!isRecord(value))
|
|
1735
|
+
return undefined;
|
|
1736
|
+
const type = value.type;
|
|
1737
|
+
const expectedKeys = type === "symlink"
|
|
1738
|
+
? ["path", "type", "dev", "ino", "nlink", "mode", "size", "mtimeNs", "ctimeNs", "target"]
|
|
1739
|
+
: ["path", "type", "dev", "ino", "nlink", "mode", "size", "mtimeNs", "ctimeNs"];
|
|
1740
|
+
if (!hasExactKeys(value, expectedKeys)
|
|
1741
|
+
|| typeof value.path !== "string"
|
|
1742
|
+
|| (type !== "directory" && type !== "file" && type !== "symlink")
|
|
1743
|
+
|| ![value.dev, value.ino, value.nlink, value.mode, value.size, value.mtimeNs, value.ctimeNs]
|
|
1744
|
+
.every((entry) => typeof entry === "string" && /^-?\d+$/u.test(entry))
|
|
1745
|
+
|| (type === "symlink" && (typeof value.target !== "string" || value.target.includes("\0")))) {
|
|
1746
|
+
return undefined;
|
|
1747
|
+
}
|
|
1748
|
+
return value;
|
|
1749
|
+
}
|
|
1750
|
+
function sameRuntimeIdentityJson(value, identity) {
|
|
1751
|
+
return hasExactKeys(value, ["packageVersion", "cliSha256", "sourceClosureSha256", "nodeAbi", "platform", "arch"])
|
|
1752
|
+
&& value.packageVersion === identity.packageVersion
|
|
1753
|
+
&& value.cliSha256 === identity.cliSha256
|
|
1754
|
+
&& value.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1755
|
+
&& value.nodeAbi === identity.nodeAbi
|
|
1756
|
+
&& value.platform === identity.platform
|
|
1757
|
+
&& value.arch === identity.arch;
|
|
1758
|
+
}
|
|
1759
|
+
function hasExactKeys(value, keys) {
|
|
1760
|
+
return isRecord(value)
|
|
1761
|
+
&& Object.keys(value).length === keys.length
|
|
1762
|
+
&& keys.every((key) => Object.hasOwn(value, key));
|
|
1763
|
+
}
|
|
1764
|
+
function isCanonicalAbsolutePath(path) {
|
|
1765
|
+
return isAbsolute(path) && resolve(path) === path && !path.includes("\0");
|
|
1766
|
+
}
|
|
1767
|
+
function isSafePortableRelativePath(path) {
|
|
1768
|
+
if (path.length === 0 || path === "." || path.includes("\\") || path.includes("\0") || isAbsolute(path)) {
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
const segments = path.split("/");
|
|
1772
|
+
return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
1773
|
+
}
|
|
1774
|
+
function runtimePathFromPortableRelative(root, path) {
|
|
1775
|
+
if (!isSafePortableRelativePath(path)) {
|
|
1776
|
+
throw new Error(`Managed runtime proof path ${JSON.stringify(path)} is unsafe.`);
|
|
1777
|
+
}
|
|
1778
|
+
const target = join(root, ...path.split("/"));
|
|
1779
|
+
assertPathInsideRoot(root, target, `proof path ${path}`);
|
|
1780
|
+
return target;
|
|
1781
|
+
}
|
|
1782
|
+
async function mapWithConcurrency(values, concurrency, mapper) {
|
|
1783
|
+
const results = new Array(values.length);
|
|
1784
|
+
let nextIndex = 0;
|
|
1785
|
+
const workers = Array.from({ length: Math.min(Math.max(1, concurrency), values.length) }, async () => {
|
|
1786
|
+
for (;;) {
|
|
1787
|
+
const index = nextIndex;
|
|
1788
|
+
nextIndex += 1;
|
|
1789
|
+
if (index >= values.length)
|
|
1790
|
+
return;
|
|
1791
|
+
results[index] = await mapper(values[index], index);
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
await Promise.all(workers);
|
|
1795
|
+
return results;
|
|
1796
|
+
}
|
|
1797
|
+
async function pathExists(path) {
|
|
1798
|
+
try {
|
|
1799
|
+
await lstat(path);
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
catch (error) {
|
|
1803
|
+
if (isErrno(error, "ENOENT"))
|
|
1804
|
+
return false;
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
async function acquireRuntimeLock(layout, identity, additionalPackages, deps, isSatisfied = () => verifyRuntime(layout, identity, additionalPackages)) {
|
|
1087
1809
|
const incarnation = await (deps.currentProcessIncarnation ?? currentProcessIncarnation)();
|
|
1088
1810
|
return acquireOwnerPrivateLock({
|
|
1089
1811
|
path: layout.lockDir,
|
|
@@ -1110,7 +1832,7 @@ async function acquireRuntimeLock(layout, identity, additionalPackages, deps) {
|
|
|
1110
1832
|
: undefined;
|
|
1111
1833
|
},
|
|
1112
1834
|
invalidOwner: "ownerless",
|
|
1113
|
-
isSatisfied
|
|
1835
|
+
isSatisfied,
|
|
1114
1836
|
sleep: deps.sleep,
|
|
1115
1837
|
staleRace: "return",
|
|
1116
1838
|
beforeStaleRename: async () => {
|
|
@@ -1231,7 +1953,22 @@ function sameRuntimeMarker(left, right) {
|
|
|
1231
1953
|
&& left.nodeAbi === right.nodeAbi
|
|
1232
1954
|
&& left.platform === right.platform
|
|
1233
1955
|
&& left.arch === right.arch
|
|
1234
|
-
&& left.installedAt === right.installedAt
|
|
1956
|
+
&& left.installedAt === right.installedAt
|
|
1957
|
+
&& (left.schema !== "mono-agent.managed-runtime.v5"
|
|
1958
|
+
|| (right.schema === "mono-agent.managed-runtime.v5"
|
|
1959
|
+
&& left.reuseProofSha256 === right.reuseProofSha256));
|
|
1960
|
+
}
|
|
1961
|
+
function runtimeMarkerMatchesProvenance(marker, input) {
|
|
1962
|
+
return marker.packageName === PACKAGE_NAME
|
|
1963
|
+
&& marker.closureManifestSha256 === input.closureManifestSha256
|
|
1964
|
+
&& marker.executionProofSha256 === input.executionProofSha256
|
|
1965
|
+
&& marker.packageVersion === input.packageVersion
|
|
1966
|
+
&& marker.cliSha256 === input.cliSha256
|
|
1967
|
+
&& marker.sourceClosureSha256 === input.sourceClosureSha256
|
|
1968
|
+
&& marker.nodeAbi === input.nodeAbi
|
|
1969
|
+
&& marker.platform === input.platform
|
|
1970
|
+
&& marker.arch === input.arch
|
|
1971
|
+
&& marker.installedAt === input.installedAt;
|
|
1235
1972
|
}
|
|
1236
1973
|
async function verifiedRuntimeMarker(layout, identity) {
|
|
1237
1974
|
try {
|
|
@@ -1250,6 +1987,17 @@ async function verifiedRuntimeMarker(layout, identity) {
|
|
|
1250
1987
|
}
|
|
1251
1988
|
}
|
|
1252
1989
|
async function readRuntimeMarker(path, identity) {
|
|
1990
|
+
const bytes = await readPrivateRuntimeMarkerBytes(path);
|
|
1991
|
+
if (bytes === undefined)
|
|
1992
|
+
return undefined;
|
|
1993
|
+
try {
|
|
1994
|
+
return runtimeMarkerFromJson(JSON.parse(bytes.toString("utf8")), identity);
|
|
1995
|
+
}
|
|
1996
|
+
catch {
|
|
1997
|
+
return undefined;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
async function readPrivateRuntimeMarkerBytes(path) {
|
|
1253
2001
|
let handle;
|
|
1254
2002
|
try {
|
|
1255
2003
|
handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
@@ -1267,7 +2015,7 @@ async function readRuntimeMarker(path, identity) {
|
|
|
1267
2015
|
|| !sameSourceStats(before, named)) {
|
|
1268
2016
|
return undefined;
|
|
1269
2017
|
}
|
|
1270
|
-
return
|
|
2018
|
+
return bytes;
|
|
1271
2019
|
}
|
|
1272
2020
|
catch {
|
|
1273
2021
|
return undefined;
|
|
@@ -1285,10 +2033,17 @@ function privateRuntimeMarkerFile(details) {
|
|
|
1285
2033
|
&& (details.mode & 63n) === 0n
|
|
1286
2034
|
&& owned;
|
|
1287
2035
|
}
|
|
2036
|
+
function privateRuntimeDirectory(details) {
|
|
2037
|
+
const owned = typeof process.getuid !== "function" || details.uid === BigInt(process.getuid());
|
|
2038
|
+
return details.isDirectory()
|
|
2039
|
+
&& !details.isSymbolicLink()
|
|
2040
|
+
&& (details.mode & 63n) === 0n
|
|
2041
|
+
&& owned;
|
|
2042
|
+
}
|
|
1288
2043
|
function runtimeMarkerFromJson(value, identity) {
|
|
1289
2044
|
if (!isRecord(value))
|
|
1290
2045
|
return undefined;
|
|
1291
|
-
const
|
|
2046
|
+
const commonKeys = [
|
|
1292
2047
|
"schema",
|
|
1293
2048
|
"packageName",
|
|
1294
2049
|
"closureManifestSha256",
|
|
@@ -1301,10 +2056,13 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1301
2056
|
"arch",
|
|
1302
2057
|
"installedAt",
|
|
1303
2058
|
];
|
|
2059
|
+
const exactKeys = value.schema === "mono-agent.managed-runtime.v5"
|
|
2060
|
+
? [...commonKeys, "reuseProofSha256"]
|
|
2061
|
+
: commonKeys;
|
|
1304
2062
|
const installedAtMs = typeof value.installedAt === "string" ? Date.parse(value.installedAt) : Number.NaN;
|
|
1305
2063
|
const valid = Object.keys(value).length === exactKeys.length
|
|
1306
2064
|
&& exactKeys.every((key) => Object.hasOwn(value, key))
|
|
1307
|
-
&& value.schema === "mono-agent.managed-runtime.v4"
|
|
2065
|
+
&& (value.schema === "mono-agent.managed-runtime.v4" || value.schema === "mono-agent.managed-runtime.v5")
|
|
1308
2066
|
&& value.packageName === PACKAGE_NAME
|
|
1309
2067
|
&& value.packageVersion === identity.packageVersion
|
|
1310
2068
|
&& value.cliSha256 === identity.cliSha256
|
|
@@ -1316,6 +2074,8 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1316
2074
|
&& /^[0-9a-f]{64}$/u.test(value.closureManifestSha256)
|
|
1317
2075
|
&& typeof value.executionProofSha256 === "string"
|
|
1318
2076
|
&& /^[0-9a-f]{64}$/u.test(value.executionProofSha256)
|
|
2077
|
+
&& (value.schema !== "mono-agent.managed-runtime.v5"
|
|
2078
|
+
|| (typeof value.reuseProofSha256 === "string" && /^[0-9a-f]{64}$/u.test(value.reuseProofSha256)))
|
|
1319
2079
|
&& typeof value.installedAt === "string"
|
|
1320
2080
|
&& Number.isFinite(installedAtMs)
|
|
1321
2081
|
&& new Date(installedAtMs).toISOString() === value.installedAt
|
|
@@ -1325,6 +2085,32 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1325
2085
|
&& value.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT;
|
|
1326
2086
|
return valid ? value : undefined;
|
|
1327
2087
|
}
|
|
2088
|
+
function runtimeIdentityFromMarkerJson(value) {
|
|
2089
|
+
if (!isRecord(value))
|
|
2090
|
+
return undefined;
|
|
2091
|
+
if (typeof value.packageVersion !== "string"
|
|
2092
|
+
|| !isExactVersion(value.packageVersion)
|
|
2093
|
+
|| typeof value.cliSha256 !== "string"
|
|
2094
|
+
|| !/^[0-9a-f]{64}$/u.test(value.cliSha256)
|
|
2095
|
+
|| typeof value.sourceClosureSha256 !== "string"
|
|
2096
|
+
|| !/^[0-9a-f]{64}$/u.test(value.sourceClosureSha256)
|
|
2097
|
+
|| typeof value.nodeAbi !== "string"
|
|
2098
|
+
|| value.nodeAbi.length === 0
|
|
2099
|
+
|| typeof value.platform !== "string"
|
|
2100
|
+
|| value.platform.length === 0
|
|
2101
|
+
|| typeof value.arch !== "string"
|
|
2102
|
+
|| value.arch.length === 0) {
|
|
2103
|
+
return undefined;
|
|
2104
|
+
}
|
|
2105
|
+
return {
|
|
2106
|
+
packageVersion: value.packageVersion,
|
|
2107
|
+
cliSha256: value.cliSha256,
|
|
2108
|
+
sourceClosureSha256: value.sourceClosureSha256,
|
|
2109
|
+
nodeAbi: value.nodeAbi,
|
|
2110
|
+
platform: value.platform,
|
|
2111
|
+
arch: value.arch,
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
1328
2114
|
async function verifyInstalledPackage(layout, identity) {
|
|
1329
2115
|
try {
|
|
1330
2116
|
const packageRoot = dirname(layout.packageJsonPath);
|
|
@@ -1386,6 +2172,28 @@ async function verifyClosureManifest(layout, expectedSha256, deps = {}) {
|
|
|
1386
2172
|
return false;
|
|
1387
2173
|
}
|
|
1388
2174
|
}
|
|
2175
|
+
async function verifyClosureManifestFingerprint(layout, expectedSha256) {
|
|
2176
|
+
try {
|
|
2177
|
+
await assertRegularFile(layout.closureManifestPath, "managed runtime closure manifest");
|
|
2178
|
+
const stored = await fingerprintClosureFile(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
2179
|
+
if (stored.sha256 !== expectedSha256)
|
|
2180
|
+
return false;
|
|
2181
|
+
const parsed = JSON.parse(stored.bytes.toString("utf8"));
|
|
2182
|
+
return parsed.schema === "mono-agent.execution-closure.v1" && Array.isArray(parsed.entries);
|
|
2183
|
+
}
|
|
2184
|
+
catch {
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async function verifyManagedRuntimeCliFingerprint(layout, expectedSha256) {
|
|
2189
|
+
try {
|
|
2190
|
+
const captured = await fingerprintClosureFile(layout.cliPath, "node_modules/@mono-agent/agent-app/dist/cli.js");
|
|
2191
|
+
return captured.sha256 === expectedSha256;
|
|
2192
|
+
}
|
|
2193
|
+
catch {
|
|
2194
|
+
return false;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1389
2197
|
async function captureRuntimeClosureManifest(layout, deps = {}) {
|
|
1390
2198
|
const entries = [];
|
|
1391
2199
|
const proofs = [];
|
|
@@ -1544,18 +2352,21 @@ async function writeFinalizedRuntimeMarker(path, marker, deps) {
|
|
|
1544
2352
|
throw new Error("Could not publish a stable managed runtime launch boundary.");
|
|
1545
2353
|
}
|
|
1546
2354
|
async function waitForManagedRuntimeLaunchBoundary(layout, identity, deps) {
|
|
1547
|
-
const marker = await
|
|
2355
|
+
const marker = await readRuntimeMarker(layout.markerPath, identity);
|
|
1548
2356
|
if (marker === undefined) {
|
|
1549
2357
|
throw new Error("The managed runtime marker became invalid before launch.");
|
|
1550
2358
|
}
|
|
1551
|
-
|
|
2359
|
+
await waitForLaunchBoundary(marker.installedAt, deps.now, deps.sleep);
|
|
2360
|
+
}
|
|
2361
|
+
async function waitForLaunchBoundary(installedAt, now, sleepFor) {
|
|
2362
|
+
const boundaryMs = Date.parse(installedAt);
|
|
1552
2363
|
const safeStartMs = (Math.floor(boundaryMs / 1_000) + 1) * 1_000;
|
|
1553
|
-
const nowMs =
|
|
2364
|
+
const nowMs = now();
|
|
1554
2365
|
if (safeStartMs - nowMs > 5_000) {
|
|
1555
2366
|
throw new Error("The managed runtime launch boundary is implausibly far in the future.");
|
|
1556
2367
|
}
|
|
1557
2368
|
if (nowMs < safeStartMs)
|
|
1558
|
-
await
|
|
2369
|
+
await sleepFor(safeStartMs - nowMs);
|
|
1559
2370
|
}
|
|
1560
2371
|
function privateJsonContents(value) {
|
|
1561
2372
|
return `${JSON.stringify(value, undefined, 2)}\n`;
|
|
@@ -1611,7 +2422,15 @@ function ownedPrivately(details) {
|
|
|
1611
2422
|
const owned = typeof process.getuid !== "function" || Number(details.uid) === process.getuid();
|
|
1612
2423
|
return owned && (Number(details.mode) & 0o077) === 0;
|
|
1613
2424
|
}
|
|
1614
|
-
function runtimeResult(layout, identity, nodePath) {
|
|
2425
|
+
async function runtimeResult(layout, identity, nodePath, verificationMode) {
|
|
2426
|
+
const markerBytes = await readPrivateRuntimeMarkerBytes(layout.markerPath);
|
|
2427
|
+
if (markerBytes === undefined) {
|
|
2428
|
+
throw new Error("The managed runtime marker became unsafe before launch proof publication.");
|
|
2429
|
+
}
|
|
2430
|
+
const marker = runtimeMarkerFromJson(parseJson(markerBytes, "managed runtime marker"), identity);
|
|
2431
|
+
if (marker === undefined) {
|
|
2432
|
+
throw new Error("The managed runtime marker became invalid before launch proof publication.");
|
|
2433
|
+
}
|
|
1615
2434
|
return {
|
|
1616
2435
|
cliPath: layout.cliPath,
|
|
1617
2436
|
nodePath: resolve(nodePath),
|
|
@@ -1619,8 +2438,17 @@ function runtimeResult(layout, identity, nodePath) {
|
|
|
1619
2438
|
packageVersion: identity.packageVersion,
|
|
1620
2439
|
cliSha256: identity.cliSha256,
|
|
1621
2440
|
nodeAbi: identity.nodeAbi,
|
|
2441
|
+
verificationMode,
|
|
2442
|
+
launchProof: encodeManagedRuntimeLaunchProof({
|
|
2443
|
+
schema: MANAGED_RUNTIME_LAUNCH_PROOF_SCHEMA,
|
|
2444
|
+
markerSha256: sha256(markerBytes),
|
|
2445
|
+
installedAt: marker.installedAt,
|
|
2446
|
+
}),
|
|
1622
2447
|
};
|
|
1623
2448
|
}
|
|
2449
|
+
async function sleep(milliseconds) {
|
|
2450
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
2451
|
+
}
|
|
1624
2452
|
function requiredNodeAbi() {
|
|
1625
2453
|
const abi = process.versions.modules;
|
|
1626
2454
|
if (abi === undefined || abi.trim().length === 0) {
|