@mono-agent/agent-app 0.12.0 → 0.14.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 +190 -33
- package/dist/adapter-send-tools-main.js +0 -1
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +8 -5
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +195 -257
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-controller-channels.d.ts.map +1 -1
- package/dist/app-controller-channels.js +2 -1
- package/dist/app-controller-channels.js.map +1 -1
- package/dist/app-controller-continuation.d.ts.map +1 -1
- package/dist/app-controller-continuation.js +1 -5
- package/dist/app-controller-continuation.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 +2 -1
- package/dist/app-controller-maintenance.d.ts.map +1 -1
- package/dist/app-controller-maintenance.js +66 -2
- 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 +12 -4
- 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-utils.js +1 -1
- package/dist/app-controller-utils.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-environment.d.ts +1 -0
- package/dist/background-environment.d.ts.map +1 -1
- package/dist/background-environment.js +1 -0
- package/dist/background-environment.js.map +1 -1
- package/dist/background-runtime.d.ts +39 -2
- package/dist/background-runtime.d.ts.map +1 -1
- package/dist/background-runtime.js +901 -49
- package/dist/background-runtime.js.map +1 -1
- package/dist/background.d.ts +36 -3
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +404 -57
- package/dist/background.js.map +1 -1
- package/dist/channel-drivers/cron.js +8 -1
- package/dist/channel-drivers/cron.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/native-notify.d.ts +2 -0
- package/dist/channel-drivers/native-notify.d.ts.map +1 -1
- package/dist/channel-drivers/native-notify.js +14 -2
- package/dist/channel-drivers/native-notify.js.map +1 -1
- package/dist/channel-drivers/slack.d.ts.map +1 -1
- package/dist/channel-drivers/slack.js +55 -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 +19 -14
- package/dist/channel-drivers/telegram.js.map +1 -1
- package/dist/channel-drivers/tui.d.ts.map +1 -1
- package/dist/channel-drivers/tui.js +8 -0
- package/dist/channel-drivers/tui.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 +23 -22
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +136 -83
- 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 +158 -27
- 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 +27 -59
- 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 +203 -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 +10 -0
- package/dist/cli-runs-command.d.ts.map +1 -0
- package/dist/cli-runs-command.js +66 -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.d.ts.map +1 -1
- package/dist/config-reference.js +4 -5
- package/dist/config-reference.js.map +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 +4 -5
- 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 +5 -20
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +255 -100
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/launchd.d.ts +35 -1
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +131 -2
- 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/modules/catalog.js +1 -1
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/known-tools.d.ts +1 -1
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +0 -3
- package/dist/modules/known-tools.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 +6 -2
- package/dist/run-history.d.ts.map +1 -1
- package/dist/run-history.js +511 -118
- 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/ui.js +1 -1
- package/dist/ui.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/dist/wizard/prompts.d.ts.map +1 -1
- package/dist/wizard/prompts.js +3 -4
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.js +3 -3
- package/dist/wizard/run.js.map +1 -1
- package/package.json +15 -16
- package/schema/mono-agent.config.schema.json +4 -4
- 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 +18 -10
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -5
- package/skills/mono-agent-composer/references/feature-coverage.md +10 -9
- package/skills/mono-agent-composer/references/package-map.md +10 -4
- package/skills/mono-agent-composer/references/playbooks.md +6 -6
- package/skills/mono-agent-composer/references/validation.md +24 -2
|
@@ -4,16 +4,21 @@ import { chmod, cp, lstat, mkdir, open, readFile, readlink, readdir, realpath, r
|
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { accountHomeDirectory } from "./account-home.js";
|
|
7
|
-
import { isBackgroundOperationalEnvName } from "./background-environment.js";
|
|
7
|
+
import { isBackgroundOperationalEnvName, MANAGED_BACKGROUND_WORKER_ENV, } from "./background-environment.js";
|
|
8
|
+
export { MANAGED_BACKGROUND_WORKER_ENV } from "./background-environment.js";
|
|
8
9
|
import { currentProcessIncarnation, isSameProcessIncarnation as matchesProcessIncarnation, processIncarnationFromJson, } from "./process-incarnation.js";
|
|
9
10
|
import { acquireOwnerPrivateLock } from "./owner-private-lock.js";
|
|
10
11
|
const PACKAGE_NAME = "@mono-agent/agent-app";
|
|
11
|
-
|
|
12
|
-
const LOCK_WAIT_TIMEOUT_MS = 30_000;
|
|
12
|
+
const LOCK_WAIT_TIMEOUT_MS = 5 * 60_000;
|
|
13
13
|
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,163 @@ 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
|
+
* Inspect the controller CLI as inert installation input. The recovery helper
|
|
229
|
+
* uses this to decide whether launchd owns an older closure without ever
|
|
230
|
+
* executing mutable checkout bytes.
|
|
231
|
+
*/
|
|
232
|
+
export async function inspectManagedRuntimeSourceIdentity(currentCliPathInput) {
|
|
233
|
+
const currentCliPath = resolve(currentCliPathInput);
|
|
234
|
+
await assertRegularFile(currentCliPath, "current mono-agent CLI");
|
|
235
|
+
const packageSource = packageRootForCli(currentCliPath);
|
|
236
|
+
const sourceDetails = await lstat(packageSource);
|
|
237
|
+
if (!sourceDetails.isDirectory() || sourceDetails.isSymbolicLink()) {
|
|
238
|
+
throw new Error(`Managed runtime package source ${packageSource} must be a real directory.`);
|
|
239
|
+
}
|
|
240
|
+
const packageVersion = await packageVersionAt(packageSource);
|
|
241
|
+
if (!isExactVersion(packageVersion)) {
|
|
242
|
+
throw new Error(`Cannot inspect a durable managed runtime for invalid package version ${JSON.stringify(packageVersion)}.`);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
packageVersion,
|
|
246
|
+
cliSha256: sha256(await readFile(currentCliPath)),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Validate the opaque proof carried by a managed LaunchAgent against the exact
|
|
251
|
+
* CLI that is about to start. This is intentionally lighter than controller
|
|
252
|
+
* attestation: it rechecks the private layout, marker, CLI, and stored closure
|
|
253
|
+
* manifest fingerprints without traversing the entire installed dependency
|
|
254
|
+
* tree on every KeepAlive restart.
|
|
255
|
+
*/
|
|
256
|
+
export async function verifyManagedRuntimeLaunch(input) {
|
|
257
|
+
const proof = decodeManagedRuntimeLaunchProof(input.launchProof);
|
|
258
|
+
const currentCliPath = resolve(input.currentCliPath);
|
|
259
|
+
const packageRoot = packageRootForCli(currentCliPath);
|
|
260
|
+
const installRoot = dirname(dirname(dirname(packageRoot)));
|
|
261
|
+
const home = resolve(input.homeDir ?? accountHomeDirectory());
|
|
262
|
+
const initialInstallRoot = await lstat(installRoot, { bigint: true });
|
|
263
|
+
if (!privateRuntimeDirectory(initialInstallRoot)) {
|
|
264
|
+
throw new Error("The managed runtime closure root is not a real owner-private directory.");
|
|
265
|
+
}
|
|
266
|
+
const markerPath = join(installRoot, ".mono-agent-runtime.json");
|
|
267
|
+
const initialBytes = await readPrivateRuntimeMarkerBytes(markerPath);
|
|
268
|
+
if (initialBytes === undefined)
|
|
269
|
+
throw new Error("The managed runtime marker is unavailable or unsafe.");
|
|
270
|
+
const markerValue = parseJson(initialBytes, "managed runtime marker");
|
|
271
|
+
const identity = runtimeIdentityFromMarkerJson(markerValue);
|
|
272
|
+
if (identity === undefined)
|
|
273
|
+
throw new Error("The managed runtime marker has an invalid identity.");
|
|
274
|
+
const marker = runtimeMarkerFromJson(markerValue, identity);
|
|
275
|
+
if (marker === undefined)
|
|
276
|
+
throw new Error("The managed runtime marker is provisional or malformed.");
|
|
277
|
+
const layout = runtimeLayout(home, identity, "launch", 0);
|
|
278
|
+
if (layout.installRoot !== installRoot || layout.cliPath !== currentCliPath) {
|
|
279
|
+
throw new Error("The managed worker CLI is outside its canonical runtime closure.");
|
|
280
|
+
}
|
|
281
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir))) {
|
|
282
|
+
throw new Error("The managed runtime has an unsafe private ancestry.");
|
|
283
|
+
}
|
|
284
|
+
if (sha256(initialBytes) !== proof.markerSha256 || marker.installedAt !== proof.installedAt) {
|
|
285
|
+
throw new Error("The managed runtime marker does not match its LaunchAgent proof.");
|
|
286
|
+
}
|
|
287
|
+
if (!(await verifyInstalledPackage(layout, identity))) {
|
|
288
|
+
throw new Error("The managed runtime CLI or package identity failed verification.");
|
|
289
|
+
}
|
|
290
|
+
if (!(await verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256))) {
|
|
291
|
+
throw new Error("The managed runtime CLI fingerprint is unstable.");
|
|
292
|
+
}
|
|
293
|
+
if (!(await verifyClosureManifestFingerprint(layout, marker.closureManifestSha256))) {
|
|
294
|
+
throw new Error("The managed runtime closure manifest fingerprint is invalid.");
|
|
295
|
+
}
|
|
296
|
+
await waitForLaunchBoundary(marker.installedAt, input.now ?? Date.now, input.sleep ?? sleep);
|
|
297
|
+
const finalBytes = await readPrivateRuntimeMarkerBytes(markerPath);
|
|
298
|
+
if (finalBytes === undefined || sha256(finalBytes) !== proof.markerSha256 || !finalBytes.equals(initialBytes)) {
|
|
299
|
+
throw new Error("The managed runtime marker changed during launch verification.");
|
|
300
|
+
}
|
|
301
|
+
if (!(await verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256))) {
|
|
302
|
+
throw new Error("The managed runtime CLI changed during launch verification.");
|
|
303
|
+
}
|
|
304
|
+
if (!(await verifyClosureManifestFingerprint(layout, marker.closureManifestSha256))) {
|
|
305
|
+
throw new Error("The managed runtime closure manifest changed during launch verification.");
|
|
306
|
+
}
|
|
307
|
+
const finalInstallRoot = await lstat(installRoot, { bigint: true });
|
|
308
|
+
if (!privateRuntimeDirectory(finalInstallRoot) || !sameSourceStats(initialInstallRoot, finalInstallRoot)) {
|
|
309
|
+
throw new Error("The managed runtime closure root changed during launch verification.");
|
|
310
|
+
}
|
|
311
|
+
const closureId = `${identity.cliSha256}-${identity.sourceClosureSha256}`;
|
|
312
|
+
return {
|
|
313
|
+
installRoot,
|
|
314
|
+
packageVersion: identity.packageVersion,
|
|
315
|
+
cliSha256: identity.cliSha256,
|
|
316
|
+
provenanceDetail: `Runtime provenance: managed closure ${closureId} (`
|
|
317
|
+
+ `${PACKAGE_NAME} ${identity.packageVersion}; ${identity.platform}-${identity.arch}; `
|
|
318
|
+
+ `Node ABI ${identity.nodeAbi}; installed ${marker.installedAt}).`,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function encodeManagedRuntimeLaunchProof(proof) {
|
|
322
|
+
return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
|
|
323
|
+
}
|
|
324
|
+
function decodeManagedRuntimeLaunchProof(encoded) {
|
|
325
|
+
if (typeof encoded !== "string"
|
|
326
|
+
|| encoded.length === 0
|
|
327
|
+
|| encoded.length > MAX_MANAGED_RUNTIME_LAUNCH_PROOF_BYTES * 2
|
|
328
|
+
|| !/^[0-9A-Za-z_-]+$/u.test(encoded)) {
|
|
329
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
330
|
+
}
|
|
331
|
+
let bytes;
|
|
332
|
+
try {
|
|
333
|
+
bytes = Buffer.from(encoded, "base64url");
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
337
|
+
}
|
|
338
|
+
if (bytes.length === 0
|
|
339
|
+
|| bytes.length > MAX_MANAGED_RUNTIME_LAUNCH_PROOF_BYTES
|
|
340
|
+
|| bytes.toString("base64url") !== encoded) {
|
|
341
|
+
throw new Error("The managed runtime launch proof is malformed.");
|
|
342
|
+
}
|
|
343
|
+
const value = parseJson(bytes, "managed runtime launch proof");
|
|
344
|
+
if (!isRecord(value) || Object.keys(value).length !== 3) {
|
|
345
|
+
throw new Error("The managed runtime launch proof has an invalid schema.");
|
|
346
|
+
}
|
|
347
|
+
const installedAtMs = typeof value.installedAt === "string" ? Date.parse(value.installedAt) : Number.NaN;
|
|
348
|
+
if (value.schema !== MANAGED_RUNTIME_LAUNCH_PROOF_SCHEMA
|
|
349
|
+
|| typeof value.markerSha256 !== "string"
|
|
350
|
+
|| !/^[0-9a-f]{64}$/u.test(value.markerSha256)
|
|
351
|
+
|| typeof value.installedAt !== "string"
|
|
352
|
+
|| !Number.isFinite(installedAtMs)
|
|
353
|
+
|| new Date(installedAtMs).toISOString() !== value.installedAt
|
|
354
|
+
|| value.installedAt === PROVISIONAL_RUNTIME_INSTALLED_AT) {
|
|
355
|
+
throw new Error("The managed runtime launch proof has an invalid schema.");
|
|
356
|
+
}
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
function parseJson(bytes, label) {
|
|
360
|
+
try {
|
|
361
|
+
return JSON.parse(bytes.toString("utf8"));
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
throw new Error(`The ${label} is not valid JSON.`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
181
367
|
/**
|
|
182
368
|
* Canonically verify the installed package, freshly recomputed execution
|
|
183
369
|
* closure/filesystem proof, and coherent current manifest behind a provenance
|
|
@@ -212,16 +398,8 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
212
398
|
return false;
|
|
213
399
|
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir)))
|
|
214
400
|
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
401
|
const markerInitial = await verifiedRuntimeMarker(layout, identity);
|
|
224
|
-
if (markerInitial === undefined || !
|
|
402
|
+
if (markerInitial === undefined || !runtimeMarkerMatchesProvenance(markerInitial, input))
|
|
225
403
|
return false;
|
|
226
404
|
const additionalPackages = await managedRuntimeAdditionalPackages(layout);
|
|
227
405
|
if (additionalPackages === undefined)
|
|
@@ -242,7 +420,7 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
242
420
|
const markerFinal = await readRuntimeMarker(layout.markerPath, identity);
|
|
243
421
|
if (markerFinal === undefined
|
|
244
422
|
|| !sameRuntimeMarker(markerFinal, markerInitial)
|
|
245
|
-
|| !
|
|
423
|
+
|| !runtimeMarkerMatchesProvenance(markerFinal, input)
|
|
246
424
|
|| !(await verifyClosureManifest(layout, markerFinal.closureManifestSha256, deps.beforeFinalManifestProof === undefined
|
|
247
425
|
? {}
|
|
248
426
|
: { beforeFinalProof: deps.beforeFinalManifestProof }))) {
|
|
@@ -251,7 +429,7 @@ export async function verifyManagedRuntimeClosureForProvenance(input, deps = {})
|
|
|
251
429
|
const markerAfterManifest = await readRuntimeMarker(layout.markerPath, identity);
|
|
252
430
|
return markerAfterManifest !== undefined
|
|
253
431
|
&& sameRuntimeMarker(markerAfterManifest, markerInitial)
|
|
254
|
-
&&
|
|
432
|
+
&& runtimeMarkerMatchesProvenance(markerAfterManifest, input);
|
|
255
433
|
}
|
|
256
434
|
catch {
|
|
257
435
|
return false;
|
|
@@ -569,6 +747,15 @@ async function captureExecutionClosure(packageSource, additionalPackages, filesy
|
|
|
569
747
|
sourceClosureSha256: sha256(Buffer.from(JSON.stringify(stable), "utf8")),
|
|
570
748
|
sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
|
|
571
749
|
filesystemProofSha256: sha256(Buffer.from(JSON.stringify(filesystemProof), "utf8")),
|
|
750
|
+
reuseProof: {
|
|
751
|
+
sourceProofSha256: sha256(Buffer.from(JSON.stringify(proof), "utf8")),
|
|
752
|
+
packages: closure.map((node) => ({
|
|
753
|
+
id: node.id,
|
|
754
|
+
sourceRoot: node.sourceRoot,
|
|
755
|
+
entries: requiredMapValue(proofs, node.id, "source package proof"),
|
|
756
|
+
})),
|
|
757
|
+
links: [...dependencyProofPaths, ...additionalProofPaths],
|
|
758
|
+
},
|
|
572
759
|
};
|
|
573
760
|
}
|
|
574
761
|
/**
|
|
@@ -626,6 +813,15 @@ async function assertSourceClosureUnchanged(packageSource, additionalPackages, e
|
|
|
626
813
|
throw new Error(`The managed runtime package closure changed ${phase}; no staged runtime was promoted.`);
|
|
627
814
|
}
|
|
628
815
|
}
|
|
816
|
+
async function currentSourceReuseProof(packageSource, additionalPackages, expected) {
|
|
817
|
+
if (await verifySourceReuseProofStats(expected.reuseProof))
|
|
818
|
+
return expected.reuseProof;
|
|
819
|
+
const current = await captureExecutionClosure(packageSource, additionalPackages);
|
|
820
|
+
if (current.sourceClosureSha256 !== expected.sourceClosureSha256) {
|
|
821
|
+
throw new Error("The managed runtime source closure changed before warm-proof publication.");
|
|
822
|
+
}
|
|
823
|
+
return current.reuseProof;
|
|
824
|
+
}
|
|
629
825
|
/**
|
|
630
826
|
* Capture exactly the package-owned files copied by `materializeExactExecutionClosure`.
|
|
631
827
|
* `node_modules`, VCS metadata, and dotenv files are deliberately excluded.
|
|
@@ -1054,8 +1250,7 @@ async function packageVersionAt(packageRoot) {
|
|
|
1054
1250
|
return record.version;
|
|
1055
1251
|
}
|
|
1056
1252
|
function runtimeLayout(home, identity, id, nowMs) {
|
|
1057
|
-
const
|
|
1058
|
-
const versionAbiDir = join(home, ".mono-agent", "runtimes", "agent-app", identity.packageVersion, platformAbi);
|
|
1253
|
+
const versionAbiDir = runtimeVersionAbiDir(home, identity.packageVersion, identity.nodeAbi, identity.platform, identity.arch);
|
|
1059
1254
|
const closureId = `${identity.cliSha256}-${identity.sourceClosureSha256}`;
|
|
1060
1255
|
const installRoot = join(versionAbiDir, closureId);
|
|
1061
1256
|
const suffix = `${Math.max(0, Math.floor(nowMs))}-${id}`;
|
|
@@ -1067,11 +1262,17 @@ function runtimeLayout(home, identity, id, nowMs) {
|
|
|
1067
1262
|
packageLockPath: join(installRoot, "package-lock.json"),
|
|
1068
1263
|
closureManifestPath: join(installRoot, ".mono-agent-closure.json"),
|
|
1069
1264
|
markerPath: join(installRoot, ".mono-agent-runtime.json"),
|
|
1265
|
+
reuseProofDir: join(versionAbiDir, ".reuse-proofs"),
|
|
1266
|
+
reuseProofPath: join(versionAbiDir, ".reuse-proofs", `${closureId}.json`),
|
|
1070
1267
|
lockDir: join(versionAbiDir, `.${closureId}.lock`),
|
|
1071
1268
|
stagingDir: join(versionAbiDir, `.${closureId}.staging-${suffix}`),
|
|
1072
1269
|
quarantineDir: join(versionAbiDir, "quarantine", `${closureId}-${suffix}`),
|
|
1073
1270
|
};
|
|
1074
1271
|
}
|
|
1272
|
+
function runtimeVersionAbiDir(home, packageVersion, nodeAbi, platform, arch) {
|
|
1273
|
+
const platformAbi = `${safeSegment(platform)}-${safeSegment(arch)}-abi-${safeSegment(nodeAbi)}`;
|
|
1274
|
+
return join(home, ".mono-agent", "runtimes", "agent-app", packageVersion, platformAbi);
|
|
1275
|
+
}
|
|
1075
1276
|
function runtimeLayoutForRoot(layout, root) {
|
|
1076
1277
|
return {
|
|
1077
1278
|
...layout,
|
|
@@ -1083,7 +1284,552 @@ function runtimeLayoutForRoot(layout, root) {
|
|
|
1083
1284
|
markerPath: join(root, ".mono-agent-runtime.json"),
|
|
1084
1285
|
};
|
|
1085
1286
|
}
|
|
1086
|
-
async function
|
|
1287
|
+
async function findFastReusableRuntime(input) {
|
|
1288
|
+
let entries;
|
|
1289
|
+
try {
|
|
1290
|
+
entries = await readdir(input.versionAbiDir, { withFileTypes: true });
|
|
1291
|
+
}
|
|
1292
|
+
catch {
|
|
1293
|
+
return undefined;
|
|
1294
|
+
}
|
|
1295
|
+
const prefix = `${input.cliSha256}-`;
|
|
1296
|
+
for (const entry of entries.sort((left, right) => compareCodeUnits(left.name, right.name))) {
|
|
1297
|
+
if (!entry.isDirectory() || !entry.name.startsWith(prefix))
|
|
1298
|
+
continue;
|
|
1299
|
+
const sourceClosureSha256 = entry.name.slice(prefix.length);
|
|
1300
|
+
if (!/^[0-9a-f]{64}$/u.test(sourceClosureSha256))
|
|
1301
|
+
continue;
|
|
1302
|
+
const identity = {
|
|
1303
|
+
packageVersion: input.packageVersion,
|
|
1304
|
+
cliSha256: input.cliSha256,
|
|
1305
|
+
sourceClosureSha256,
|
|
1306
|
+
nodeAbi: input.nodeAbi,
|
|
1307
|
+
platform: input.platform,
|
|
1308
|
+
arch: input.arch,
|
|
1309
|
+
};
|
|
1310
|
+
const layout = runtimeLayout(input.home, identity, "fast-reuse", 0);
|
|
1311
|
+
if (await verifyRuntimeFast(layout, identity, input.packageSource, input.additionalPackages)) {
|
|
1312
|
+
return { layout, identity };
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return undefined;
|
|
1316
|
+
}
|
|
1317
|
+
async function verifyRuntimeFast(layout, identity, packageSource, additionalPackages) {
|
|
1318
|
+
try {
|
|
1319
|
+
const home = dirname(dirname(dirname(dirname(dirname(layout.versionAbiDir)))));
|
|
1320
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir)))
|
|
1321
|
+
return false;
|
|
1322
|
+
const [initialRoot, initialProofDir] = await Promise.all([
|
|
1323
|
+
lstat(layout.installRoot, { bigint: true }),
|
|
1324
|
+
lstat(layout.reuseProofDir, { bigint: true }),
|
|
1325
|
+
]);
|
|
1326
|
+
if (!privateRuntimeDirectory(initialRoot) || !privateRuntimeDirectory(initialProofDir))
|
|
1327
|
+
return false;
|
|
1328
|
+
const [markerBytes, proofBytes] = await Promise.all([
|
|
1329
|
+
readPrivateRuntimeMarkerBytes(layout.markerPath),
|
|
1330
|
+
readPrivateRuntimeReuseProofBytes(layout.reuseProofPath),
|
|
1331
|
+
]);
|
|
1332
|
+
if (markerBytes === undefined || proofBytes === undefined)
|
|
1333
|
+
return false;
|
|
1334
|
+
const marker = runtimeMarkerFromJson(parseJson(markerBytes, "managed runtime marker"), identity);
|
|
1335
|
+
if (marker?.schema !== "mono-agent.managed-runtime.v5"
|
|
1336
|
+
|| marker.reuseProofSha256 !== sha256(proofBytes)) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
const proof = runtimeReuseProofFromJson(parseJson(proofBytes, "managed runtime reuse proof"), identity, packageSource, additionalPackages, marker);
|
|
1340
|
+
if (proof === undefined)
|
|
1341
|
+
return false;
|
|
1342
|
+
const [installed, cliStable, manifestStable] = await Promise.all([
|
|
1343
|
+
verifyInstalledPackage(layout, identity),
|
|
1344
|
+
verifyManagedRuntimeCliFingerprint(layout, identity.cliSha256),
|
|
1345
|
+
verifyClosureManifestFingerprint(layout, marker.closureManifestSha256),
|
|
1346
|
+
]);
|
|
1347
|
+
if (!installed
|
|
1348
|
+
|| !cliStable
|
|
1349
|
+
|| !manifestStable
|
|
1350
|
+
|| !(await runtimeReuseProofMatchesManifest(layout, proof.runtime))) {
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
if (!(await verifyReuseProofStats(layout, proof)))
|
|
1354
|
+
return false;
|
|
1355
|
+
if (!(await verifyReuseProofStats(layout, proof)))
|
|
1356
|
+
return false;
|
|
1357
|
+
const [markerBytesAfter, proofBytesAfter, finalRoot, finalProofDir] = await Promise.all([
|
|
1358
|
+
readPrivateRuntimeMarkerBytes(layout.markerPath),
|
|
1359
|
+
readPrivateRuntimeReuseProofBytes(layout.reuseProofPath),
|
|
1360
|
+
lstat(layout.installRoot, { bigint: true }),
|
|
1361
|
+
lstat(layout.reuseProofDir, { bigint: true }),
|
|
1362
|
+
]);
|
|
1363
|
+
return markerBytesAfter !== undefined
|
|
1364
|
+
&& proofBytesAfter !== undefined
|
|
1365
|
+
&& markerBytesAfter.equals(markerBytes)
|
|
1366
|
+
&& proofBytesAfter.equals(proofBytes)
|
|
1367
|
+
&& privateRuntimeDirectory(finalRoot)
|
|
1368
|
+
&& privateRuntimeDirectory(finalProofDir)
|
|
1369
|
+
&& sameSourceStats(initialRoot, finalRoot)
|
|
1370
|
+
&& sameSourceStats(initialProofDir, finalProofDir);
|
|
1371
|
+
}
|
|
1372
|
+
catch {
|
|
1373
|
+
return false;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
async function publishRuntimeReuseProof(layout, identity, packageSource, additionalPackages, source, marker, deps, finalizeLaunchBoundary = false) {
|
|
1377
|
+
if (!(await verifySourceReuseProofStats(source))) {
|
|
1378
|
+
throw new Error("The managed runtime source closure changed before warm-proof publication.");
|
|
1379
|
+
}
|
|
1380
|
+
const runtime = await captureRuntimeClosureReuseProof(layout, marker);
|
|
1381
|
+
const proof = {
|
|
1382
|
+
schema: RUNTIME_REUSE_PROOF_SCHEMA,
|
|
1383
|
+
identity,
|
|
1384
|
+
packageSource,
|
|
1385
|
+
additionalPackages,
|
|
1386
|
+
source,
|
|
1387
|
+
runtime,
|
|
1388
|
+
};
|
|
1389
|
+
const contents = privateJsonContents(proof);
|
|
1390
|
+
const bytes = Buffer.from(contents, "utf8");
|
|
1391
|
+
if (bytes.length > MAX_RUNTIME_REUSE_PROOF_BYTES) {
|
|
1392
|
+
throw new Error("The managed runtime warm-reuse proof exceeds its safe size limit.");
|
|
1393
|
+
}
|
|
1394
|
+
await ensurePrivateReuseProofDirectory(layout.reuseProofDir);
|
|
1395
|
+
await writeFile(layout.reuseProofPath, contents, { encoding: "utf8", mode: 0o600 });
|
|
1396
|
+
await chmod(layout.reuseProofPath, 0o600);
|
|
1397
|
+
const stored = await readPrivateRuntimeReuseProofBytes(layout.reuseProofPath);
|
|
1398
|
+
if (stored === undefined || !stored.equals(bytes)) {
|
|
1399
|
+
throw new Error("The managed runtime warm-reuse proof changed while it was published.");
|
|
1400
|
+
}
|
|
1401
|
+
const nextMarker = {
|
|
1402
|
+
schema: "mono-agent.managed-runtime.v5",
|
|
1403
|
+
packageName: PACKAGE_NAME,
|
|
1404
|
+
packageVersion: identity.packageVersion,
|
|
1405
|
+
cliSha256: identity.cliSha256,
|
|
1406
|
+
sourceClosureSha256: identity.sourceClosureSha256,
|
|
1407
|
+
nodeAbi: identity.nodeAbi,
|
|
1408
|
+
platform: identity.platform,
|
|
1409
|
+
arch: identity.arch,
|
|
1410
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1411
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1412
|
+
reuseProofSha256: sha256(bytes),
|
|
1413
|
+
installedAt: marker.installedAt,
|
|
1414
|
+
};
|
|
1415
|
+
if (finalizeLaunchBoundary) {
|
|
1416
|
+
await writeFinalizedRuntimeMarker(layout.markerPath, nextMarker, deps);
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
await writePrivateJson(layout.markerPath, nextMarker);
|
|
1420
|
+
}
|
|
1421
|
+
if (!(await verifySourceReuseProofStats(source))) {
|
|
1422
|
+
throw new Error("The managed runtime source closure changed during warm-proof publication.");
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async function captureRuntimeClosureReuseProof(layout, marker) {
|
|
1426
|
+
const stored = await fingerprintClosureFile(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
1427
|
+
if (stored.sha256 !== marker.closureManifestSha256) {
|
|
1428
|
+
throw new Error("The managed runtime closure manifest changed before warm-proof capture.");
|
|
1429
|
+
}
|
|
1430
|
+
const manifest = runtimeClosureManifestFromJson(parseJson(stored.bytes, "managed runtime closure manifest"));
|
|
1431
|
+
if (manifest === undefined)
|
|
1432
|
+
throw new Error("The managed runtime closure manifest is malformed.");
|
|
1433
|
+
const excludedRootEntries = new Set([".mono-agent-closure.json", ".mono-agent-runtime.json"]);
|
|
1434
|
+
const rootEntries = (await readdir(layout.installRoot))
|
|
1435
|
+
.filter((name) => !excludedRootEntries.has(name))
|
|
1436
|
+
.sort(compareCodeUnits);
|
|
1437
|
+
const entries = await mapWithConcurrency(manifest.entries, RUNTIME_REUSE_STAT_CONCURRENCY, async (entry) => ({
|
|
1438
|
+
path: entry.path,
|
|
1439
|
+
proof: await captureFilesystemPathProof(runtimePathFromPortableRelative(layout.installRoot, entry.path), entry.path),
|
|
1440
|
+
}));
|
|
1441
|
+
const result = {
|
|
1442
|
+
rootEntries,
|
|
1443
|
+
entries,
|
|
1444
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1445
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1446
|
+
};
|
|
1447
|
+
if (!(await verifyRuntimeReuseProofStats(layout, result))) {
|
|
1448
|
+
throw new Error("The managed runtime closure changed during warm-proof capture.");
|
|
1449
|
+
}
|
|
1450
|
+
return result;
|
|
1451
|
+
}
|
|
1452
|
+
async function verifyReuseProofStats(layout, proof) {
|
|
1453
|
+
const [sourceValid, runtimeValid] = await Promise.all([
|
|
1454
|
+
verifySourceReuseProofStats(proof.source),
|
|
1455
|
+
verifyRuntimeReuseProofStats(layout, proof.runtime),
|
|
1456
|
+
]);
|
|
1457
|
+
return sourceValid && runtimeValid;
|
|
1458
|
+
}
|
|
1459
|
+
async function verifySourceReuseProofStats(proof) {
|
|
1460
|
+
try {
|
|
1461
|
+
const targets = [
|
|
1462
|
+
...proof.packages.flatMap((pkg) => pkg.entries.map((entry) => ({
|
|
1463
|
+
path: entry.path === "."
|
|
1464
|
+
? pkg.sourceRoot
|
|
1465
|
+
: runtimePathFromPortableRelative(pkg.sourceRoot, entry.path),
|
|
1466
|
+
logicalPath: entry.path,
|
|
1467
|
+
proof: entry,
|
|
1468
|
+
}))),
|
|
1469
|
+
...proof.links,
|
|
1470
|
+
];
|
|
1471
|
+
const matches = await mapWithConcurrency(targets, RUNTIME_REUSE_STAT_CONCURRENCY, async (target) => {
|
|
1472
|
+
const current = await captureFilesystemPathProof(target.path, target.logicalPath);
|
|
1473
|
+
return sameSourceProofEntry(target.proof, current);
|
|
1474
|
+
});
|
|
1475
|
+
return matches.every(Boolean);
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async function verifyRuntimeReuseProofStats(layout, proof) {
|
|
1482
|
+
try {
|
|
1483
|
+
const excludedRootEntries = new Set([".mono-agent-closure.json", ".mono-agent-runtime.json"]);
|
|
1484
|
+
const rootEntries = (await readdir(layout.installRoot))
|
|
1485
|
+
.filter((name) => !excludedRootEntries.has(name))
|
|
1486
|
+
.sort(compareCodeUnits);
|
|
1487
|
+
if (JSON.stringify(rootEntries) !== JSON.stringify(proof.rootEntries))
|
|
1488
|
+
return false;
|
|
1489
|
+
const matches = await mapWithConcurrency(proof.entries, RUNTIME_REUSE_STAT_CONCURRENCY, async (entry) => {
|
|
1490
|
+
const current = await captureFilesystemPathProof(runtimePathFromPortableRelative(layout.installRoot, entry.path), entry.path);
|
|
1491
|
+
return sameSourceProofEntry(entry.proof, current);
|
|
1492
|
+
});
|
|
1493
|
+
return matches.every(Boolean);
|
|
1494
|
+
}
|
|
1495
|
+
catch {
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
async function runtimeReuseProofMatchesManifest(layout, proof) {
|
|
1500
|
+
try {
|
|
1501
|
+
const bytes = await readFile(layout.closureManifestPath);
|
|
1502
|
+
if (sha256(bytes) !== proof.closureManifestSha256)
|
|
1503
|
+
return false;
|
|
1504
|
+
const manifest = runtimeClosureManifestFromJson(parseJson(bytes, "managed runtime closure manifest"));
|
|
1505
|
+
if (manifest === undefined || manifest.entries.length !== proof.entries.length)
|
|
1506
|
+
return false;
|
|
1507
|
+
return manifest.entries.every((entry, index) => {
|
|
1508
|
+
const proven = proof.entries[index];
|
|
1509
|
+
return proven !== undefined
|
|
1510
|
+
&& proven.path === entry.path
|
|
1511
|
+
&& proven.proof.type === entry.type
|
|
1512
|
+
&& fileMode(BigInt(proven.proof.mode)) === entry.mode
|
|
1513
|
+
&& (entry.type !== "symlink" || proven.proof.target === entry.target);
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
catch {
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
async function ensurePrivateReuseProofDirectory(path) {
|
|
1521
|
+
await mkdir(path, { recursive: false, mode: 0o700 }).catch((error) => {
|
|
1522
|
+
if (!isErrno(error, "EEXIST"))
|
|
1523
|
+
throw error;
|
|
1524
|
+
});
|
|
1525
|
+
const details = await lstat(path, { bigint: true });
|
|
1526
|
+
if (!privateRuntimeDirectory(details)) {
|
|
1527
|
+
throw new Error(`Managed runtime reuse-proof directory ${path} must be owner-private.`);
|
|
1528
|
+
}
|
|
1529
|
+
await chmod(path, 0o700);
|
|
1530
|
+
}
|
|
1531
|
+
async function readPrivateRuntimeReuseProofBytes(path) {
|
|
1532
|
+
let handle;
|
|
1533
|
+
try {
|
|
1534
|
+
handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
1535
|
+
const before = await handle.stat({ bigint: true });
|
|
1536
|
+
if (!privateRuntimeReuseProofFile(before))
|
|
1537
|
+
return undefined;
|
|
1538
|
+
const bytes = await handle.readFile();
|
|
1539
|
+
const [after, named] = await Promise.all([
|
|
1540
|
+
handle.stat({ bigint: true }),
|
|
1541
|
+
lstat(path, { bigint: true }),
|
|
1542
|
+
]);
|
|
1543
|
+
if (!privateRuntimeReuseProofFile(after)
|
|
1544
|
+
|| !privateRuntimeReuseProofFile(named)
|
|
1545
|
+
|| !sameSourceStats(before, after)
|
|
1546
|
+
|| !sameSourceStats(before, named)) {
|
|
1547
|
+
return undefined;
|
|
1548
|
+
}
|
|
1549
|
+
return bytes;
|
|
1550
|
+
}
|
|
1551
|
+
catch {
|
|
1552
|
+
return undefined;
|
|
1553
|
+
}
|
|
1554
|
+
finally {
|
|
1555
|
+
await handle?.close();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
function privateRuntimeReuseProofFile(details) {
|
|
1559
|
+
const owned = typeof process.getuid !== "function" || details.uid === BigInt(process.getuid());
|
|
1560
|
+
return details.isFile()
|
|
1561
|
+
&& !details.isSymbolicLink()
|
|
1562
|
+
&& details.nlink === 1n
|
|
1563
|
+
&& details.size <= BigInt(MAX_RUNTIME_REUSE_PROOF_BYTES)
|
|
1564
|
+
&& (details.mode & 63n) === 0n
|
|
1565
|
+
&& owned;
|
|
1566
|
+
}
|
|
1567
|
+
function runtimeReuseProofFromJson(value, identity, packageSource, additionalPackages, marker) {
|
|
1568
|
+
if (!hasExactKeys(value, ["schema", "identity", "packageSource", "additionalPackages", "source", "runtime"])) {
|
|
1569
|
+
return undefined;
|
|
1570
|
+
}
|
|
1571
|
+
if (value.schema !== RUNTIME_REUSE_PROOF_SCHEMA
|
|
1572
|
+
|| !sameRuntimeIdentityJson(value.identity, identity)
|
|
1573
|
+
|| value.packageSource !== packageSource
|
|
1574
|
+
|| !Array.isArray(value.additionalPackages)
|
|
1575
|
+
|| JSON.stringify(value.additionalPackages) !== JSON.stringify(additionalPackages)) {
|
|
1576
|
+
return undefined;
|
|
1577
|
+
}
|
|
1578
|
+
const source = sourceClosureReuseProofFromJson(value.source, packageSource);
|
|
1579
|
+
const runtime = runtimeClosureReuseProofFromJson(value.runtime, marker);
|
|
1580
|
+
return source === undefined || runtime === undefined
|
|
1581
|
+
? undefined
|
|
1582
|
+
: {
|
|
1583
|
+
schema: RUNTIME_REUSE_PROOF_SCHEMA,
|
|
1584
|
+
identity,
|
|
1585
|
+
packageSource,
|
|
1586
|
+
additionalPackages,
|
|
1587
|
+
source,
|
|
1588
|
+
runtime,
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function sourceClosureReuseProofFromJson(value, packageSource) {
|
|
1592
|
+
if (!hasExactKeys(value, ["sourceProofSha256", "packages", "links"]))
|
|
1593
|
+
return undefined;
|
|
1594
|
+
if (typeof value.sourceProofSha256 !== "string"
|
|
1595
|
+
|| !/^[0-9a-f]{64}$/u.test(value.sourceProofSha256)
|
|
1596
|
+
|| !Array.isArray(value.packages)
|
|
1597
|
+
|| !Array.isArray(value.links)) {
|
|
1598
|
+
return undefined;
|
|
1599
|
+
}
|
|
1600
|
+
const packages = [];
|
|
1601
|
+
const roots = new Set();
|
|
1602
|
+
for (const [index, rawPackage] of value.packages.entries()) {
|
|
1603
|
+
if (!hasExactKeys(rawPackage, ["id", "sourceRoot", "entries"]))
|
|
1604
|
+
return undefined;
|
|
1605
|
+
if (rawPackage.id !== index
|
|
1606
|
+
|| typeof rawPackage.sourceRoot !== "string"
|
|
1607
|
+
|| !isCanonicalAbsolutePath(rawPackage.sourceRoot)
|
|
1608
|
+
|| roots.has(rawPackage.sourceRoot)
|
|
1609
|
+
|| !Array.isArray(rawPackage.entries)) {
|
|
1610
|
+
return undefined;
|
|
1611
|
+
}
|
|
1612
|
+
const entries = proofEntriesFromJson(rawPackage.entries, true);
|
|
1613
|
+
if (entries === undefined || entries[0]?.path !== ".")
|
|
1614
|
+
return undefined;
|
|
1615
|
+
roots.add(rawPackage.sourceRoot);
|
|
1616
|
+
packages.push({ id: index, sourceRoot: rawPackage.sourceRoot, entries });
|
|
1617
|
+
}
|
|
1618
|
+
if (packages[0]?.sourceRoot !== packageSource)
|
|
1619
|
+
return undefined;
|
|
1620
|
+
const links = [];
|
|
1621
|
+
const linkPaths = new Set();
|
|
1622
|
+
for (const rawLink of value.links) {
|
|
1623
|
+
if (!hasExactKeys(rawLink, ["path", "logicalPath", "proof"]))
|
|
1624
|
+
return undefined;
|
|
1625
|
+
if (typeof rawLink.path !== "string"
|
|
1626
|
+
|| !isCanonicalAbsolutePath(rawLink.path)
|
|
1627
|
+
|| linkPaths.has(rawLink.path)
|
|
1628
|
+
|| typeof rawLink.logicalPath !== "string"
|
|
1629
|
+
|| rawLink.logicalPath.length === 0) {
|
|
1630
|
+
return undefined;
|
|
1631
|
+
}
|
|
1632
|
+
const proof = sourceProofEntryFromJson(rawLink.proof);
|
|
1633
|
+
if (proof === undefined || proof.path !== rawLink.logicalPath)
|
|
1634
|
+
return undefined;
|
|
1635
|
+
linkPaths.add(rawLink.path);
|
|
1636
|
+
links.push({ path: rawLink.path, logicalPath: rawLink.logicalPath, proof });
|
|
1637
|
+
}
|
|
1638
|
+
return { sourceProofSha256: value.sourceProofSha256, packages, links };
|
|
1639
|
+
}
|
|
1640
|
+
function runtimeClosureReuseProofFromJson(value, marker) {
|
|
1641
|
+
if (!hasExactKeys(value, [
|
|
1642
|
+
"rootEntries",
|
|
1643
|
+
"entries",
|
|
1644
|
+
"closureManifestSha256",
|
|
1645
|
+
"executionProofSha256",
|
|
1646
|
+
]))
|
|
1647
|
+
return undefined;
|
|
1648
|
+
if (value.closureManifestSha256 !== marker.closureManifestSha256
|
|
1649
|
+
|| value.executionProofSha256 !== marker.executionProofSha256
|
|
1650
|
+
|| !Array.isArray(value.rootEntries)
|
|
1651
|
+
|| !Array.isArray(value.entries)) {
|
|
1652
|
+
return undefined;
|
|
1653
|
+
}
|
|
1654
|
+
const rootEntries = [];
|
|
1655
|
+
const rootNames = new Set();
|
|
1656
|
+
for (const name of value.rootEntries) {
|
|
1657
|
+
if (typeof name !== "string"
|
|
1658
|
+
|| name.length === 0
|
|
1659
|
+
|| name === "."
|
|
1660
|
+
|| name === ".."
|
|
1661
|
+
|| name.includes("/")
|
|
1662
|
+
|| name.includes("\\")
|
|
1663
|
+
|| name.includes("\0")
|
|
1664
|
+
|| rootNames.has(name)
|
|
1665
|
+
|| name === ".mono-agent-closure.json"
|
|
1666
|
+
|| name === ".mono-agent-runtime.json") {
|
|
1667
|
+
return undefined;
|
|
1668
|
+
}
|
|
1669
|
+
rootNames.add(name);
|
|
1670
|
+
rootEntries.push(name);
|
|
1671
|
+
}
|
|
1672
|
+
if (JSON.stringify(rootEntries) !== JSON.stringify([...rootEntries].sort(compareCodeUnits)))
|
|
1673
|
+
return undefined;
|
|
1674
|
+
const entries = [];
|
|
1675
|
+
const entryPaths = new Set();
|
|
1676
|
+
for (const rawEntry of value.entries) {
|
|
1677
|
+
if (!hasExactKeys(rawEntry, ["path", "proof"]))
|
|
1678
|
+
return undefined;
|
|
1679
|
+
if (typeof rawEntry.path !== "string"
|
|
1680
|
+
|| !isSafePortableRelativePath(rawEntry.path)
|
|
1681
|
+
|| entryPaths.has(rawEntry.path)) {
|
|
1682
|
+
return undefined;
|
|
1683
|
+
}
|
|
1684
|
+
const proof = sourceProofEntryFromJson(rawEntry.proof);
|
|
1685
|
+
if (proof === undefined || proof.path !== rawEntry.path)
|
|
1686
|
+
return undefined;
|
|
1687
|
+
entryPaths.add(rawEntry.path);
|
|
1688
|
+
entries.push({ path: rawEntry.path, proof });
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
rootEntries,
|
|
1692
|
+
entries,
|
|
1693
|
+
closureManifestSha256: marker.closureManifestSha256,
|
|
1694
|
+
executionProofSha256: marker.executionProofSha256,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
function runtimeClosureManifestFromJson(value) {
|
|
1698
|
+
if (!hasExactKeys(value, ["schema", "entries"]) || value.schema !== "mono-agent.execution-closure.v1") {
|
|
1699
|
+
return undefined;
|
|
1700
|
+
}
|
|
1701
|
+
if (!Array.isArray(value.entries))
|
|
1702
|
+
return undefined;
|
|
1703
|
+
const entries = [];
|
|
1704
|
+
const paths = new Set();
|
|
1705
|
+
for (const rawEntry of value.entries) {
|
|
1706
|
+
if (!isRecord(rawEntry) || typeof rawEntry.type !== "string")
|
|
1707
|
+
return undefined;
|
|
1708
|
+
const expectedKeys = rawEntry.type === "file"
|
|
1709
|
+
? ["path", "type", "mode", "sha256"]
|
|
1710
|
+
: rawEntry.type === "symlink"
|
|
1711
|
+
? ["path", "type", "mode", "target"]
|
|
1712
|
+
: ["path", "type", "mode"];
|
|
1713
|
+
if (!hasExactKeys(rawEntry, expectedKeys)
|
|
1714
|
+
|| typeof rawEntry.path !== "string"
|
|
1715
|
+
|| !isSafePortableRelativePath(rawEntry.path)
|
|
1716
|
+
|| paths.has(rawEntry.path)
|
|
1717
|
+
|| typeof rawEntry.mode !== "string"
|
|
1718
|
+
|| !/^0[0-7]{3}$/u.test(rawEntry.mode)) {
|
|
1719
|
+
return undefined;
|
|
1720
|
+
}
|
|
1721
|
+
if (rawEntry.type === "file") {
|
|
1722
|
+
if (typeof rawEntry.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(rawEntry.sha256))
|
|
1723
|
+
return undefined;
|
|
1724
|
+
entries.push({ path: rawEntry.path, type: "file", mode: rawEntry.mode, sha256: rawEntry.sha256 });
|
|
1725
|
+
}
|
|
1726
|
+
else if (rawEntry.type === "symlink") {
|
|
1727
|
+
if (typeof rawEntry.target !== "string" || rawEntry.target.includes("\0"))
|
|
1728
|
+
return undefined;
|
|
1729
|
+
entries.push({ path: rawEntry.path, type: "symlink", mode: rawEntry.mode, target: rawEntry.target });
|
|
1730
|
+
}
|
|
1731
|
+
else if (rawEntry.type === "directory") {
|
|
1732
|
+
entries.push({ path: rawEntry.path, type: "directory", mode: rawEntry.mode });
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
return undefined;
|
|
1736
|
+
}
|
|
1737
|
+
paths.add(rawEntry.path);
|
|
1738
|
+
}
|
|
1739
|
+
return { schema: "mono-agent.execution-closure.v1", entries };
|
|
1740
|
+
}
|
|
1741
|
+
function proofEntriesFromJson(values, allowRoot) {
|
|
1742
|
+
const entries = [];
|
|
1743
|
+
const paths = new Set();
|
|
1744
|
+
for (const value of values) {
|
|
1745
|
+
const entry = sourceProofEntryFromJson(value);
|
|
1746
|
+
if (entry === undefined
|
|
1747
|
+
|| (entry.path !== "." && !isSafePortableRelativePath(entry.path))
|
|
1748
|
+
|| (!allowRoot && entry.path === ".")
|
|
1749
|
+
|| paths.has(entry.path)) {
|
|
1750
|
+
return undefined;
|
|
1751
|
+
}
|
|
1752
|
+
paths.add(entry.path);
|
|
1753
|
+
entries.push(entry);
|
|
1754
|
+
}
|
|
1755
|
+
return entries;
|
|
1756
|
+
}
|
|
1757
|
+
function sourceProofEntryFromJson(value) {
|
|
1758
|
+
if (!isRecord(value))
|
|
1759
|
+
return undefined;
|
|
1760
|
+
const type = value.type;
|
|
1761
|
+
const expectedKeys = type === "symlink"
|
|
1762
|
+
? ["path", "type", "dev", "ino", "nlink", "mode", "size", "mtimeNs", "ctimeNs", "target"]
|
|
1763
|
+
: ["path", "type", "dev", "ino", "nlink", "mode", "size", "mtimeNs", "ctimeNs"];
|
|
1764
|
+
if (!hasExactKeys(value, expectedKeys)
|
|
1765
|
+
|| typeof value.path !== "string"
|
|
1766
|
+
|| (type !== "directory" && type !== "file" && type !== "symlink")
|
|
1767
|
+
|| ![value.dev, value.ino, value.nlink, value.mode, value.size, value.mtimeNs, value.ctimeNs]
|
|
1768
|
+
.every((entry) => typeof entry === "string" && /^-?\d+$/u.test(entry))
|
|
1769
|
+
|| (type === "symlink" && (typeof value.target !== "string" || value.target.includes("\0")))) {
|
|
1770
|
+
return undefined;
|
|
1771
|
+
}
|
|
1772
|
+
return value;
|
|
1773
|
+
}
|
|
1774
|
+
function sameRuntimeIdentityJson(value, identity) {
|
|
1775
|
+
return hasExactKeys(value, ["packageVersion", "cliSha256", "sourceClosureSha256", "nodeAbi", "platform", "arch"])
|
|
1776
|
+
&& value.packageVersion === identity.packageVersion
|
|
1777
|
+
&& value.cliSha256 === identity.cliSha256
|
|
1778
|
+
&& value.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1779
|
+
&& value.nodeAbi === identity.nodeAbi
|
|
1780
|
+
&& value.platform === identity.platform
|
|
1781
|
+
&& value.arch === identity.arch;
|
|
1782
|
+
}
|
|
1783
|
+
function hasExactKeys(value, keys) {
|
|
1784
|
+
return isRecord(value)
|
|
1785
|
+
&& Object.keys(value).length === keys.length
|
|
1786
|
+
&& keys.every((key) => Object.hasOwn(value, key));
|
|
1787
|
+
}
|
|
1788
|
+
function isCanonicalAbsolutePath(path) {
|
|
1789
|
+
return isAbsolute(path) && resolve(path) === path && !path.includes("\0");
|
|
1790
|
+
}
|
|
1791
|
+
function isSafePortableRelativePath(path) {
|
|
1792
|
+
if (path.length === 0 || path === "." || path.includes("\\") || path.includes("\0") || isAbsolute(path)) {
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
const segments = path.split("/");
|
|
1796
|
+
return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
1797
|
+
}
|
|
1798
|
+
function runtimePathFromPortableRelative(root, path) {
|
|
1799
|
+
if (!isSafePortableRelativePath(path)) {
|
|
1800
|
+
throw new Error(`Managed runtime proof path ${JSON.stringify(path)} is unsafe.`);
|
|
1801
|
+
}
|
|
1802
|
+
const target = join(root, ...path.split("/"));
|
|
1803
|
+
assertPathInsideRoot(root, target, `proof path ${path}`);
|
|
1804
|
+
return target;
|
|
1805
|
+
}
|
|
1806
|
+
async function mapWithConcurrency(values, concurrency, mapper) {
|
|
1807
|
+
const results = new Array(values.length);
|
|
1808
|
+
let nextIndex = 0;
|
|
1809
|
+
const workers = Array.from({ length: Math.min(Math.max(1, concurrency), values.length) }, async () => {
|
|
1810
|
+
for (;;) {
|
|
1811
|
+
const index = nextIndex;
|
|
1812
|
+
nextIndex += 1;
|
|
1813
|
+
if (index >= values.length)
|
|
1814
|
+
return;
|
|
1815
|
+
results[index] = await mapper(values[index], index);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
await Promise.all(workers);
|
|
1819
|
+
return results;
|
|
1820
|
+
}
|
|
1821
|
+
async function pathExists(path) {
|
|
1822
|
+
try {
|
|
1823
|
+
await lstat(path);
|
|
1824
|
+
return true;
|
|
1825
|
+
}
|
|
1826
|
+
catch (error) {
|
|
1827
|
+
if (isErrno(error, "ENOENT"))
|
|
1828
|
+
return false;
|
|
1829
|
+
throw error;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
async function acquireRuntimeLock(layout, identity, additionalPackages, deps, isSatisfied = () => verifyRuntime(layout, identity, additionalPackages)) {
|
|
1087
1833
|
const incarnation = await (deps.currentProcessIncarnation ?? currentProcessIncarnation)();
|
|
1088
1834
|
return acquireOwnerPrivateLock({
|
|
1089
1835
|
path: layout.lockDir,
|
|
@@ -1110,7 +1856,7 @@ async function acquireRuntimeLock(layout, identity, additionalPackages, deps) {
|
|
|
1110
1856
|
: undefined;
|
|
1111
1857
|
},
|
|
1112
1858
|
invalidOwner: "ownerless",
|
|
1113
|
-
isSatisfied
|
|
1859
|
+
isSatisfied,
|
|
1114
1860
|
sleep: deps.sleep,
|
|
1115
1861
|
staleRace: "return",
|
|
1116
1862
|
beforeStaleRename: async () => {
|
|
@@ -1231,7 +1977,22 @@ function sameRuntimeMarker(left, right) {
|
|
|
1231
1977
|
&& left.nodeAbi === right.nodeAbi
|
|
1232
1978
|
&& left.platform === right.platform
|
|
1233
1979
|
&& left.arch === right.arch
|
|
1234
|
-
&& left.installedAt === right.installedAt
|
|
1980
|
+
&& left.installedAt === right.installedAt
|
|
1981
|
+
&& (left.schema !== "mono-agent.managed-runtime.v5"
|
|
1982
|
+
|| (right.schema === "mono-agent.managed-runtime.v5"
|
|
1983
|
+
&& left.reuseProofSha256 === right.reuseProofSha256));
|
|
1984
|
+
}
|
|
1985
|
+
function runtimeMarkerMatchesProvenance(marker, input) {
|
|
1986
|
+
return marker.packageName === PACKAGE_NAME
|
|
1987
|
+
&& marker.closureManifestSha256 === input.closureManifestSha256
|
|
1988
|
+
&& marker.executionProofSha256 === input.executionProofSha256
|
|
1989
|
+
&& marker.packageVersion === input.packageVersion
|
|
1990
|
+
&& marker.cliSha256 === input.cliSha256
|
|
1991
|
+
&& marker.sourceClosureSha256 === input.sourceClosureSha256
|
|
1992
|
+
&& marker.nodeAbi === input.nodeAbi
|
|
1993
|
+
&& marker.platform === input.platform
|
|
1994
|
+
&& marker.arch === input.arch
|
|
1995
|
+
&& marker.installedAt === input.installedAt;
|
|
1235
1996
|
}
|
|
1236
1997
|
async function verifiedRuntimeMarker(layout, identity) {
|
|
1237
1998
|
try {
|
|
@@ -1250,6 +2011,17 @@ async function verifiedRuntimeMarker(layout, identity) {
|
|
|
1250
2011
|
}
|
|
1251
2012
|
}
|
|
1252
2013
|
async function readRuntimeMarker(path, identity) {
|
|
2014
|
+
const bytes = await readPrivateRuntimeMarkerBytes(path);
|
|
2015
|
+
if (bytes === undefined)
|
|
2016
|
+
return undefined;
|
|
2017
|
+
try {
|
|
2018
|
+
return runtimeMarkerFromJson(JSON.parse(bytes.toString("utf8")), identity);
|
|
2019
|
+
}
|
|
2020
|
+
catch {
|
|
2021
|
+
return undefined;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
async function readPrivateRuntimeMarkerBytes(path) {
|
|
1253
2025
|
let handle;
|
|
1254
2026
|
try {
|
|
1255
2027
|
handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
@@ -1267,7 +2039,7 @@ async function readRuntimeMarker(path, identity) {
|
|
|
1267
2039
|
|| !sameSourceStats(before, named)) {
|
|
1268
2040
|
return undefined;
|
|
1269
2041
|
}
|
|
1270
|
-
return
|
|
2042
|
+
return bytes;
|
|
1271
2043
|
}
|
|
1272
2044
|
catch {
|
|
1273
2045
|
return undefined;
|
|
@@ -1285,10 +2057,17 @@ function privateRuntimeMarkerFile(details) {
|
|
|
1285
2057
|
&& (details.mode & 63n) === 0n
|
|
1286
2058
|
&& owned;
|
|
1287
2059
|
}
|
|
2060
|
+
function privateRuntimeDirectory(details) {
|
|
2061
|
+
const owned = typeof process.getuid !== "function" || details.uid === BigInt(process.getuid());
|
|
2062
|
+
return details.isDirectory()
|
|
2063
|
+
&& !details.isSymbolicLink()
|
|
2064
|
+
&& (details.mode & 63n) === 0n
|
|
2065
|
+
&& owned;
|
|
2066
|
+
}
|
|
1288
2067
|
function runtimeMarkerFromJson(value, identity) {
|
|
1289
2068
|
if (!isRecord(value))
|
|
1290
2069
|
return undefined;
|
|
1291
|
-
const
|
|
2070
|
+
const commonKeys = [
|
|
1292
2071
|
"schema",
|
|
1293
2072
|
"packageName",
|
|
1294
2073
|
"closureManifestSha256",
|
|
@@ -1301,10 +2080,13 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1301
2080
|
"arch",
|
|
1302
2081
|
"installedAt",
|
|
1303
2082
|
];
|
|
2083
|
+
const exactKeys = value.schema === "mono-agent.managed-runtime.v5"
|
|
2084
|
+
? [...commonKeys, "reuseProofSha256"]
|
|
2085
|
+
: commonKeys;
|
|
1304
2086
|
const installedAtMs = typeof value.installedAt === "string" ? Date.parse(value.installedAt) : Number.NaN;
|
|
1305
2087
|
const valid = Object.keys(value).length === exactKeys.length
|
|
1306
2088
|
&& exactKeys.every((key) => Object.hasOwn(value, key))
|
|
1307
|
-
&& value.schema === "mono-agent.managed-runtime.v4"
|
|
2089
|
+
&& (value.schema === "mono-agent.managed-runtime.v4" || value.schema === "mono-agent.managed-runtime.v5")
|
|
1308
2090
|
&& value.packageName === PACKAGE_NAME
|
|
1309
2091
|
&& value.packageVersion === identity.packageVersion
|
|
1310
2092
|
&& value.cliSha256 === identity.cliSha256
|
|
@@ -1316,6 +2098,8 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1316
2098
|
&& /^[0-9a-f]{64}$/u.test(value.closureManifestSha256)
|
|
1317
2099
|
&& typeof value.executionProofSha256 === "string"
|
|
1318
2100
|
&& /^[0-9a-f]{64}$/u.test(value.executionProofSha256)
|
|
2101
|
+
&& (value.schema !== "mono-agent.managed-runtime.v5"
|
|
2102
|
+
|| (typeof value.reuseProofSha256 === "string" && /^[0-9a-f]{64}$/u.test(value.reuseProofSha256)))
|
|
1319
2103
|
&& typeof value.installedAt === "string"
|
|
1320
2104
|
&& Number.isFinite(installedAtMs)
|
|
1321
2105
|
&& new Date(installedAtMs).toISOString() === value.installedAt
|
|
@@ -1325,6 +2109,32 @@ function runtimeMarkerFromJson(value, identity) {
|
|
|
1325
2109
|
&& value.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT;
|
|
1326
2110
|
return valid ? value : undefined;
|
|
1327
2111
|
}
|
|
2112
|
+
function runtimeIdentityFromMarkerJson(value) {
|
|
2113
|
+
if (!isRecord(value))
|
|
2114
|
+
return undefined;
|
|
2115
|
+
if (typeof value.packageVersion !== "string"
|
|
2116
|
+
|| !isExactVersion(value.packageVersion)
|
|
2117
|
+
|| typeof value.cliSha256 !== "string"
|
|
2118
|
+
|| !/^[0-9a-f]{64}$/u.test(value.cliSha256)
|
|
2119
|
+
|| typeof value.sourceClosureSha256 !== "string"
|
|
2120
|
+
|| !/^[0-9a-f]{64}$/u.test(value.sourceClosureSha256)
|
|
2121
|
+
|| typeof value.nodeAbi !== "string"
|
|
2122
|
+
|| value.nodeAbi.length === 0
|
|
2123
|
+
|| typeof value.platform !== "string"
|
|
2124
|
+
|| value.platform.length === 0
|
|
2125
|
+
|| typeof value.arch !== "string"
|
|
2126
|
+
|| value.arch.length === 0) {
|
|
2127
|
+
return undefined;
|
|
2128
|
+
}
|
|
2129
|
+
return {
|
|
2130
|
+
packageVersion: value.packageVersion,
|
|
2131
|
+
cliSha256: value.cliSha256,
|
|
2132
|
+
sourceClosureSha256: value.sourceClosureSha256,
|
|
2133
|
+
nodeAbi: value.nodeAbi,
|
|
2134
|
+
platform: value.platform,
|
|
2135
|
+
arch: value.arch,
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
1328
2138
|
async function verifyInstalledPackage(layout, identity) {
|
|
1329
2139
|
try {
|
|
1330
2140
|
const packageRoot = dirname(layout.packageJsonPath);
|
|
@@ -1386,6 +2196,28 @@ async function verifyClosureManifest(layout, expectedSha256, deps = {}) {
|
|
|
1386
2196
|
return false;
|
|
1387
2197
|
}
|
|
1388
2198
|
}
|
|
2199
|
+
async function verifyClosureManifestFingerprint(layout, expectedSha256) {
|
|
2200
|
+
try {
|
|
2201
|
+
await assertRegularFile(layout.closureManifestPath, "managed runtime closure manifest");
|
|
2202
|
+
const stored = await fingerprintClosureFile(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
2203
|
+
if (stored.sha256 !== expectedSha256)
|
|
2204
|
+
return false;
|
|
2205
|
+
const parsed = JSON.parse(stored.bytes.toString("utf8"));
|
|
2206
|
+
return parsed.schema === "mono-agent.execution-closure.v1" && Array.isArray(parsed.entries);
|
|
2207
|
+
}
|
|
2208
|
+
catch {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
async function verifyManagedRuntimeCliFingerprint(layout, expectedSha256) {
|
|
2213
|
+
try {
|
|
2214
|
+
const captured = await fingerprintClosureFile(layout.cliPath, "node_modules/@mono-agent/agent-app/dist/cli.js");
|
|
2215
|
+
return captured.sha256 === expectedSha256;
|
|
2216
|
+
}
|
|
2217
|
+
catch {
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
1389
2221
|
async function captureRuntimeClosureManifest(layout, deps = {}) {
|
|
1390
2222
|
const entries = [];
|
|
1391
2223
|
const proofs = [];
|
|
@@ -1544,18 +2376,21 @@ async function writeFinalizedRuntimeMarker(path, marker, deps) {
|
|
|
1544
2376
|
throw new Error("Could not publish a stable managed runtime launch boundary.");
|
|
1545
2377
|
}
|
|
1546
2378
|
async function waitForManagedRuntimeLaunchBoundary(layout, identity, deps) {
|
|
1547
|
-
const marker = await
|
|
2379
|
+
const marker = await readRuntimeMarker(layout.markerPath, identity);
|
|
1548
2380
|
if (marker === undefined) {
|
|
1549
2381
|
throw new Error("The managed runtime marker became invalid before launch.");
|
|
1550
2382
|
}
|
|
1551
|
-
|
|
2383
|
+
await waitForLaunchBoundary(marker.installedAt, deps.now, deps.sleep);
|
|
2384
|
+
}
|
|
2385
|
+
async function waitForLaunchBoundary(installedAt, now, sleepFor) {
|
|
2386
|
+
const boundaryMs = Date.parse(installedAt);
|
|
1552
2387
|
const safeStartMs = (Math.floor(boundaryMs / 1_000) + 1) * 1_000;
|
|
1553
|
-
const nowMs =
|
|
2388
|
+
const nowMs = now();
|
|
1554
2389
|
if (safeStartMs - nowMs > 5_000) {
|
|
1555
2390
|
throw new Error("The managed runtime launch boundary is implausibly far in the future.");
|
|
1556
2391
|
}
|
|
1557
2392
|
if (nowMs < safeStartMs)
|
|
1558
|
-
await
|
|
2393
|
+
await sleepFor(safeStartMs - nowMs);
|
|
1559
2394
|
}
|
|
1560
2395
|
function privateJsonContents(value) {
|
|
1561
2396
|
return `${JSON.stringify(value, undefined, 2)}\n`;
|
|
@@ -1611,7 +2446,15 @@ function ownedPrivately(details) {
|
|
|
1611
2446
|
const owned = typeof process.getuid !== "function" || Number(details.uid) === process.getuid();
|
|
1612
2447
|
return owned && (Number(details.mode) & 0o077) === 0;
|
|
1613
2448
|
}
|
|
1614
|
-
function runtimeResult(layout, identity, nodePath) {
|
|
2449
|
+
async function runtimeResult(layout, identity, nodePath, verificationMode) {
|
|
2450
|
+
const markerBytes = await readPrivateRuntimeMarkerBytes(layout.markerPath);
|
|
2451
|
+
if (markerBytes === undefined) {
|
|
2452
|
+
throw new Error("The managed runtime marker became unsafe before launch proof publication.");
|
|
2453
|
+
}
|
|
2454
|
+
const marker = runtimeMarkerFromJson(parseJson(markerBytes, "managed runtime marker"), identity);
|
|
2455
|
+
if (marker === undefined) {
|
|
2456
|
+
throw new Error("The managed runtime marker became invalid before launch proof publication.");
|
|
2457
|
+
}
|
|
1615
2458
|
return {
|
|
1616
2459
|
cliPath: layout.cliPath,
|
|
1617
2460
|
nodePath: resolve(nodePath),
|
|
@@ -1619,8 +2462,17 @@ function runtimeResult(layout, identity, nodePath) {
|
|
|
1619
2462
|
packageVersion: identity.packageVersion,
|
|
1620
2463
|
cliSha256: identity.cliSha256,
|
|
1621
2464
|
nodeAbi: identity.nodeAbi,
|
|
2465
|
+
verificationMode,
|
|
2466
|
+
launchProof: encodeManagedRuntimeLaunchProof({
|
|
2467
|
+
schema: MANAGED_RUNTIME_LAUNCH_PROOF_SCHEMA,
|
|
2468
|
+
markerSha256: sha256(markerBytes),
|
|
2469
|
+
installedAt: marker.installedAt,
|
|
2470
|
+
}),
|
|
1622
2471
|
};
|
|
1623
2472
|
}
|
|
2473
|
+
async function sleep(milliseconds) {
|
|
2474
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
2475
|
+
}
|
|
1624
2476
|
function requiredNodeAbi() {
|
|
1625
2477
|
const abi = process.versions.modules;
|
|
1626
2478
|
if (abi === undefined || abi.trim().length === 0) {
|