@mono-agent/agent-app 0.11.2 → 0.11.4
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/LICENSE +674 -0
- package/README.md +222 -26
- package/dist/adapter-send-tools-main.js +7 -1
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +19 -1
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +127 -13
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +15 -2
- package/dist/app-config.js.map +1 -1
- package/dist/app-controller-channels.d.ts +8 -0
- package/dist/app-controller-channels.d.ts.map +1 -0
- package/dist/app-controller-channels.js +207 -0
- package/dist/app-controller-channels.js.map +1 -0
- package/dist/app-controller-continuation.d.ts +26 -0
- package/dist/app-controller-continuation.d.ts.map +1 -0
- package/dist/app-controller-continuation.js +198 -0
- package/dist/app-controller-continuation.js.map +1 -0
- package/dist/app-controller-lifecycle.d.ts +6 -0
- package/dist/app-controller-lifecycle.d.ts.map +1 -0
- package/dist/app-controller-lifecycle.js +81 -0
- package/dist/app-controller-lifecycle.js.map +1 -0
- package/dist/app-controller-maintenance.d.ts +13 -0
- package/dist/app-controller-maintenance.d.ts.map +1 -0
- package/dist/app-controller-maintenance.js +180 -0
- package/dist/app-controller-maintenance.js.map +1 -0
- package/dist/app-controller-memory-health.d.ts +12 -0
- package/dist/app-controller-memory-health.d.ts.map +1 -0
- package/dist/app-controller-memory-health.js +160 -0
- package/dist/app-controller-memory-health.js.map +1 -0
- package/dist/app-controller-memory.d.ts +9 -0
- package/dist/app-controller-memory.d.ts.map +1 -0
- package/dist/app-controller-memory.js +83 -0
- package/dist/app-controller-memory.js.map +1 -0
- package/dist/app-controller-responder.d.ts +20 -0
- package/dist/app-controller-responder.d.ts.map +1 -0
- package/dist/app-controller-responder.js +246 -0
- package/dist/app-controller-responder.js.map +1 -0
- package/dist/app-controller-traceability.d.ts +25 -0
- package/dist/app-controller-traceability.d.ts.map +1 -0
- package/dist/app-controller-traceability.js +418 -0
- package/dist/app-controller-traceability.js.map +1 -0
- package/dist/app-controller-utils.d.ts +38 -0
- package/dist/app-controller-utils.d.ts.map +1 -0
- package/dist/app-controller-utils.js +185 -0
- package/dist/app-controller-utils.js.map +1 -0
- package/dist/app-controller.d.ts +400 -0
- package/dist/app-controller.d.ts.map +1 -0
- package/dist/app-controller.js +336 -0
- package/dist/app-controller.js.map +1 -0
- package/dist/app.d.ts +7 -115
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +6 -1912
- package/dist/app.js.map +1 -1
- package/dist/artifact-retention.d.ts +4 -0
- package/dist/artifact-retention.d.ts.map +1 -1
- package/dist/artifact-retention.js +38 -0
- package/dist/artifact-retention.js.map +1 -1
- package/dist/backfill.d.ts +3 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +5 -1
- package/dist/backfill.js.map +1 -1
- package/dist/background-runtime.d.ts +27 -0
- package/dist/background-runtime.d.ts.map +1 -1
- package/dist/background-runtime.js +383 -205
- package/dist/background-runtime.js.map +1 -1
- package/dist/background-worker-lease.d.ts.map +1 -1
- package/dist/background-worker-lease.js +41 -241
- package/dist/background-worker-lease.js.map +1 -1
- package/dist/background.d.ts +31 -9
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +543 -272
- package/dist/background.js.map +1 -1
- package/dist/channel-drivers/cron.d.ts +11 -0
- package/dist/channel-drivers/cron.d.ts.map +1 -0
- package/dist/channel-drivers/cron.js +168 -0
- package/dist/channel-drivers/cron.js.map +1 -0
- package/dist/channel-drivers/live.d.ts +11 -0
- package/dist/channel-drivers/live.d.ts.map +1 -0
- package/dist/channel-drivers/live.js +47 -0
- package/dist/channel-drivers/live.js.map +1 -0
- package/dist/channel-drivers/native-notify.d.ts +27 -0
- package/dist/channel-drivers/native-notify.d.ts.map +1 -0
- package/dist/channel-drivers/native-notify.js +94 -0
- package/dist/channel-drivers/native-notify.js.map +1 -0
- package/dist/channel-drivers/openai-api.d.ts +7 -0
- package/dist/channel-drivers/openai-api.d.ts.map +1 -0
- package/dist/channel-drivers/openai-api.js +63 -0
- package/dist/channel-drivers/openai-api.js.map +1 -0
- package/dist/channel-drivers/shared.d.ts +4 -0
- package/dist/channel-drivers/shared.d.ts.map +1 -0
- package/dist/channel-drivers/shared.js +5 -0
- package/dist/channel-drivers/shared.js.map +1 -0
- package/dist/channel-drivers/slack.d.ts +14 -0
- package/dist/channel-drivers/slack.d.ts.map +1 -0
- package/dist/channel-drivers/slack.js +227 -0
- package/dist/channel-drivers/slack.js.map +1 -0
- package/dist/channel-drivers/telegram.d.ts +11 -0
- package/dist/channel-drivers/telegram.d.ts.map +1 -0
- package/dist/channel-drivers/telegram.js +244 -0
- package/dist/channel-drivers/telegram.js.map +1 -0
- package/dist/channel-drivers/tui.d.ts +15 -0
- package/dist/channel-drivers/tui.d.ts.map +1 -0
- package/dist/channel-drivers/tui.js +108 -0
- package/dist/channel-drivers/tui.js.map +1 -0
- package/dist/channel-drivers/webhook.d.ts +7 -0
- package/dist/channel-drivers/webhook.d.ts.map +1 -0
- package/dist/channel-drivers/webhook.js +115 -0
- package/dist/channel-drivers/webhook.js.map +1 -0
- package/dist/channels.d.ts +21 -81
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +11 -1174
- package/dist/channels.js.map +1 -1
- package/dist/cli-args.d.ts +121 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +532 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-background-command.d.ts +59 -0
- package/dist/cli-background-command.d.ts.map +1 -0
- package/dist/cli-background-command.js +475 -0
- package/dist/cli-background-command.js.map +1 -0
- package/dist/cli-cancellation.d.ts +6 -0
- package/dist/cli-cancellation.d.ts.map +1 -0
- package/dist/cli-cancellation.js +23 -0
- package/dist/cli-cancellation.js.map +1 -0
- package/dist/cli-commands.d.ts +12 -0
- package/dist/cli-commands.d.ts.map +1 -0
- package/dist/cli-commands.js +306 -0
- package/dist/cli-commands.js.map +1 -0
- package/dist/cli-help.d.ts +11 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +262 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli-init-command.d.ts +60 -0
- package/dist/cli-init-command.d.ts.map +1 -0
- package/dist/cli-init-command.js +1910 -0
- package/dist/cli-init-command.js.map +1 -0
- package/dist/cli-install-skill-command.d.ts +3 -0
- package/dist/cli-install-skill-command.d.ts.map +1 -0
- package/dist/cli-install-skill-command.js +51 -0
- package/dist/cli-install-skill-command.js.map +1 -0
- package/dist/cli-sandbox-command.d.ts +10 -0
- package/dist/cli-sandbox-command.d.ts.map +1 -0
- package/dist/cli-sandbox-command.js +100 -0
- package/dist/cli-sandbox-command.js.map +1 -0
- package/dist/cli-validate-config-command.d.ts +36 -0
- package/dist/cli-validate-config-command.d.ts.map +1 -0
- package/dist/cli-validate-config-command.js +317 -0
- package/dist/cli-validate-config-command.js.map +1 -0
- package/dist/cli.d.ts +6 -248
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3792
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts +2 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +34 -8
- package/dist/config-reference.js.map +1 -1
- package/dist/configured-agent.d.ts +23 -0
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +92 -13
- package/dist/configured-agent.js.map +1 -1
- package/dist/continuation-dispatch-worker.d.ts +21 -0
- package/dist/continuation-dispatch-worker.d.ts.map +1 -0
- package/dist/continuation-dispatch-worker.js +425 -0
- package/dist/continuation-dispatch-worker.js.map +1 -0
- package/dist/continuation-http-routes.d.ts +15 -0
- package/dist/continuation-http-routes.d.ts.map +1 -0
- package/dist/continuation-http-routes.js +265 -0
- package/dist/continuation-http-routes.js.map +1 -0
- package/dist/continuation-operator-api.d.ts +18 -0
- package/dist/continuation-operator-api.d.ts.map +1 -0
- package/dist/continuation-operator-api.js +164 -0
- package/dist/continuation-operator-api.js.map +1 -0
- package/dist/continuation-origin-store.d.ts +19 -0
- package/dist/continuation-origin-store.d.ts.map +1 -0
- package/dist/continuation-origin-store.js +273 -0
- package/dist/continuation-origin-store.js.map +1 -0
- package/dist/continuation-service-errors.d.ts +18 -0
- package/dist/continuation-service-errors.d.ts.map +1 -0
- package/dist/continuation-service-errors.js +35 -0
- package/dist/continuation-service-errors.js.map +1 -0
- package/dist/continuation-service-helpers.d.ts +39 -0
- package/dist/continuation-service-helpers.d.ts.map +1 -0
- package/dist/continuation-service-helpers.js +308 -0
- package/dist/continuation-service-helpers.js.map +1 -0
- package/dist/continuation-service-origin.d.ts +55 -0
- package/dist/continuation-service-origin.d.ts.map +1 -0
- package/dist/continuation-service-origin.js +325 -0
- package/dist/continuation-service-origin.js.map +1 -0
- package/dist/continuation-service-types.d.ts +110 -0
- package/dist/continuation-service-types.d.ts.map +1 -0
- package/dist/continuation-service-types.js +11 -0
- package/dist/continuation-service-types.js.map +1 -0
- package/dist/continuation-service.d.ts +4 -97
- package/dist/continuation-service.d.ts.map +1 -1
- package/dist/continuation-service.js +9 -1525
- package/dist/continuation-service.js.map +1 -1
- package/dist/continuation-store-fs.d.ts +15 -0
- package/dist/continuation-store-fs.d.ts.map +1 -0
- package/dist/continuation-store-fs.js +247 -0
- package/dist/continuation-store-fs.js.map +1 -0
- package/dist/continuation-store-policy.d.ts +21 -0
- package/dist/continuation-store-policy.d.ts.map +1 -0
- package/dist/continuation-store-policy.js +278 -0
- package/dist/continuation-store-policy.js.map +1 -0
- package/dist/continuation-store-records.d.ts +9 -0
- package/dist/continuation-store-records.d.ts.map +1 -0
- package/dist/continuation-store-records.js +221 -0
- package/dist/continuation-store-records.js.map +1 -0
- package/dist/continuation-store-types.d.ts +173 -0
- package/dist/continuation-store-types.d.ts.map +1 -0
- package/dist/continuation-store-types.js +26 -0
- package/dist/continuation-store-types.js.map +1 -0
- package/dist/continuation-store.d.ts +4 -138
- package/dist/continuation-store.d.ts.map +1 -1
- package/dist/continuation-store.js +145 -1049
- package/dist/continuation-store.js.map +1 -1
- package/dist/doctor.d.ts +10 -5
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +629 -58
- package/dist/doctor.js.map +1 -1
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +92 -239
- package/dist/init.js.map +1 -1
- package/dist/interaction-bridge.d.ts +19 -0
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +130 -0
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/launchd-logs.d.ts +91 -0
- package/dist/launchd-logs.d.ts.map +1 -0
- package/dist/launchd-logs.js +1499 -0
- package/dist/launchd-logs.js.map +1 -0
- package/dist/launchd.d.ts +27 -0
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +89 -10
- package/dist/launchd.js.map +1 -1
- package/dist/memory-command.d.ts +2 -0
- package/dist/memory-command.d.ts.map +1 -1
- package/dist/memory-command.js +161 -48
- package/dist/memory-command.js.map +1 -1
- package/dist/memory-recall-settings.d.ts +64 -0
- package/dist/memory-recall-settings.d.ts.map +1 -0
- package/dist/memory-recall-settings.js +51 -0
- package/dist/memory-recall-settings.js.map +1 -0
- package/dist/memory-recall.d.ts +7 -95
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +8 -143
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-rituals.d.ts +3 -3
- package/dist/memory-rituals.d.ts.map +1 -1
- package/dist/memory-rituals.js +16 -111
- package/dist/memory-rituals.js.map +1 -1
- package/dist/modules/catalog.js +2 -2
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/known-tools.d.ts +8 -6
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +8 -6
- package/dist/modules/known-tools.js.map +1 -1
- package/dist/notify-destinations.d.ts +5 -0
- package/dist/notify-destinations.d.ts.map +1 -1
- package/dist/notify-destinations.js +7 -1
- package/dist/notify-destinations.js.map +1 -1
- package/dist/owner-private-lock.d.ts +95 -0
- package/dist/owner-private-lock.d.ts.map +1 -0
- package/dist/owner-private-lock.js +566 -0
- package/dist/owner-private-lock.js.map +1 -0
- package/dist/pi-oauth-login.d.ts +2 -0
- package/dist/pi-oauth-login.d.ts.map +1 -1
- package/dist/pi-oauth-login.js +39 -5
- package/dist/pi-oauth-login.js.map +1 -1
- package/dist/posted-message-index.d.ts +35 -16
- package/dist/posted-message-index.d.ts.map +1 -1
- package/dist/posted-message-index.js +1068 -52
- package/dist/posted-message-index.js.map +1 -1
- package/dist/posted-reply-history.d.ts +17 -0
- package/dist/posted-reply-history.d.ts.map +1 -0
- package/dist/posted-reply-history.js +169 -0
- package/dist/posted-reply-history.js.map +1 -0
- package/dist/proactive-notify.d.ts.map +1 -1
- package/dist/proactive-notify.js +6 -4
- package/dist/proactive-notify.js.map +1 -1
- package/dist/project-skills.d.ts +4 -0
- package/dist/project-skills.d.ts.map +1 -1
- package/dist/project-skills.js +99 -130
- package/dist/project-skills.js.map +1 -1
- package/dist/provider-setup.d.ts +0 -1
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +0 -3
- package/dist/provider-setup.js.map +1 -1
- package/dist/readiness-probe-worker.d.ts +61 -1
- package/dist/readiness-probe-worker.d.ts.map +1 -1
- package/dist/readiness-probe-worker.js +54 -58
- package/dist/readiness-probe-worker.js.map +1 -1
- package/dist/readiness-probe.d.ts +6 -9
- package/dist/readiness-probe.d.ts.map +1 -1
- package/dist/readiness-probe.js +30 -56
- package/dist/readiness-probe.js.map +1 -1
- package/dist/redact-secrets.d.ts +15 -0
- package/dist/redact-secrets.d.ts.map +1 -0
- package/dist/redact-secrets.js +65 -0
- package/dist/redact-secrets.js.map +1 -0
- package/dist/run-history.js +34 -24
- package/dist/run-history.js.map +1 -1
- package/dist/runtime-provenance.d.ts +8 -0
- package/dist/runtime-provenance.d.ts.map +1 -0
- package/dist/runtime-provenance.js +225 -0
- package/dist/runtime-provenance.js.map +1 -0
- package/dist/sandbox-manager.d.ts +4 -1
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox-manager.js +114 -273
- package/dist/sandbox-manager.js.map +1 -1
- package/dist/secure-file-replace.d.ts +49 -0
- package/dist/secure-file-replace.d.ts.map +1 -0
- package/dist/secure-file-replace.js +270 -0
- package/dist/secure-file-replace.js.map +1 -0
- package/dist/seen-conversations.d.ts +32 -5
- package/dist/seen-conversations.d.ts.map +1 -1
- package/dist/seen-conversations.js +145 -15
- package/dist/seen-conversations.js.map +1 -1
- package/dist/sessions.d.ts +20 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +41 -6
- package/dist/sessions.js.map +1 -1
- package/dist/wizard/index.d.ts +2 -2
- 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 +2 -1
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +2 -1
- package/dist/wizard/presets.js.map +1 -1
- package/dist/wizard/run.d.ts +0 -26
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +0 -53
- package/dist/wizard/run.js.map +1 -1
- package/package.json +22 -17
- package/schema/mono-agent.config.schema.json +45 -10
- package/skills/mono-agent-composer/SKILL.md +3 -3
- package/skills/mono-agent-composer/references/config-blueprint.md +123 -29
- package/skills/mono-agent-composer/references/discovery-questions.md +10 -8
- package/skills/mono-agent-composer/references/feature-coverage.md +90 -76
- package/skills/mono-agent-composer/references/package-map.md +6 -4
- package/skills/mono-agent-composer/references/playbooks.md +48 -27
- package/skills/mono-agent-composer/references/validation.md +16 -3
- package/dist/notify-runtime.d.ts +0 -26
- package/dist/notify-runtime.d.ts.map +0 -1
- package/dist/notify-runtime.js +0 -27
- package/dist/notify-runtime.js.map +0 -1
- package/dist/notify-tool.d.ts +0 -51
- package/dist/notify-tool.d.ts.map +0 -1
- package/dist/notify-tool.js +0 -182
- package/dist/notify-tool.js.map +0 -1
- package/dist/recipes/base.d.ts +0 -15
- package/dist/recipes/base.d.ts.map +0 -1
- package/dist/recipes/base.js +0 -51
- package/dist/recipes/base.js.map +0 -1
- package/dist/recipes/catalog.d.ts +0 -4
- package/dist/recipes/catalog.d.ts.map +0 -1
- package/dist/recipes/catalog.js +0 -525
- package/dist/recipes/catalog.js.map +0 -1
- package/dist/recipes/index.d.ts +0 -11
- package/dist/recipes/index.d.ts.map +0 -1
- package/dist/recipes/index.js +0 -14
- package/dist/recipes/index.js.map +0 -1
- package/dist/recipes/types.d.ts +0 -70
- package/dist/recipes/types.d.ts.map +0 -1
- package/dist/recipes/types.js +0 -15
- package/dist/recipes/types.js.map +0 -1
- package/dist/setup.d.ts +0 -29
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js +0 -97
- package/dist/setup.js.map +0 -1
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
|
-
import { chmod, cp, lstat, mkdir, open, readFile, readlink, readdir, realpath, rename, rm,
|
|
3
|
+
import { chmod, cp, lstat, mkdir, open, readFile, readlink, readdir, realpath, rename, rm, symlink, writeFile, } from "node:fs/promises";
|
|
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
7
|
import { isBackgroundOperationalEnvName } from "./background-environment.js";
|
|
8
8
|
import { currentProcessIncarnation, isSameProcessIncarnation as matchesProcessIncarnation, processIncarnationFromJson, } from "./process-incarnation.js";
|
|
9
|
+
import { acquireOwnerPrivateLock } from "./owner-private-lock.js";
|
|
9
10
|
const PACKAGE_NAME = "@mono-agent/agent-app";
|
|
10
11
|
export const MANAGED_BACKGROUND_WORKER_ENV = "MONO_AGENT_MANAGED_WORKER";
|
|
11
12
|
const LOCK_WAIT_TIMEOUT_MS = 30_000;
|
|
12
13
|
const LOCK_STALE_AFTER_MS = 5 * 60_000;
|
|
13
14
|
const LOCK_POLL_INTERVAL_MS = 200;
|
|
14
15
|
const PROVISIONAL_RUNTIME_INSTALLED_AT = "1970-01-01T00:00:00.000Z";
|
|
16
|
+
const MAX_RUNTIME_MARKER_BYTES = 16 * 1024;
|
|
15
17
|
export function defaultManagedBackgroundRuntimeDeps() {
|
|
16
18
|
return {
|
|
17
19
|
now: () => Date.now(),
|
|
@@ -173,7 +175,86 @@ export async function ensureManagedBackgroundRuntime(input, deps = defaultManage
|
|
|
173
175
|
return runtimeResult(layout, identity, input.nodePath);
|
|
174
176
|
}
|
|
175
177
|
finally {
|
|
176
|
-
await
|
|
178
|
+
await acquired.release().catch(() => undefined);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Canonically verify the installed package, freshly recomputed execution
|
|
183
|
+
* closure/filesystem proof, and coherent current manifest behind a provenance
|
|
184
|
+
* marker. This intentionally does not install, repair, or compare against a
|
|
185
|
+
* deploy checkout; it only validates the managed snapshot executing the caller.
|
|
186
|
+
*/
|
|
187
|
+
export async function verifyManagedRuntimeClosureForProvenance(input, deps = {}) {
|
|
188
|
+
try {
|
|
189
|
+
if (!isExactVersion(input.packageVersion)
|
|
190
|
+
|| !/^[0-9a-f]{64}$/u.test(input.cliSha256)
|
|
191
|
+
|| !/^[0-9a-f]{64}$/u.test(input.sourceClosureSha256)
|
|
192
|
+
|| !/^[0-9a-f]{64}$/u.test(input.closureManifestSha256)
|
|
193
|
+
|| !/^[0-9a-f]{64}$/u.test(input.executionProofSha256)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const packageRoot = resolve(input.packageRoot);
|
|
197
|
+
const installRoot = dirname(dirname(dirname(packageRoot)));
|
|
198
|
+
if (join(installRoot, "node_modules", "@mono-agent", "agent-app") !== packageRoot)
|
|
199
|
+
return false;
|
|
200
|
+
const identity = {
|
|
201
|
+
packageVersion: input.packageVersion,
|
|
202
|
+
cliSha256: input.cliSha256,
|
|
203
|
+
sourceClosureSha256: input.sourceClosureSha256,
|
|
204
|
+
nodeAbi: input.nodeAbi,
|
|
205
|
+
platform: input.platform,
|
|
206
|
+
arch: input.arch,
|
|
207
|
+
};
|
|
208
|
+
const appRuntimeRoot = dirname(dirname(dirname(installRoot)));
|
|
209
|
+
const home = dirname(dirname(dirname(appRuntimeRoot)));
|
|
210
|
+
const layout = runtimeLayout(home, identity, "provenance", 0);
|
|
211
|
+
if (layout.installRoot !== installRoot || layout.cliPath !== join(packageRoot, "dist", "cli.js"))
|
|
212
|
+
return false;
|
|
213
|
+
if (!(await verifyPrivateRuntimeAncestors(home, layout.versionAbiDir)))
|
|
214
|
+
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
|
+
const markerInitial = await verifiedRuntimeMarker(layout, identity);
|
|
224
|
+
if (markerInitial === undefined || !sameRuntimeMarker(markerInitial, expectedMarker))
|
|
225
|
+
return false;
|
|
226
|
+
const additionalPackages = await managedRuntimeAdditionalPackages(layout);
|
|
227
|
+
if (additionalPackages === undefined)
|
|
228
|
+
return false;
|
|
229
|
+
const closureInitial = await matchingRuntimeClosureCapture(layout, identity, markerInitial, additionalPackages);
|
|
230
|
+
if (closureInitial === undefined)
|
|
231
|
+
return false;
|
|
232
|
+
await deps.afterInitialClosureCapture?.();
|
|
233
|
+
const closureFinal = await matchingRuntimeClosureCapture(layout, identity, markerInitial, additionalPackages, deps.afterFinalRootCapture === undefined
|
|
234
|
+
? {}
|
|
235
|
+
: { afterRootCapture: deps.afterFinalRootCapture });
|
|
236
|
+
if (closureFinal === undefined
|
|
237
|
+
|| closureFinal.sourceClosureSha256 !== closureInitial.sourceClosureSha256
|
|
238
|
+
|| closureFinal.sourceProofSha256 !== closureInitial.sourceProofSha256
|
|
239
|
+
|| closureFinal.filesystemProofSha256 !== closureInitial.filesystemProofSha256) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const markerFinal = await readRuntimeMarker(layout.markerPath, identity);
|
|
243
|
+
if (markerFinal === undefined
|
|
244
|
+
|| !sameRuntimeMarker(markerFinal, markerInitial)
|
|
245
|
+
|| !sameRuntimeMarker(markerFinal, expectedMarker)
|
|
246
|
+
|| !(await verifyClosureManifest(layout, markerFinal.closureManifestSha256, deps.beforeFinalManifestProof === undefined
|
|
247
|
+
? {}
|
|
248
|
+
: { beforeFinalProof: deps.beforeFinalManifestProof }))) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const markerAfterManifest = await readRuntimeMarker(layout.markerPath, identity);
|
|
252
|
+
return markerAfterManifest !== undefined
|
|
253
|
+
&& sameRuntimeMarker(markerAfterManifest, markerInitial)
|
|
254
|
+
&& sameRuntimeMarker(markerAfterManifest, expectedMarker);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return false;
|
|
177
258
|
}
|
|
178
259
|
}
|
|
179
260
|
/**
|
|
@@ -353,11 +434,12 @@ async function materializeExactExecutionClosure(input) {
|
|
|
353
434
|
throw new Error("The managed runtime package closure changed while it was staged.");
|
|
354
435
|
}
|
|
355
436
|
}
|
|
356
|
-
async function captureExecutionClosure(packageSource, additionalPackages, filesystemProofRoot) {
|
|
437
|
+
async function captureExecutionClosure(packageSource, additionalPackages, filesystemProofRoot, deps = {}) {
|
|
357
438
|
const nodes = [];
|
|
358
439
|
const bySourceRoot = new Map();
|
|
359
440
|
const proofs = new Map();
|
|
360
441
|
const dependencyProofs = [];
|
|
442
|
+
const dependencyProofPaths = [];
|
|
361
443
|
const resolutionPathProof = filesystemProofRoot === undefined
|
|
362
444
|
? undefined
|
|
363
445
|
: await createResolutionPathProof(filesystemProofRoot);
|
|
@@ -400,13 +482,20 @@ async function captureExecutionClosure(packageSource, additionalPackages, filesy
|
|
|
400
482
|
throw new Error(`Installed dependency ${dependencyName} changed while its execution closure was inspected.`);
|
|
401
483
|
}
|
|
402
484
|
dependencyProofs.push({ ownerId: id, dependencyName, proof: dependencyProofAfter });
|
|
485
|
+
dependencyProofPaths.push({
|
|
486
|
+
path: dependency.candidatePath,
|
|
487
|
+
logicalPath: dependencyName,
|
|
488
|
+
proof: dependencyProofAfter,
|
|
489
|
+
});
|
|
403
490
|
node.dependencies.set(dependencyName, dependencyNodeId);
|
|
404
491
|
}
|
|
405
492
|
return id;
|
|
406
493
|
};
|
|
407
494
|
await visit(packageSource);
|
|
495
|
+
await deps.afterRootCapture?.();
|
|
408
496
|
const additionalRoots = [];
|
|
409
497
|
const additionalProofs = [];
|
|
498
|
+
const additionalProofPaths = [];
|
|
410
499
|
for (const additional of additionalPackages) {
|
|
411
500
|
const additionalProof = await captureFilesystemPathProof(additional.packageSource, additional.packageName);
|
|
412
501
|
const nodeId = await visit(additional.packageSource);
|
|
@@ -423,9 +512,18 @@ async function captureExecutionClosure(packageSource, additionalPackages, filesy
|
|
|
423
512
|
throw new Error(`Additional managed runtime package ${additional.packageName} changed while its execution closure was inspected.`);
|
|
424
513
|
}
|
|
425
514
|
additionalProofs.push({ packageName: additional.packageName, proof: additionalProofAfter });
|
|
515
|
+
additionalProofPaths.push({
|
|
516
|
+
path: additional.packageSource,
|
|
517
|
+
logicalPath: additional.packageName,
|
|
518
|
+
proof: additionalProofAfter,
|
|
519
|
+
});
|
|
426
520
|
additionalRoots.push({ packageName: additional.packageName, nodeId });
|
|
427
521
|
}
|
|
428
522
|
const closure = nodes;
|
|
523
|
+
await revalidateExecutionClosureProofs(closure, proofs, [
|
|
524
|
+
...dependencyProofPaths,
|
|
525
|
+
...additionalProofPaths,
|
|
526
|
+
]);
|
|
429
527
|
const resolutionPaths = await resolutionPathProof?.finalize() ?? [];
|
|
430
528
|
const stable = {
|
|
431
529
|
schema: "mono-agent.source-execution-closure.v1",
|
|
@@ -473,6 +571,30 @@ async function captureExecutionClosure(packageSource, additionalPackages, filesy
|
|
|
473
571
|
filesystemProofSha256: sha256(Buffer.from(JSON.stringify(filesystemProof), "utf8")),
|
|
474
572
|
};
|
|
475
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Final identity pass for every package entry and link captured above. Per-file
|
|
576
|
+
* reads are descriptor-stable, but without this pass an early file could be
|
|
577
|
+
* overwritten while a later large file was still being hashed.
|
|
578
|
+
*/
|
|
579
|
+
async function revalidateExecutionClosureProofs(closure, proofs, links) {
|
|
580
|
+
for (const node of closure) {
|
|
581
|
+
for (const expected of requiredMapValue(proofs, node.id, "source package proof")) {
|
|
582
|
+
const path = expected.path === "."
|
|
583
|
+
? node.sourceRoot
|
|
584
|
+
: join(node.sourceRoot, ...expected.path.split("/"));
|
|
585
|
+
const current = await captureFilesystemPathProof(path, expected.path);
|
|
586
|
+
if (!sameSourceProofEntry(expected, current)) {
|
|
587
|
+
throw new Error(`Managed runtime source package entry ${expected.path} changed during closure capture.`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const link of links) {
|
|
592
|
+
const current = await captureFilesystemPathProof(link.path, link.logicalPath);
|
|
593
|
+
if (!sameSourceProofEntry(link.proof, current)) {
|
|
594
|
+
throw new Error(`Managed runtime package link ${link.logicalPath} changed during closure capture.`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
476
598
|
async function canonicalAdditionalPackages(packages) {
|
|
477
599
|
const byName = new Map();
|
|
478
600
|
for (const entry of packages) {
|
|
@@ -660,6 +782,7 @@ function sourceProofEntry(path, type, details, target) {
|
|
|
660
782
|
type,
|
|
661
783
|
dev: details.dev.toString(),
|
|
662
784
|
ino: details.ino.toString(),
|
|
785
|
+
nlink: details.nlink.toString(),
|
|
663
786
|
mode: details.mode.toString(),
|
|
664
787
|
size: details.size.toString(),
|
|
665
788
|
mtimeNs: details.mtimeNs.toString(),
|
|
@@ -672,6 +795,7 @@ function sameSourceIdentity(left, right) {
|
|
|
672
795
|
}
|
|
673
796
|
function sameSourceStats(left, right) {
|
|
674
797
|
return sameSourceIdentity(left, right)
|
|
798
|
+
&& left.nlink === right.nlink
|
|
675
799
|
&& left.mode === right.mode
|
|
676
800
|
&& left.size === right.size
|
|
677
801
|
&& left.mtimeNs === right.mtimeNs
|
|
@@ -960,127 +1084,40 @@ function runtimeLayoutForRoot(layout, root) {
|
|
|
960
1084
|
};
|
|
961
1085
|
}
|
|
962
1086
|
async function acquireRuntimeLock(layout, identity, additionalPackages, deps) {
|
|
963
|
-
const deadline = deps.now() + LOCK_WAIT_TIMEOUT_MS;
|
|
964
1087
|
const incarnation = await (deps.currentProcessIncarnation ?? currentProcessIncarnation)();
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
: !(await isSameProcess(owner.pid, owner.incarnation));
|
|
999
|
-
if (!(await sameRuntimeLockDirectory(layout.lockDir, observedIdentity)))
|
|
1000
|
-
continue;
|
|
1001
|
-
if (stale) {
|
|
1002
|
-
const staleLock = `${layout.quarantineDir}-stale-lock`;
|
|
1003
|
-
await mkdir(dirname(staleLock), { recursive: true, mode: 0o700 });
|
|
1004
|
-
try {
|
|
1005
|
-
await rename(layout.lockDir, staleLock);
|
|
1006
|
-
}
|
|
1007
|
-
catch (error) {
|
|
1008
|
-
if (isErrno(error, "ENOENT") || isErrno(error, "EEXIST"))
|
|
1009
|
-
continue;
|
|
1010
|
-
throw error;
|
|
1011
|
-
}
|
|
1012
|
-
const moved = await lstat(staleLock);
|
|
1013
|
-
if (!sameRuntimeLockIdentity(moved, observedIdentity)) {
|
|
1014
|
-
await rename(staleLock, layout.lockDir).catch(() => undefined);
|
|
1015
|
-
return undefined;
|
|
1016
|
-
}
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
catch (error) {
|
|
1021
|
-
if (isErrno(error, "ENOENT"))
|
|
1022
|
-
continue;
|
|
1023
|
-
throw error;
|
|
1024
|
-
}
|
|
1025
|
-
if (deps.now() >= deadline)
|
|
1026
|
-
return undefined;
|
|
1027
|
-
await deps.sleep(LOCK_POLL_INTERVAL_MS);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
async function runtimeLockOwner(path) {
|
|
1031
|
-
try {
|
|
1032
|
-
const owner = JSON.parse(await readFile(path, "utf8"));
|
|
1033
|
-
const incarnation = processIncarnationFromJson(owner.incarnation);
|
|
1034
|
-
return typeof owner.pid === "number"
|
|
1035
|
-
&& Number.isSafeInteger(owner.pid)
|
|
1036
|
-
&& owner.pid > 0
|
|
1037
|
-
&& incarnation !== undefined
|
|
1038
|
-
? { pid: owner.pid, incarnation }
|
|
1039
|
-
: undefined;
|
|
1040
|
-
}
|
|
1041
|
-
catch {
|
|
1042
|
-
return undefined;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
async function assertPrivateRuntimeLockDirectory(path) {
|
|
1046
|
-
const details = await lstat(path);
|
|
1047
|
-
if (!details.isDirectory() || details.isSymbolicLink() || !ownedPrivately(details)) {
|
|
1048
|
-
throw new Error(`Managed runtime lock ${path} is not a private current-user directory.`);
|
|
1049
|
-
}
|
|
1050
|
-
return details;
|
|
1051
|
-
}
|
|
1052
|
-
function runtimeLockIdentity(details) {
|
|
1053
|
-
return { dev: details.dev, ino: details.ino };
|
|
1054
|
-
}
|
|
1055
|
-
function sameRuntimeLockIdentity(details, identity) {
|
|
1056
|
-
return details.dev === identity.dev && details.ino === identity.ino;
|
|
1057
|
-
}
|
|
1058
|
-
async function sameRuntimeLockDirectory(path, identity) {
|
|
1059
|
-
try {
|
|
1060
|
-
return sameRuntimeLockIdentity(await assertPrivateRuntimeLockDirectory(path), identity);
|
|
1061
|
-
}
|
|
1062
|
-
catch {
|
|
1063
|
-
return false;
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
async function removeSameRuntimeLockDirectory(path, identity) {
|
|
1067
|
-
if (!(await sameRuntimeLockDirectory(path, identity)))
|
|
1068
|
-
return;
|
|
1069
|
-
const released = `${path}.released-${process.pid}-${randomUUID()}`;
|
|
1070
|
-
try {
|
|
1071
|
-
await rename(path, released);
|
|
1072
|
-
}
|
|
1073
|
-
catch (error) {
|
|
1074
|
-
if (isErrno(error, "ENOENT"))
|
|
1075
|
-
return;
|
|
1076
|
-
throw error;
|
|
1077
|
-
}
|
|
1078
|
-
const moved = await lstat(released);
|
|
1079
|
-
if (!sameRuntimeLockIdentity(moved, identity)) {
|
|
1080
|
-
await rename(released, path).catch(() => undefined);
|
|
1081
|
-
throw new Error(`Managed runtime lock ${path} changed while it was released.`);
|
|
1082
|
-
}
|
|
1083
|
-
await rm(released, { recursive: true, force: true });
|
|
1088
|
+
return acquireOwnerPrivateLock({
|
|
1089
|
+
path: layout.lockDir,
|
|
1090
|
+
label: "Managed runtime lock",
|
|
1091
|
+
schemaTag: "mono-agent.managed-runtime-lock.v1",
|
|
1092
|
+
ownerlessGraceMs: LOCK_STALE_AFTER_MS,
|
|
1093
|
+
waitTimeoutMs: LOCK_WAIT_TIMEOUT_MS,
|
|
1094
|
+
pollIntervalMs: LOCK_POLL_INTERVAL_MS,
|
|
1095
|
+
now: deps.now,
|
|
1096
|
+
processIncarnation: incarnation,
|
|
1097
|
+
...(deps.isSameProcessIncarnation === undefined
|
|
1098
|
+
? {}
|
|
1099
|
+
: { isSameProcessIncarnation: deps.isSameProcessIncarnation }),
|
|
1100
|
+
ownerFields: () => ({ ...identity }),
|
|
1101
|
+
validateOwnerFields: (record) => Object.entries(identity)
|
|
1102
|
+
.every(([key, value]) => record[key] === value),
|
|
1103
|
+
parseLegacyOwner: (record) => {
|
|
1104
|
+
const legacyIncarnation = processIncarnationFromJson(record.incarnation);
|
|
1105
|
+
return typeof record.pid === "number"
|
|
1106
|
+
&& Number.isSafeInteger(record.pid)
|
|
1107
|
+
&& record.pid > 0
|
|
1108
|
+
&& legacyIncarnation !== undefined
|
|
1109
|
+
? { pid: record.pid, incarnation: legacyIncarnation }
|
|
1110
|
+
: undefined;
|
|
1111
|
+
},
|
|
1112
|
+
invalidOwner: "ownerless",
|
|
1113
|
+
isSatisfied: () => verifyRuntime(layout, identity, additionalPackages),
|
|
1114
|
+
sleep: deps.sleep,
|
|
1115
|
+
staleRace: "return",
|
|
1116
|
+
beforeStaleRename: async () => {
|
|
1117
|
+
await mkdir(dirname(layout.quarantineDir), { recursive: true, mode: 0o700 });
|
|
1118
|
+
},
|
|
1119
|
+
stalePath: () => `${layout.quarantineDir}-stale-lock`,
|
|
1120
|
+
});
|
|
1084
1121
|
}
|
|
1085
1122
|
async function quarantineInvalidRuntime(layout, identity, additionalPackages, deps) {
|
|
1086
1123
|
try {
|
|
@@ -1114,24 +1151,88 @@ async function promoteStaging(layout, staged, identity, additionalPackages) {
|
|
|
1114
1151
|
return false;
|
|
1115
1152
|
}
|
|
1116
1153
|
}
|
|
1154
|
+
async function managedRuntimeAdditionalPackages(layout) {
|
|
1155
|
+
try {
|
|
1156
|
+
const parsed = JSON.parse(await readFile(layout.packageLockPath, "utf8"));
|
|
1157
|
+
if (!isRecord(parsed) || parsed.lockfileVersion !== 3 || !isRecord(parsed.packages))
|
|
1158
|
+
return undefined;
|
|
1159
|
+
const byName = new Map();
|
|
1160
|
+
for (const [path, value] of Object.entries(parsed.packages)) {
|
|
1161
|
+
if (!isRecord(value))
|
|
1162
|
+
return undefined;
|
|
1163
|
+
if (value.link !== true)
|
|
1164
|
+
continue;
|
|
1165
|
+
const segments = path.split("/");
|
|
1166
|
+
if (segments[0] !== "node_modules")
|
|
1167
|
+
return undefined;
|
|
1168
|
+
const nameSegments = segments.slice(1);
|
|
1169
|
+
const packageName = nameSegments.join("/");
|
|
1170
|
+
packageNameSegments(packageName);
|
|
1171
|
+
if (packageName === PACKAGE_NAME
|
|
1172
|
+
|| join("node_modules", ...nameSegments).split(sep).join("/") !== path
|
|
1173
|
+
|| typeof value.resolved !== "string") {
|
|
1174
|
+
return undefined;
|
|
1175
|
+
}
|
|
1176
|
+
const packageSource = join(layout.installRoot, "node_modules", ...nameSegments);
|
|
1177
|
+
const linkDetails = await lstat(packageSource);
|
|
1178
|
+
if (!linkDetails.isSymbolicLink())
|
|
1179
|
+
return undefined;
|
|
1180
|
+
const target = await readlink(packageSource);
|
|
1181
|
+
if (isAbsolute(target))
|
|
1182
|
+
return undefined;
|
|
1183
|
+
const canonical = await realpath(packageSource);
|
|
1184
|
+
assertPathInsideRoot(layout.installRoot, canonical, `additional package ${packageName}`);
|
|
1185
|
+
if (portableRelativePath(layout.installRoot, canonical) !== value.resolved)
|
|
1186
|
+
return undefined;
|
|
1187
|
+
const existing = byName.get(packageName);
|
|
1188
|
+
if (existing !== undefined && existing !== packageSource)
|
|
1189
|
+
return undefined;
|
|
1190
|
+
byName.set(packageName, packageSource);
|
|
1191
|
+
}
|
|
1192
|
+
return [...byName]
|
|
1193
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
1194
|
+
.map(([packageName, packageSource]) => ({ packageName, packageSource }));
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
return undefined;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
async function matchingRuntimeClosureCapture(layout, identity, marker, additionalPackages, deps = {}) {
|
|
1201
|
+
const packageSource = dirname(dirname(layout.cliPath));
|
|
1202
|
+
const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
1203
|
+
packageName,
|
|
1204
|
+
packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
1205
|
+
}));
|
|
1206
|
+
const closure = await captureExecutionClosure(packageSource, runtimeAdditionalPackages, layout.installRoot, deps);
|
|
1207
|
+
return closure.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1208
|
+
&& closure.filesystemProofSha256 === marker.executionProofSha256
|
|
1209
|
+
? closure
|
|
1210
|
+
: undefined;
|
|
1211
|
+
}
|
|
1117
1212
|
async function verifyRuntime(layout, identity, additionalPackages) {
|
|
1118
1213
|
const marker = await verifiedRuntimeMarker(layout, identity);
|
|
1119
1214
|
if (marker === undefined)
|
|
1120
1215
|
return false;
|
|
1121
1216
|
try {
|
|
1122
|
-
|
|
1123
|
-
const runtimeAdditionalPackages = additionalPackages.map(({ packageName }) => ({
|
|
1124
|
-
packageName,
|
|
1125
|
-
packageSource: join(layout.installRoot, "node_modules", ...packageNameSegments(packageName)),
|
|
1126
|
-
}));
|
|
1127
|
-
const closure = await captureExecutionClosure(packageSource, runtimeAdditionalPackages, layout.installRoot);
|
|
1128
|
-
return closure.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1129
|
-
&& closure.filesystemProofSha256 === marker.executionProofSha256;
|
|
1217
|
+
return await matchingRuntimeClosureCapture(layout, identity, marker, additionalPackages) !== undefined;
|
|
1130
1218
|
}
|
|
1131
1219
|
catch {
|
|
1132
1220
|
return false;
|
|
1133
1221
|
}
|
|
1134
1222
|
}
|
|
1223
|
+
function sameRuntimeMarker(left, right) {
|
|
1224
|
+
return left.schema === right.schema
|
|
1225
|
+
&& left.packageName === right.packageName
|
|
1226
|
+
&& left.closureManifestSha256 === right.closureManifestSha256
|
|
1227
|
+
&& left.executionProofSha256 === right.executionProofSha256
|
|
1228
|
+
&& left.packageVersion === right.packageVersion
|
|
1229
|
+
&& left.cliSha256 === right.cliSha256
|
|
1230
|
+
&& left.sourceClosureSha256 === right.sourceClosureSha256
|
|
1231
|
+
&& left.nodeAbi === right.nodeAbi
|
|
1232
|
+
&& left.platform === right.platform
|
|
1233
|
+
&& left.arch === right.arch
|
|
1234
|
+
&& left.installedAt === right.installedAt;
|
|
1235
|
+
}
|
|
1135
1236
|
async function verifiedRuntimeMarker(layout, identity) {
|
|
1136
1237
|
try {
|
|
1137
1238
|
const root = await lstat(layout.installRoot);
|
|
@@ -1139,49 +1240,90 @@ async function verifiedRuntimeMarker(layout, identity) {
|
|
|
1139
1240
|
return undefined;
|
|
1140
1241
|
if (!(await verifyInstalledPackage(layout, identity)))
|
|
1141
1242
|
return undefined;
|
|
1142
|
-
await
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
&& typeof marker.executionProofSha256 === "string"
|
|
1171
|
-
&& /^[0-9a-f]{64}$/u.test(marker.executionProofSha256)
|
|
1172
|
-
&& typeof marker.installedAt === "string"
|
|
1173
|
-
&& Number.isFinite(installedAtMs)
|
|
1174
|
-
&& new Date(installedAtMs).toISOString() === marker.installedAt
|
|
1175
|
-
// Promotion writes this sentinel before the final filesystem proof is
|
|
1176
|
-
// rebound. It must remain unverifiable even when the filesystem records
|
|
1177
|
-
// both writes with indistinguishable timestamp precision.
|
|
1178
|
-
&& marker.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT
|
|
1179
|
-
&& await verifyClosureManifest(layout, marker.closureManifestSha256);
|
|
1180
|
-
return valid ? marker : undefined;
|
|
1243
|
+
const marker = await readRuntimeMarker(layout.markerPath, identity);
|
|
1244
|
+
return marker !== undefined && await verifyClosureManifest(layout, marker.closureManifestSha256)
|
|
1245
|
+
? marker
|
|
1246
|
+
: undefined;
|
|
1247
|
+
}
|
|
1248
|
+
catch {
|
|
1249
|
+
return undefined;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
async function readRuntimeMarker(path, identity) {
|
|
1253
|
+
let handle;
|
|
1254
|
+
try {
|
|
1255
|
+
handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
1256
|
+
const before = await handle.stat({ bigint: true });
|
|
1257
|
+
if (!privateRuntimeMarkerFile(before))
|
|
1258
|
+
return undefined;
|
|
1259
|
+
const bytes = await handle.readFile();
|
|
1260
|
+
const [after, named] = await Promise.all([
|
|
1261
|
+
handle.stat({ bigint: true }),
|
|
1262
|
+
lstat(path, { bigint: true }),
|
|
1263
|
+
]);
|
|
1264
|
+
if (!privateRuntimeMarkerFile(after)
|
|
1265
|
+
|| !privateRuntimeMarkerFile(named)
|
|
1266
|
+
|| !sameSourceStats(before, after)
|
|
1267
|
+
|| !sameSourceStats(before, named)) {
|
|
1268
|
+
return undefined;
|
|
1269
|
+
}
|
|
1270
|
+
return runtimeMarkerFromJson(JSON.parse(bytes.toString("utf8")), identity);
|
|
1181
1271
|
}
|
|
1182
1272
|
catch {
|
|
1183
1273
|
return undefined;
|
|
1184
1274
|
}
|
|
1275
|
+
finally {
|
|
1276
|
+
await handle?.close();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function privateRuntimeMarkerFile(details) {
|
|
1280
|
+
const owned = typeof process.getuid !== "function" || details.uid === BigInt(process.getuid());
|
|
1281
|
+
return details.isFile()
|
|
1282
|
+
&& !details.isSymbolicLink()
|
|
1283
|
+
&& details.nlink === 1n
|
|
1284
|
+
&& details.size <= BigInt(MAX_RUNTIME_MARKER_BYTES)
|
|
1285
|
+
&& (details.mode & 63n) === 0n
|
|
1286
|
+
&& owned;
|
|
1287
|
+
}
|
|
1288
|
+
function runtimeMarkerFromJson(value, identity) {
|
|
1289
|
+
if (!isRecord(value))
|
|
1290
|
+
return undefined;
|
|
1291
|
+
const exactKeys = [
|
|
1292
|
+
"schema",
|
|
1293
|
+
"packageName",
|
|
1294
|
+
"closureManifestSha256",
|
|
1295
|
+
"executionProofSha256",
|
|
1296
|
+
"packageVersion",
|
|
1297
|
+
"cliSha256",
|
|
1298
|
+
"sourceClosureSha256",
|
|
1299
|
+
"nodeAbi",
|
|
1300
|
+
"platform",
|
|
1301
|
+
"arch",
|
|
1302
|
+
"installedAt",
|
|
1303
|
+
];
|
|
1304
|
+
const installedAtMs = typeof value.installedAt === "string" ? Date.parse(value.installedAt) : Number.NaN;
|
|
1305
|
+
const valid = Object.keys(value).length === exactKeys.length
|
|
1306
|
+
&& exactKeys.every((key) => Object.hasOwn(value, key))
|
|
1307
|
+
&& value.schema === "mono-agent.managed-runtime.v4"
|
|
1308
|
+
&& value.packageName === PACKAGE_NAME
|
|
1309
|
+
&& value.packageVersion === identity.packageVersion
|
|
1310
|
+
&& value.cliSha256 === identity.cliSha256
|
|
1311
|
+
&& value.sourceClosureSha256 === identity.sourceClosureSha256
|
|
1312
|
+
&& value.nodeAbi === identity.nodeAbi
|
|
1313
|
+
&& value.platform === identity.platform
|
|
1314
|
+
&& value.arch === identity.arch
|
|
1315
|
+
&& typeof value.closureManifestSha256 === "string"
|
|
1316
|
+
&& /^[0-9a-f]{64}$/u.test(value.closureManifestSha256)
|
|
1317
|
+
&& typeof value.executionProofSha256 === "string"
|
|
1318
|
+
&& /^[0-9a-f]{64}$/u.test(value.executionProofSha256)
|
|
1319
|
+
&& typeof value.installedAt === "string"
|
|
1320
|
+
&& Number.isFinite(installedAtMs)
|
|
1321
|
+
&& new Date(installedAtMs).toISOString() === value.installedAt
|
|
1322
|
+
// Promotion writes this sentinel before the final filesystem proof is
|
|
1323
|
+
// rebound. It must remain unverifiable even when the filesystem records
|
|
1324
|
+
// both writes with indistinguishable timestamp precision.
|
|
1325
|
+
&& value.installedAt !== PROVISIONAL_RUNTIME_INSTALLED_AT;
|
|
1326
|
+
return valid ? value : undefined;
|
|
1185
1327
|
}
|
|
1186
1328
|
async function verifyInstalledPackage(layout, identity) {
|
|
1187
1329
|
try {
|
|
@@ -1226,37 +1368,45 @@ async function writeClosureManifest(layout) {
|
|
|
1226
1368
|
await chmod(layout.closureManifestPath, 0o600);
|
|
1227
1369
|
return sha256(Buffer.from(contents, "utf8"));
|
|
1228
1370
|
}
|
|
1229
|
-
async function verifyClosureManifest(layout, expectedSha256) {
|
|
1371
|
+
async function verifyClosureManifest(layout, expectedSha256, deps = {}) {
|
|
1230
1372
|
try {
|
|
1231
1373
|
await assertRegularFile(layout.closureManifestPath, "managed runtime closure manifest");
|
|
1232
|
-
const
|
|
1233
|
-
if (sha256
|
|
1374
|
+
const stored = await fingerprintClosureFile(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
1375
|
+
if (stored.sha256 !== expectedSha256)
|
|
1234
1376
|
return false;
|
|
1235
|
-
const parsed = JSON.parse(
|
|
1377
|
+
const parsed = JSON.parse(stored.bytes.toString("utf8"));
|
|
1236
1378
|
if (parsed.schema !== "mono-agent.execution-closure.v1" || !Array.isArray(parsed.entries))
|
|
1237
1379
|
return false;
|
|
1238
|
-
const current = await captureRuntimeClosureManifest(layout);
|
|
1239
|
-
|
|
1380
|
+
const current = await captureRuntimeClosureManifest(layout, deps);
|
|
1381
|
+
const storedAfter = await captureFilesystemPathProof(layout.closureManifestPath, ".mono-agent-closure.json");
|
|
1382
|
+
return sameSourceProofEntry(stored.proof, storedAfter)
|
|
1383
|
+
&& JSON.stringify(parsed) === JSON.stringify(current);
|
|
1240
1384
|
}
|
|
1241
1385
|
catch {
|
|
1242
1386
|
return false;
|
|
1243
1387
|
}
|
|
1244
1388
|
}
|
|
1245
|
-
async function captureRuntimeClosureManifest(layout) {
|
|
1389
|
+
async function captureRuntimeClosureManifest(layout, deps = {}) {
|
|
1246
1390
|
const entries = [];
|
|
1391
|
+
const proofs = [];
|
|
1247
1392
|
const excluded = new Set([resolve(layout.closureManifestPath), resolve(layout.markerPath)]);
|
|
1248
|
-
const visit = async (directory) => {
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1393
|
+
const visit = async (directory, logicalPath) => {
|
|
1394
|
+
const before = await captureFilesystemPathProof(directory, logicalPath);
|
|
1395
|
+
if (before.type !== "directory") {
|
|
1396
|
+
throw new Error(`Managed runtime closure directory ${logicalPath} changed type.`);
|
|
1397
|
+
}
|
|
1398
|
+
const childNames = (await readdir(directory)).sort(compareCodeUnits);
|
|
1399
|
+
for (const name of childNames) {
|
|
1400
|
+
const path = join(directory, name);
|
|
1253
1401
|
if (excluded.has(resolve(path)))
|
|
1254
1402
|
continue;
|
|
1255
|
-
const details = await lstat(path);
|
|
1256
1403
|
const pathRelative = portableRelativePath(layout.installRoot, path);
|
|
1257
|
-
const
|
|
1258
|
-
if (
|
|
1259
|
-
const target =
|
|
1404
|
+
const initial = await captureFilesystemPathProof(path, pathRelative);
|
|
1405
|
+
if (initial.type === "symlink") {
|
|
1406
|
+
const target = initial.target;
|
|
1407
|
+
if (target === undefined) {
|
|
1408
|
+
throw new Error(`Managed runtime symlink ${pathRelative} has no target proof.`);
|
|
1409
|
+
}
|
|
1260
1410
|
if (isAbsolute(target)) {
|
|
1261
1411
|
throw new Error(`Managed runtime symlink ${pathRelative} has an absolute target.`);
|
|
1262
1412
|
}
|
|
@@ -1264,29 +1414,50 @@ async function captureRuntimeClosureManifest(layout) {
|
|
|
1264
1414
|
entries.push({
|
|
1265
1415
|
path: pathRelative,
|
|
1266
1416
|
type: "symlink",
|
|
1267
|
-
mode,
|
|
1268
|
-
target
|
|
1417
|
+
mode: fileMode(BigInt(initial.mode)),
|
|
1418
|
+
target,
|
|
1269
1419
|
});
|
|
1420
|
+
proofs.push({ path, logicalPath: pathRelative, proof: initial });
|
|
1270
1421
|
continue;
|
|
1271
1422
|
}
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
|
|
1423
|
+
if (initial.type === "directory") {
|
|
1424
|
+
const proof = await visit(path, pathRelative);
|
|
1425
|
+
entries.push({ path: pathRelative, type: "directory", mode: fileMode(BigInt(proof.mode)) });
|
|
1275
1426
|
continue;
|
|
1276
1427
|
}
|
|
1277
|
-
if (
|
|
1428
|
+
if (initial.type === "file") {
|
|
1429
|
+
const captured = await fingerprintClosureFile(path, pathRelative);
|
|
1278
1430
|
entries.push({
|
|
1279
1431
|
path: pathRelative,
|
|
1280
1432
|
type: "file",
|
|
1281
|
-
mode,
|
|
1282
|
-
sha256:
|
|
1433
|
+
mode: fileMode(BigInt(captured.proof.mode)),
|
|
1434
|
+
sha256: captured.sha256,
|
|
1283
1435
|
});
|
|
1436
|
+
proofs.push({ path, logicalPath: pathRelative, proof: captured.proof });
|
|
1284
1437
|
continue;
|
|
1285
1438
|
}
|
|
1286
1439
|
throw new Error(`Managed runtime closure contains unsupported entry ${pathRelative}.`);
|
|
1287
1440
|
}
|
|
1441
|
+
const [after, childNamesAfter] = await Promise.all([
|
|
1442
|
+
captureFilesystemPathProof(directory, logicalPath),
|
|
1443
|
+
readdir(directory).then((names) => names.sort(compareCodeUnits)),
|
|
1444
|
+
]);
|
|
1445
|
+
if (after.type !== "directory"
|
|
1446
|
+
|| !sameSourceProofEntry(before, after)
|
|
1447
|
+
|| JSON.stringify(childNames) !== JSON.stringify(childNamesAfter)) {
|
|
1448
|
+
throw new Error(`Managed runtime closure directory ${logicalPath} changed while it was inspected.`);
|
|
1449
|
+
}
|
|
1450
|
+
proofs.push({ path: directory, logicalPath, proof: after });
|
|
1451
|
+
return after;
|
|
1288
1452
|
};
|
|
1289
|
-
await visit(layout.installRoot);
|
|
1453
|
+
await visit(layout.installRoot, ".");
|
|
1454
|
+
await deps.beforeFinalProof?.();
|
|
1455
|
+
for (const expected of proofs) {
|
|
1456
|
+
const current = await captureFilesystemPathProof(expected.path, expected.logicalPath);
|
|
1457
|
+
if (!sameSourceProofEntry(expected.proof, current)) {
|
|
1458
|
+
throw new Error(`Managed runtime closure entry ${expected.logicalPath} changed during closure capture.`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1290
1461
|
entries.sort((left, right) => compareCodeUnits(left.path, right.path));
|
|
1291
1462
|
return { schema: "mono-agent.execution-closure.v1", entries };
|
|
1292
1463
|
}
|
|
@@ -1302,24 +1473,31 @@ async function fingerprintClosureFile(path, pathRelative) {
|
|
|
1302
1473
|
throw error;
|
|
1303
1474
|
}
|
|
1304
1475
|
try {
|
|
1305
|
-
const before = await handle.stat();
|
|
1476
|
+
const before = await handle.stat({ bigint: true });
|
|
1306
1477
|
if (!before.isFile())
|
|
1307
1478
|
throw new Error(`Managed runtime entry ${pathRelative} is not a regular file.`);
|
|
1479
|
+
if (before.nlink !== 1n) {
|
|
1480
|
+
throw new Error(`Managed runtime file ${pathRelative} must not have additional hard links.`);
|
|
1481
|
+
}
|
|
1308
1482
|
const bytes = await handle.readFile();
|
|
1309
|
-
const [after, pathAfter] = await Promise.all([
|
|
1483
|
+
const [after, pathAfter] = await Promise.all([
|
|
1484
|
+
handle.stat({ bigint: true }),
|
|
1485
|
+
lstat(path, { bigint: true }),
|
|
1486
|
+
]);
|
|
1310
1487
|
if (!after.isFile()
|
|
1311
1488
|
|| !pathAfter.isFile()
|
|
1312
1489
|
|| pathAfter.isSymbolicLink()
|
|
1313
|
-
||
|
|
1314
|
-
||
|
|
1315
|
-
|| before
|
|
1316
|
-
||
|
|
1317
|
-
|| before.ctimeMs !== after.ctimeMs
|
|
1318
|
-
|| after.dev !== pathAfter.dev
|
|
1319
|
-
|| after.ino !== pathAfter.ino) {
|
|
1490
|
+
|| after.nlink !== 1n
|
|
1491
|
+
|| pathAfter.nlink !== 1n
|
|
1492
|
+
|| !sameSourceStats(before, after)
|
|
1493
|
+
|| !sameSourceStats(after, pathAfter)) {
|
|
1320
1494
|
throw new Error(`Managed runtime file ${pathRelative} changed while it was fingerprinted.`);
|
|
1321
1495
|
}
|
|
1322
|
-
return
|
|
1496
|
+
return {
|
|
1497
|
+
sha256: sha256(bytes),
|
|
1498
|
+
proof: sourceProofEntry(pathRelative, "file", after),
|
|
1499
|
+
bytes,
|
|
1500
|
+
};
|
|
1323
1501
|
}
|
|
1324
1502
|
finally {
|
|
1325
1503
|
await handle.close();
|