machine-bridge-mcp 2.0.0 → 3.0.0-beta.102
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/CHANGELOG.md +1142 -0
- package/CONTRIBUTING.md +23 -25
- package/GOVERNANCE.md +2 -2
- package/README.md +58 -20
- package/SECURITY.md +161 -120
- package/SUPPORT.md +1 -1
- package/browser-extension/broker-auth.js +83 -0
- package/browser-extension/broker-liveness.js +35 -0
- package/browser-extension/browser-error-boundary.js +96 -0
- package/browser-extension/browser-operations.js +1456 -101
- package/browser-extension/devtools-input.js +195 -34
- package/browser-extension/devtools-observation.js +429 -0
- package/browser-extension/devtools-session.js +48 -0
- package/browser-extension/manifest.json +4 -3
- package/browser-extension/page-automation.js +578 -118
- package/browser-extension/pairing-bootstrap.js +42 -0
- package/browser-extension/pairing.js +28 -12
- package/browser-extension/service-worker.js +126 -106
- package/docs/AGENT_CONTEXT.md +13 -10
- package/docs/ARCHITECTURE.md +159 -67
- package/docs/AUDIT.md +1302 -4
- package/docs/CLIENTS.md +19 -6
- package/docs/COMPUTER_USE.md +297 -0
- package/docs/ENGINEERING.md +72 -13
- package/docs/GETTING_STARTED.md +18 -20
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOCAL_AUTOMATION.md +27 -21
- package/docs/LOGGING.md +40 -19
- package/docs/MANAGED_JOBS.md +37 -34
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +145 -51
- package/docs/OVERVIEW.md +14 -11
- package/docs/POLICY_REFERENCE.md +71 -3
- package/docs/PRIVACY.md +41 -2
- package/docs/PROJECT_STANDARDS.md +29 -6
- package/docs/RELEASING.md +238 -64
- package/docs/TESTING.md +150 -39
- package/docs/THREAT_MODEL.md +164 -67
- package/docs/TOOL_REFERENCE.md +653 -60
- package/docs/UPGRADING.md +78 -18
- package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
- package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +94 -16
- package/scripts/accepted-candidate-tarball.mjs +77 -0
- package/scripts/candidate-runtime-store.mjs +93 -0
- package/scripts/check-plan.mjs +73 -3
- package/scripts/check-runner.mjs +129 -17
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/consumer-package-security.mjs +293 -0
- package/scripts/coverage-check.mjs +251 -15
- package/scripts/coverage-generation.mjs +41 -0
- package/scripts/coverage-range-merge.mjs +41 -0
- package/scripts/foreground-daemon-recovery.mjs +88 -0
- package/scripts/generate-policy-reference.mjs +28 -5
- package/scripts/generate-tool-reference.mjs +1 -1
- package/scripts/generate-worker-types.mjs +21 -11
- package/scripts/github-backlog.mjs +11 -10
- package/scripts/github-push.mjs +62 -16
- package/scripts/github-release-asset.mjs +58 -0
- package/scripts/github-release.mjs +241 -132
- package/scripts/global-package-installation.mjs +68 -0
- package/scripts/hardened-npm-session.mjs +42 -0
- package/scripts/install-published-prerelease.mjs +198 -0
- package/scripts/local-release-acceptance.mjs +103 -45
- package/scripts/macos-background-input-smoke.mjs +515 -0
- package/scripts/network-retry.mjs +8 -1
- package/scripts/npm-global-prefix.mjs +27 -0
- package/scripts/npm-publication-policy.mjs +17 -0
- package/scripts/official-mcp-conformance.mjs +267 -0
- package/scripts/persistent-activation-process.mjs +101 -0
- package/scripts/prepare-pinned-npm.mjs +12 -69
- package/scripts/prerelease-activation.mjs +160 -0
- package/scripts/privacy-check.mjs +14 -6
- package/scripts/promotion-digest.mjs +128 -0
- package/scripts/publish-npm.mjs +260 -0
- package/scripts/published-release.mjs +132 -0
- package/scripts/release-acceptance.mjs +62 -54
- package/scripts/release-browser-extension-store.mjs +172 -0
- package/scripts/release-candidate-manifest.mjs +52 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-diagnostic.mjs +41 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-oauth-canary-core.mjs +325 -0
- package/scripts/release-oauth-canary-evidence.mjs +100 -0
- package/scripts/release-oauth-canary.mjs +127 -0
- package/scripts/release-publication-guard.mjs +67 -0
- package/scripts/release-soak.mjs +284 -0
- package/scripts/release-state.mjs +1 -1
- package/scripts/run-checks.mjs +44 -7
- package/scripts/run-worker-dry-run.mjs +21 -0
- package/scripts/sarif-security-gate.mjs +2 -1
- package/scripts/sbom-check.mjs +106 -0
- package/scripts/start-release-candidate.mjs +267 -44
- package/scripts/syntax-check.mjs +36 -14
- package/scripts/verification-generation-guard.mjs +15 -0
- package/scripts/verification-idle-sleep-guard.mjs +37 -0
- package/scripts/verification-state.mjs +108 -0
- package/scripts/wrangler-command-lifecycle.mjs +115 -0
- package/src/local/account-access.mjs +13 -1
- package/src/local/account-admin-response.mjs +92 -0
- package/src/local/account-admin.mjs +52 -19
- package/src/local/agent-context-projection.mjs +26 -7
- package/src/local/agent-context.mjs +88 -49
- package/src/local/agent-skill-discovery.mjs +16 -5
- package/src/local/app-automation-macos-jxa.mjs +350 -0
- package/src/local/app-automation.mjs +873 -163
- package/src/local/application-capability-projection.mjs +24 -0
- package/src/local/authority-context.mjs +106 -0
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/browser-bridge-http.mjs +5 -3
- package/src/local/browser-bridge.mjs +109 -31
- package/src/local/browser-broker-auth-http.mjs +67 -0
- package/src/local/browser-broker-auth.mjs +98 -0
- package/src/local/browser-broker-routes.mjs +63 -20
- package/src/local/browser-broker-server.mjs +17 -7
- package/src/local/browser-command.mjs +79 -30
- package/src/local/browser-computer-observation-service.mjs +266 -0
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-path.mjs +22 -0
- package/src/local/browser-extension-protocol.mjs +50 -11
- package/src/local/browser-operation-service.mjs +147 -117
- package/src/local/browser-pairing-grant.mjs +109 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-launch.mjs +58 -0
- package/src/local/browser-pairing-store.mjs +85 -48
- package/src/local/browser-request-registry.mjs +40 -14
- package/src/local/browser-request-settlement.mjs +78 -0
- package/src/local/browser-resource-input.mjs +74 -0
- package/src/local/browser-trusted-input-health.mjs +63 -0
- package/src/local/call-authority.mjs +21 -0
- package/src/local/call-capacity.mjs +45 -0
- package/src/local/call-registry-drain.mjs +54 -0
- package/src/local/call-registry.mjs +30 -15
- package/src/local/capability-observer.mjs +5 -0
- package/src/local/capability-ranking.mjs +3 -2
- package/src/local/child-process-settlement.mjs +132 -0
- package/src/local/cli-account-admin.mjs +40 -6
- package/src/local/cli-activate.mjs +162 -0
- package/src/local/cli-local-admin.mjs +20 -45
- package/src/local/cli-options.mjs +12 -13
- package/src/local/cli-ready-output.mjs +52 -0
- package/src/local/cli-service.mjs +91 -11
- package/src/local/cli.mjs +232 -150
- package/src/local/computer-use-application-observation.mjs +224 -0
- package/src/local/computer-use-arguments.mjs +270 -0
- package/src/local/computer-use-deadline.mjs +50 -0
- package/src/local/computer-use-dispatch-settlement.mjs +40 -0
- package/src/local/computer-use-expectation.mjs +171 -0
- package/src/local/computer-use-observation.mjs +769 -0
- package/src/local/computer-use-recovery.mjs +82 -0
- package/src/local/computer-use-result-budget.mjs +145 -0
- package/src/local/computer-use-snapshot-store.mjs +67 -0
- package/src/local/computer-use.mjs +2255 -0
- package/src/local/daemon-http-relay-auth.mjs +26 -0
- package/src/local/daemon-http-relay-connection.mjs +298 -0
- package/src/local/daemon-http-relay-request.mjs +64 -0
- package/src/local/daemon-http-relay-sequence.mjs +49 -0
- package/src/local/daemon-process.mjs +40 -7
- package/src/local/delegated-process-sandbox.mjs +148 -0
- package/src/local/device-identity.mjs +156 -49
- package/src/local/device-root-provider.mjs +81 -0
- package/src/local/directory-metadata.mjs +47 -0
- package/src/local/doctor-reporting.mjs +23 -0
- package/src/local/durable-process-spec.mjs +80 -0
- package/src/local/errors.mjs +25 -15
- package/src/local/exclusive-file.mjs +74 -26
- package/src/local/execution-limits.mjs +9 -0
- package/src/local/execution-routing.mjs +276 -0
- package/src/local/execution-surface.mjs +23 -0
- package/src/local/file-mutation-coordinator.mjs +47 -0
- package/src/local/file-snapshot-preservation.mjs +24 -0
- package/src/local/filesystem-identity.mjs +54 -0
- package/src/local/fixed-process-environment.mjs +21 -0
- package/src/local/full-access-test.mjs +31 -13
- package/src/local/git-commit.mjs +69 -0
- package/src/local/git-config-safety.mjs +55 -0
- package/src/local/git-log-parser.mjs +41 -0
- package/src/local/git-metadata-boundary.mjs +77 -0
- package/src/local/git-metadata-tree-safety.mjs +59 -0
- package/src/local/git-operation-state.mjs +19 -0
- package/src/local/git-service.mjs +165 -30
- package/src/local/hardened-npm-download-timeout.mjs +46 -0
- package/src/local/hardened-npm-download.mjs +109 -0
- package/src/local/hardened-npm-extract.mjs +20 -0
- package/src/local/hardened-npm-verification.mjs +104 -0
- package/src/local/hardened-npm.mjs +199 -0
- package/src/local/job-runner.mjs +244 -89
- package/src/local/lifecycle.mjs +6 -0
- package/src/local/log.mjs +19 -45
- package/src/local/macos-background-input.mjs +398 -0
- package/src/local/macos-idle-sleep-assertion.mjs +65 -0
- package/src/local/macos-trust-broker.mjs +404 -0
- package/src/local/managed-job-cancellation.mjs +42 -0
- package/src/local/managed-job-capacity.mjs +16 -0
- package/src/local/managed-job-directory-generation.mjs +60 -0
- package/src/local/managed-job-directory.mjs +75 -0
- package/src/local/managed-job-durable-process.mjs +49 -0
- package/src/local/managed-job-lock.mjs +46 -14
- package/src/local/managed-job-plan.mjs +38 -14
- package/src/local/managed-job-projection.mjs +23 -15
- package/src/local/managed-job-retention.mjs +128 -0
- package/src/local/managed-job-runner-claim.mjs +70 -0
- package/src/local/managed-job-runner.mjs +35 -10
- package/src/local/managed-job-storage.mjs +22 -6
- package/src/local/managed-job-terminal-maintenance.mjs +40 -0
- package/src/local/managed-job-terminal.mjs +150 -0
- package/src/local/managed-jobs.mjs +424 -287
- package/src/local/npm-cli.mjs +42 -0
- package/src/local/npm-environment.mjs +37 -0
- package/src/local/operation-authorization.mjs +68 -324
- package/src/local/operation-risk.mjs +51 -4
- package/src/local/owner-state-lock.mjs +118 -0
- package/src/local/package-identity.mjs +9 -0
- package/src/local/patch.mjs +20 -18
- package/src/local/path-inspection.mjs +23 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/private-toolchain-integrity.mjs +54 -0
- package/src/local/process-execution.mjs +87 -75
- package/src/local/process-foreground-timeout.mjs +50 -0
- package/src/local/process-identity.mjs +41 -7
- package/src/local/process-nonreplayable-settlement.mjs +50 -0
- package/src/local/process-result-projection.mjs +22 -0
- package/src/local/process-session-events.mjs +47 -0
- package/src/local/process-session-remote-activity.mjs +10 -0
- package/src/local/process-session-termination.mjs +73 -0
- package/src/local/process-sessions.mjs +144 -112
- package/src/local/process-tracker.mjs +140 -13
- package/src/local/process-tree-ownership-types.d.ts +37 -0
- package/src/local/process-tree-ownership.mjs +57 -0
- package/src/local/process-tree-signal.mjs +34 -0
- package/src/local/process-tree-snapshot.mjs +77 -0
- package/src/local/process-tree-supervisor.mjs +69 -0
- package/src/local/process-tree.mjs +11 -53
- package/src/local/project-metadata.mjs +19 -6
- package/src/local/relay-call-recovery.mjs +58 -38
- package/src/local/relay-connection-classification.mjs +131 -0
- package/src/local/relay-connection-support.mjs +91 -0
- package/src/local/relay-connection.mjs +179 -272
- package/src/local/relay-diagnostics.mjs +91 -0
- package/src/local/relay-heartbeat.mjs +119 -0
- package/src/local/relay-liveness.mjs +109 -0
- package/src/local/relay-peer-diagnostics.mjs +29 -0
- package/src/local/release-runtime-lock.mjs +18 -0
- package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/resilient-relay-connection.mjs +178 -0
- package/src/local/resource-admission-diagnostics.mjs +52 -0
- package/src/local/resource-admission-policy.mjs +115 -0
- package/src/local/resource-admission.mjs +339 -0
- package/src/local/resource-build-root.mjs +83 -0
- package/src/local/resource-cargo-concurrency.mjs +38 -0
- package/src/local/resource-cmake-concurrency.mjs +42 -0
- package/src/local/resource-command-concurrency.mjs +37 -0
- package/src/local/resource-command-profile.mjs +176 -0
- package/src/local/resource-coordinator-accounting.mjs +21 -0
- package/src/local/resource-cpu-window.mjs +20 -0
- package/src/local/resource-disk-headroom.mjs +13 -0
- package/src/local/resource-elastic-memory.mjs +19 -0
- package/src/local/resource-elastic-request.mjs +36 -0
- package/src/local/resource-foreground-wait.mjs +29 -0
- package/src/local/resource-go-concurrency.mjs +43 -0
- package/src/local/resource-gradle-concurrency.mjs +57 -0
- package/src/local/resource-host-cache.mjs +39 -0
- package/src/local/resource-host-darwin.mjs +54 -0
- package/src/local/resource-host-linux.mjs +35 -0
- package/src/local/resource-host-sample-file.mjs +23 -0
- package/src/local/resource-host-snapshot.mjs +99 -0
- package/src/local/resource-lease-accounting.mjs +119 -0
- package/src/local/resource-light-command.mjs +6 -0
- package/src/local/resource-make-concurrency.mjs +42 -0
- package/src/local/resource-maven-concurrency.mjs +38 -0
- package/src/local/resource-ninja-command-concurrency.mjs +20 -0
- package/src/local/resource-ninja-concurrency.mjs +28 -0
- package/src/local/resource-operations.mjs +26 -10
- package/src/local/resource-pressure.mjs +67 -0
- package/src/local/resource-probe-command.mjs +35 -0
- package/src/local/resource-process-admission.mjs +59 -0
- package/src/local/resource-process-ancestry-cache.mjs +43 -0
- package/src/local/resource-process-ancestry.mjs +32 -0
- package/src/local/resource-process-priority.mjs +15 -0
- package/src/local/resource-project-key.mjs +53 -0
- package/src/local/resource-pytest-concurrency.mjs +33 -0
- package/src/local/resource-release-control-classification.mjs +14 -0
- package/src/local/resource-release-control-executable.mjs +70 -0
- package/src/local/resource-release-control-workspace.mjs +41 -0
- package/src/local/resource-request-contract.mjs +30 -0
- package/src/local/resource-script-classification.mjs +44 -0
- package/src/local/resource-shell-analysis.mjs +58 -0
- package/src/local/resource-staging-recovery.mjs +97 -0
- package/src/local/resource-swift-concurrency.mjs +31 -0
- package/src/local/resource-transaction-lock.mjs +189 -0
- package/src/local/resource-wait.mjs +48 -0
- package/src/local/resource-waiters.mjs +139 -0
- package/src/local/resource-xcode-command.mjs +7 -0
- package/src/local/resource-xcode-concurrency.mjs +34 -0
- package/src/local/resource-xcode-non-build.mjs +19 -0
- package/src/local/runtime-activation.mjs +700 -0
- package/src/local/runtime-activity-projection.mjs +48 -0
- package/src/local/runtime-capabilities.mjs +48 -15
- package/src/local/runtime-diagnostic-state.mjs +14 -0
- package/src/local/runtime-diagnostics.mjs +40 -13
- package/src/local/runtime-info-projection.mjs +60 -0
- package/src/local/runtime-info-relay-projection.mjs +21 -0
- package/src/local/runtime-paths.mjs +2 -1
- package/src/local/runtime-process-routing.mjs +33 -0
- package/src/local/runtime-relay-connection-options.mjs +44 -0
- package/src/local/runtime-relay-control.mjs +98 -0
- package/src/local/runtime-relay.mjs +26 -50
- package/src/local/runtime-reporting.mjs +37 -21
- package/src/local/runtime-resource-service.mjs +77 -0
- package/src/local/runtime-tool-handlers.mjs +13 -10
- package/src/local/runtime.mjs +244 -208
- package/src/local/secure-file.mjs +73 -20
- package/src/local/security-audit-dispatch.mjs +35 -0
- package/src/local/security-audit-log.mjs +215 -0
- package/src/local/security-audit-state.mjs +139 -0
- package/src/local/security-audit-storage.mjs +135 -0
- package/src/local/security-audit-warning.mjs +31 -0
- package/src/local/security-audit-worker.mjs +87 -0
- package/src/local/service-convergence.mjs +42 -1
- package/src/local/service-definition.mjs +24 -0
- package/src/local/service-environment.mjs +18 -10
- package/src/local/service-owner.mjs +146 -0
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +64 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-runtime-convergence.mjs +52 -0
- package/src/local/service-runtime.mjs +105 -0
- package/src/local/service-status.mjs +60 -0
- package/src/local/service.mjs +385 -92
- package/src/local/shell.mjs +39 -19
- package/src/local/ssh-key.mjs +158 -42
- package/src/local/state-inventory.mjs +50 -24
- package/src/local/state-owner-lock-inventory.mjs +23 -0
- package/src/local/state-root-owned-namespaces.mjs +83 -0
- package/src/local/state-root-retirement.mjs +89 -0
- package/src/local/state.mjs +386 -96
- package/src/local/stdio.mjs +96 -66
- package/src/local/support-state-projection.mjs +30 -0
- package/src/local/system-network-route.mjs +76 -0
- package/src/local/systemd-removal.mjs +20 -0
- package/src/local/tool-executor.mjs +85 -20
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/toolchain-operation-lock.mjs +41 -0
- package/src/local/tools.mjs +7 -5
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service-convergence.mjs +49 -0
- package/src/local/windows-service.mjs +52 -45
- package/src/local/worker-deployment-fingerprint.mjs +102 -0
- package/src/local/worker-deployment.mjs +48 -60
- package/src/local/worker-health.mjs +12 -4
- package/src/local/worker-secret-file.mjs +37 -17
- package/src/local/workspace-file-service.mjs +156 -239
- package/src/local/workspace-file-transaction.mjs +189 -0
- package/src/local/workspace-search.mjs +55 -0
- package/src/local/wrangler-toolchain/package-lock.json +1505 -0
- package/src/local/wrangler-toolchain/package.json +23 -0
- package/src/local/wrangler-toolchain-verification.mjs +135 -0
- package/src/local/wrangler-toolchain.mjs +188 -0
- package/src/shared/access-contract.json +7 -1
- package/src/shared/activation-recovery.mjs +49 -0
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/authority-revocation.d.mts +9 -0
- package/src/shared/authority-revocation.mjs +28 -0
- package/src/shared/daemon-auth.d.mts +11 -0
- package/src/shared/daemon-auth.mjs +17 -0
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/foreground-timeout.d.mts +15 -0
- package/src/shared/foreground-timeout.mjs +70 -0
- package/src/shared/log-redaction.d.mts +9 -0
- package/src/shared/log-redaction.mjs +51 -0
- package/src/shared/mcp-protocol.d.mts +27 -0
- package/src/shared/mcp-protocol.mjs +275 -0
- package/src/shared/project-overview-projection.d.mts +4 -0
- package/src/shared/project-overview-projection.mjs +96 -0
- package/src/shared/relay-contract.json +28 -0
- package/src/shared/result-projection.d.mts +2 -1
- package/src/shared/result-projection.mjs +13 -2
- package/src/shared/server-metadata.json +15 -9
- package/src/shared/tool-argument-validation.d.mts +17 -0
- package/src/shared/tool-argument-validation.mjs +325 -0
- package/src/shared/tool-call-capacity.d.mts +41 -0
- package/src/shared/tool-call-capacity.mjs +59 -0
- package/src/shared/tool-catalog.json +626 -57
- package/src/worker/access.ts +12 -0
- package/src/worker/account-admin.ts +55 -33
- package/src/worker/authority-revocations.ts +174 -0
- package/src/worker/authority.ts +52 -6
- package/src/worker/daemon-auth.ts +49 -68
- package/src/worker/daemon-channel.ts +36 -0
- package/src/worker/daemon-http-auth.ts +63 -0
- package/src/worker/daemon-http-channel.ts +120 -0
- package/src/worker/daemon-http-controller.ts +163 -0
- package/src/worker/daemon-http-protocol.ts +62 -0
- package/src/worker/daemon-http-registry.ts +82 -0
- package/src/worker/daemon-last-observation.ts +30 -0
- package/src/worker/daemon-liveness.ts +8 -6
- package/src/worker/daemon-ready-dispatch.ts +17 -0
- package/src/worker/daemon-ready-messages.ts +73 -0
- package/src/worker/daemon-ready-waiters.ts +79 -0
- package/src/worker/daemon-recovery-budget.ts +13 -0
- package/src/worker/daemon-registry.ts +58 -0
- package/src/worker/daemon-relay-diagnostics.ts +127 -0
- package/src/worker/daemon-socket-attachment.ts +55 -0
- package/src/worker/daemon-sockets.ts +72 -62
- package/src/worker/daemon-status.ts +38 -0
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +157 -0
- package/src/worker/durable-process-timeout.ts +29 -0
- package/src/worker/errors.ts +57 -0
- package/src/worker/http.ts +112 -26
- package/src/worker/index.ts +446 -440
- package/src/worker/mcp-access.ts +57 -0
- package/src/worker/mcp-controller.ts +188 -0
- package/src/worker/mcp-http-accept.ts +11 -0
- package/src/worker/mcp-http-contract.ts +322 -0
- package/src/worker/mcp-initialization-compat.ts +141 -0
- package/src/worker/mcp-jsonrpc.ts +11 -29
- package/src/worker/mcp-removed-protocol.ts +17 -0
- package/src/worker/mcp-response-proxy.ts +135 -0
- package/src/worker/mcp-response-stream.ts +65 -0
- package/src/worker/mcp-stale-schema-compat.ts +57 -0
- package/src/worker/mcp-stream-proxy-contract.ts +48 -0
- package/src/worker/mcp-tool-call-input.ts +23 -0
- package/src/worker/nonce-store.ts +33 -35
- package/src/worker/oauth-authorization-page.ts +21 -1
- package/src/worker/oauth-client-admin.ts +52 -0
- package/src/worker/oauth-client-contract.ts +38 -0
- package/src/worker/oauth-controller.ts +107 -47
- package/src/worker/oauth-field-contract.ts +31 -0
- package/src/worker/oauth-record-contract.ts +7 -0
- package/src/worker/oauth-refresh-authority.ts +27 -0
- package/src/worker/oauth-refresh-exchange.ts +162 -0
- package/src/worker/oauth-refresh-families.ts +130 -27
- package/src/worker/oauth-refresh-persistence.ts +116 -0
- package/src/worker/oauth-state.ts +33 -18
- package/src/worker/oauth-store-validation.ts +131 -0
- package/src/worker/oauth-token-derivation.ts +33 -0
- package/src/worker/oauth-token-issuance.ts +119 -0
- package/src/worker/oauth-tokens.ts +56 -190
- package/src/worker/observability.ts +38 -5
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-capacity.ts +108 -0
- package/src/worker/pending-call-contract.ts +42 -5
- package/src/worker/pending-call-deadlines.ts +75 -0
- package/src/worker/pending-calls.ts +131 -125
- package/src/worker/runtime-alarm-storage.ts +37 -0
- package/src/worker/runtime-alarm.ts +136 -0
- package/src/worker/server-info-activity.ts +36 -0
- package/src/worker/server-info-tool-delivery.ts +15 -0
- package/src/worker/server-info.ts +168 -0
- package/src/worker/tool-catalog.ts +47 -1
- package/src/worker/tool-timeout.ts +75 -14
- package/src/worker/websocket-protocol.ts +26 -2
- package/src/worker/worker-edge-guard.ts +90 -0
- package/src/worker/worker-edge-log.ts +65 -0
- package/src/worker/worker-entry.ts +52 -0
- package/src/worker/worker-mcp-config.ts +23 -0
- package/src/worker/worker-metadata.ts +48 -0
- package/src/worker/worker-rate-limit-key.ts +30 -0
- package/src/worker/worker-runtime-config.ts +20 -0
- package/src/worker/worker-static-routes.ts +54 -0
- package/src/worker/worker-task-lifetime.ts +18 -0
- package/tsconfig.local.json +17 -1
- package/wrangler.jsonc +13 -0
- package/src/local/cli-approval.mjs +0 -117
- package/src/local/operation-state-lock.mjs +0 -92
- package/src/worker/mcp-session.ts +0 -72
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { runExecutable } from "../src/local/shell.mjs";
|
|
6
|
+
import { nestedNpmEnvironment } from "../src/local/npm-environment.mjs";
|
|
7
|
+
import { readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
|
|
8
|
+
|
|
9
|
+
const MAX_PROXY_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
11
|
+
|
|
12
|
+
export async function runOfficialMcpConformance(options) {
|
|
13
|
+
const checkout = validateConformanceCheckout(options.checkout);
|
|
14
|
+
const upstream = validatedUpstream(options.upstream);
|
|
15
|
+
const accessToken = boundedSecret(options.accessToken, "access token", 16 * 1024);
|
|
16
|
+
const scenario = requiredText(options.scenario, "conformance scenario");
|
|
17
|
+
const specVersion = String(options.specVersion || "2026-07-28");
|
|
18
|
+
const timeoutMs = positiveInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
19
|
+
const proxy = await startBearerProxy({ upstream, accessToken });
|
|
20
|
+
try {
|
|
21
|
+
const npmCli = requiredText(process.env.npm_execpath, "npm_execpath");
|
|
22
|
+
const result = await runCommand({
|
|
23
|
+
command: process.execPath,
|
|
24
|
+
args: [
|
|
25
|
+
npmCli, "start", "--workspaces=false", "--global=false", "--ignore-scripts=false", "--if-present=false", "--prefix", checkout, "--", "server", "--url", `${proxy.origin}/mcp`,
|
|
26
|
+
"--scenario", scenario, "--spec-version", specVersion,
|
|
27
|
+
...(options.expectedFailures ? ["--expected-failures", String(options.expectedFailures)] : []),
|
|
28
|
+
...(options.verbose === true ? ["--verbose"] : []),
|
|
29
|
+
],
|
|
30
|
+
cwd: checkout,
|
|
31
|
+
timeoutMs,
|
|
32
|
+
});
|
|
33
|
+
return Object.freeze({ ...result, scenario, specVersion, proxyOrigin: proxy.origin });
|
|
34
|
+
} finally {
|
|
35
|
+
await proxy.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function startBearerProxy({ upstream, accessToken }) {
|
|
40
|
+
const server = createServer((request, response) => {
|
|
41
|
+
void proxyRequest(request, response, upstream, accessToken);
|
|
42
|
+
});
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
server.once("error", reject);
|
|
45
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
46
|
+
});
|
|
47
|
+
const address = server.address();
|
|
48
|
+
if (!address || typeof address === "string") throw new Error("conformance proxy did not bind a TCP port");
|
|
49
|
+
return {
|
|
50
|
+
origin: `http://127.0.0.1:${address.port}`,
|
|
51
|
+
close: () => new Promise((resolve, reject) => {
|
|
52
|
+
server.close((error) => { if (error) reject(error); else resolve(); });
|
|
53
|
+
server.closeAllConnections?.();
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function proxyRequest(request, response, upstream, accessToken) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
request.once("aborted", () => controller.abort());
|
|
61
|
+
response.once("close", () => { if (!response.writableEnded) controller.abort(); });
|
|
62
|
+
try {
|
|
63
|
+
const body = await readBoundedBody(request, MAX_PROXY_REQUEST_BYTES);
|
|
64
|
+
const headers = new Headers();
|
|
65
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
66
|
+
if (value === undefined || ["host", "connection", "content-length", "transfer-encoding"].includes(name.toLowerCase())) continue;
|
|
67
|
+
if (Array.isArray(value)) for (const item of value) headers.append(name, item);
|
|
68
|
+
else headers.set(name, value);
|
|
69
|
+
}
|
|
70
|
+
headers.set("authorization", `Bearer ${accessToken}`);
|
|
71
|
+
const target = conformanceProxyTarget(request.url, upstream);
|
|
72
|
+
const upstreamResponse = await fetch(target, {
|
|
73
|
+
method: request.method,
|
|
74
|
+
headers,
|
|
75
|
+
body: body.length ? body : undefined,
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
redirect: "manual",
|
|
78
|
+
});
|
|
79
|
+
const responseHeaders = {};
|
|
80
|
+
upstreamResponse.headers.forEach((value, name) => {
|
|
81
|
+
if (!["connection", "content-encoding", "content-length", "transfer-encoding"].includes(name.toLowerCase())) responseHeaders[name] = value;
|
|
82
|
+
});
|
|
83
|
+
response.writeHead(upstreamResponse.status, responseHeaders);
|
|
84
|
+
if (!upstreamResponse.body) return response.end();
|
|
85
|
+
const reader = upstreamResponse.body.getReader();
|
|
86
|
+
try {
|
|
87
|
+
for (;;) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done) break;
|
|
90
|
+
if (value && !response.write(Buffer.from(value))) {
|
|
91
|
+
await new Promise((resolve) => { response.once("drain", resolve); });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
response.end();
|
|
95
|
+
} finally {
|
|
96
|
+
reader.releaseLock();
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (controller.signal.aborted) return response.destroy();
|
|
100
|
+
if (!response.headersSent) response.writeHead(error?.code === "request_too_large" ? 413 : 502, { "content-type": "application/json" });
|
|
101
|
+
response.end(JSON.stringify({ error: "conformance_proxy_failure" }));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readBoundedBody(request, maximumBytes) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const chunks = [];
|
|
108
|
+
let bytes = 0;
|
|
109
|
+
let settled = false;
|
|
110
|
+
const finish = (callback, value) => {
|
|
111
|
+
if (settled) return;
|
|
112
|
+
settled = true;
|
|
113
|
+
request.removeListener("data", onData);
|
|
114
|
+
request.removeListener("end", onEnd);
|
|
115
|
+
request.removeListener("aborted", onAborted);
|
|
116
|
+
request.removeListener("error", onError);
|
|
117
|
+
callback(value);
|
|
118
|
+
};
|
|
119
|
+
const onData = (chunk) => {
|
|
120
|
+
bytes += chunk.length;
|
|
121
|
+
if (bytes > maximumBytes) {
|
|
122
|
+
const error = new Error("conformance proxy request is too large");
|
|
123
|
+
error.code = "request_too_large";
|
|
124
|
+
finish(reject, error);
|
|
125
|
+
request.destroy();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
chunks.push(chunk);
|
|
129
|
+
};
|
|
130
|
+
const onEnd = () => finish(resolve, Buffer.concat(chunks, bytes));
|
|
131
|
+
const onAborted = () => finish(reject, proxyInputError("conformance proxy request was aborted"));
|
|
132
|
+
const onError = (error) => finish(reject, error);
|
|
133
|
+
request.on("data", onData);
|
|
134
|
+
request.once("end", onEnd);
|
|
135
|
+
request.once("aborted", onAborted);
|
|
136
|
+
request.once("error", onError);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runCommand({ command, args, cwd, timeoutMs }) {
|
|
141
|
+
const result = await runExecutable(command, args, {
|
|
142
|
+
cwd,
|
|
143
|
+
env: { ...nestedNpmEnvironment(process.env), NO_COLOR: "1", CI: "1" },
|
|
144
|
+
capture: true,
|
|
145
|
+
allowFailure: true,
|
|
146
|
+
timeoutMs,
|
|
147
|
+
maxOutputBytes: 4 * 1024 * 1024,
|
|
148
|
+
});
|
|
149
|
+
return Object.freeze({ ...result, signal: null });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function validateConformanceCheckout(value, options = {}) {
|
|
153
|
+
const inspect = options.lstatSync || lstatSync;
|
|
154
|
+
const canonicalize = options.realpathSync || realpathSync.native;
|
|
155
|
+
const readBounded = options.readBoundedRegularFileSync || readBoundedRegularFileSync;
|
|
156
|
+
const requested = resolve(requiredText(value, "conformance checkout"));
|
|
157
|
+
const requestedInfo = inspectConformancePath(requested, "conformance checkout", inspect);
|
|
158
|
+
if (!requestedInfo) throw new Error("conformance checkout does not exist");
|
|
159
|
+
if (requestedInfo.isSymbolicLink() || !requestedInfo.isDirectory()) {
|
|
160
|
+
throw new Error("conformance checkout must be a real directory");
|
|
161
|
+
}
|
|
162
|
+
const checkout = canonicalize(requested);
|
|
163
|
+
for (const file of ["package.json", "package-lock.json"]) {
|
|
164
|
+
const path = join(checkout, file);
|
|
165
|
+
const info = inspectConformancePath(path, `conformance checkout ${file}`, inspect);
|
|
166
|
+
if (!info) throw new Error(`conformance checkout omits ${file}`);
|
|
167
|
+
if (info.isSymbolicLink() || !info.isFile()) throw new Error(`conformance checkout ${file} must be a regular file`);
|
|
168
|
+
}
|
|
169
|
+
let packageState;
|
|
170
|
+
try {
|
|
171
|
+
packageState = JSON.parse(readBounded(join(checkout, "package.json"), 1024 * 1024, "conformance checkout package.json", {
|
|
172
|
+
verifyPathIdentity: true,
|
|
173
|
+
rejectMultipleLinks: true,
|
|
174
|
+
}).toString("utf8"));
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof SyntaxError) throw new Error("conformance checkout package.json is invalid", { cause: error });
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
if (typeof packageState?.scripts?.start !== "string" || !packageState.scripts.start.trim()) {
|
|
180
|
+
throw new Error("conformance checkout omits its start command");
|
|
181
|
+
}
|
|
182
|
+
const nodeModules = join(checkout, "node_modules");
|
|
183
|
+
const nodeModulesInfo = inspectConformancePath(nodeModules, "conformance checkout node_modules", inspect);
|
|
184
|
+
if (!nodeModulesInfo) {
|
|
185
|
+
throw new Error("conformance checkout dependencies are not installed; run npm ci --ignore-scripts in the checkout");
|
|
186
|
+
}
|
|
187
|
+
if (nodeModulesInfo.isSymbolicLink() || !nodeModulesInfo.isDirectory()) {
|
|
188
|
+
throw new Error("conformance checkout node_modules must be a real directory");
|
|
189
|
+
}
|
|
190
|
+
return checkout;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function inspectConformancePath(path, label, inspect) {
|
|
194
|
+
try { return inspect(path); }
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error?.code === "ENOENT") return null;
|
|
197
|
+
throw new Error(`${label} could not be inspected`, { cause: error });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function conformanceProxyTarget(requestTarget, upstream) {
|
|
202
|
+
const raw = String(requestTarget || "/mcp");
|
|
203
|
+
if (Buffer.byteLength(raw) > 8192) throw proxyInputError("conformance proxy request target is too large");
|
|
204
|
+
let parsed;
|
|
205
|
+
try { parsed = new URL(raw, "http://proxy.invalid"); }
|
|
206
|
+
catch { throw proxyInputError("conformance proxy request target is invalid"); }
|
|
207
|
+
if (parsed.origin !== "http://proxy.invalid" || parsed.hash) {
|
|
208
|
+
throw proxyInputError("conformance proxy requires a relative request target");
|
|
209
|
+
}
|
|
210
|
+
if (parsed.pathname !== "/mcp") throw proxyInputError("conformance proxy accepts only its MCP endpoint");
|
|
211
|
+
const target = new URL(upstream.href);
|
|
212
|
+
if (parsed.search) target.search = parsed.search;
|
|
213
|
+
return target;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function validatedUpstream(value) {
|
|
217
|
+
let upstream;
|
|
218
|
+
try { upstream = new URL(requiredText(value, "upstream MCP URL")); }
|
|
219
|
+
catch { throw new Error("upstream MCP URL is invalid"); }
|
|
220
|
+
if (upstream.username || upstream.password || upstream.hash) throw new Error("upstream MCP URL must not contain credentials or a fragment");
|
|
221
|
+
const loopback = upstream.hostname === "localhost" || upstream.hostname === "127.0.0.1" || upstream.hostname === "[::1]";
|
|
222
|
+
if (upstream.protocol !== "https:" && !(upstream.protocol === "http:" && loopback)) {
|
|
223
|
+
throw new Error("upstream MCP URL must use HTTPS or loopback HTTP");
|
|
224
|
+
}
|
|
225
|
+
return upstream;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function boundedSecret(value, label, maximumBytes) {
|
|
229
|
+
const text = requiredText(value, label);
|
|
230
|
+
if (Buffer.byteLength(text) > maximumBytes) throw new Error(`${label} is too large`);
|
|
231
|
+
return text;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function proxyInputError(message) {
|
|
235
|
+
const error = new Error(message);
|
|
236
|
+
error.code = "invalid_proxy_input";
|
|
237
|
+
return error;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function requiredText(value, label) {
|
|
241
|
+
const text = String(value || "").trim();
|
|
242
|
+
if (!text) throw new Error(`${label} is required`);
|
|
243
|
+
return text;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function positiveInteger(value, fallback) {
|
|
247
|
+
const number = Number(value);
|
|
248
|
+
return Number.isInteger(number) && number > 0 ? number : fallback;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function main() {
|
|
252
|
+
const result = await runOfficialMcpConformance({
|
|
253
|
+
checkout: process.env.MBM_OFFICIAL_CONFORMANCE_CHECKOUT,
|
|
254
|
+
upstream: process.env.MBM_OFFICIAL_CONFORMANCE_UPSTREAM,
|
|
255
|
+
accessToken: process.env.MBM_OFFICIAL_CONFORMANCE_ACCESS_TOKEN,
|
|
256
|
+
scenario: process.env.MBM_OFFICIAL_CONFORMANCE_SCENARIO,
|
|
257
|
+
specVersion: process.env.MBM_OFFICIAL_CONFORMANCE_SPEC_VERSION || "2026-07-28",
|
|
258
|
+
timeoutMs: process.env.MBM_OFFICIAL_CONFORMANCE_TIMEOUT_MS,
|
|
259
|
+
verbose: process.env.MBM_OFFICIAL_CONFORMANCE_VERBOSE === "1",
|
|
260
|
+
expectedFailures: process.env.MBM_OFFICIAL_CONFORMANCE_BASELINE,
|
|
261
|
+
});
|
|
262
|
+
process.stdout.write(result.stdout);
|
|
263
|
+
process.stderr.write(result.stderr);
|
|
264
|
+
process.exitCode = result.code;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) await main();
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { normalizeActivationRecovery } from "../src/shared/activation-recovery.mjs";
|
|
2
|
+
import { EXECUTION_SURFACE, executionSurface } from "../src/local/execution-surface.mjs";
|
|
3
|
+
import { runWrangler as defaultRunWrangler } from "../src/local/shell.mjs";
|
|
4
|
+
|
|
5
|
+
export function assertPersistentActivationExecutionSurface(environment = process.env) {
|
|
6
|
+
const surface = executionSurface(environment);
|
|
7
|
+
if (!surface || surface === EXECUTION_SURFACE.managedJob) return surface || "local";
|
|
8
|
+
const error = new Error(
|
|
9
|
+
`persistent activation cannot run from ${surface}; use a durable managed job or an ordinary local terminal because activation intentionally replaces the current Machine Bridge daemon`,
|
|
10
|
+
);
|
|
11
|
+
error.code = "unsafe_activation_execution_surface";
|
|
12
|
+
error.sideEffectsStarted = false;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function persistentActivationSpawnOptions({ cwd, env = process.env } = {}) {
|
|
17
|
+
if (typeof cwd !== "string" || !cwd) {
|
|
18
|
+
throw new TypeError("persistent activation subprocess requires cwd");
|
|
19
|
+
}
|
|
20
|
+
if (!env || typeof env !== "object" || Array.isArray(env)) {
|
|
21
|
+
throw new TypeError("persistent activation subprocess requires an environment record");
|
|
22
|
+
}
|
|
23
|
+
// The activation child owns bounded deployment, network, relay, and service
|
|
24
|
+
// stages plus transactional cleanup. An outer timeout could SIGKILL the child
|
|
25
|
+
// while detached helpers remain alive and before compensation releases locks.
|
|
26
|
+
return { cwd, env, encoding: "utf8", windowsHide: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function preflightPersistentActivationWorkerAuth({
|
|
30
|
+
surface = "local",
|
|
31
|
+
stateRoot,
|
|
32
|
+
packageRoot,
|
|
33
|
+
npmCli,
|
|
34
|
+
env = process.env,
|
|
35
|
+
runWrangler = defaultRunWrangler,
|
|
36
|
+
} = {}) {
|
|
37
|
+
if (surface !== "local" && surface !== EXECUTION_SURFACE.managedJob) {
|
|
38
|
+
throw new TypeError("persistent activation Wrangler preflight requires a local or managed-job execution surface");
|
|
39
|
+
}
|
|
40
|
+
if (typeof stateRoot !== "string" || !stateRoot) throw new TypeError("persistent activation Wrangler preflight requires stateRoot");
|
|
41
|
+
if (typeof packageRoot !== "string" || !packageRoot) throw new TypeError("persistent activation Wrangler preflight requires packageRoot");
|
|
42
|
+
if (typeof runWrangler !== "function") throw new TypeError("persistent activation Wrangler preflight requires runWrangler");
|
|
43
|
+
const shared = { stateRoot, packageRoot, npmCli, env };
|
|
44
|
+
const whoami = await runWrangler(["whoami"], { ...shared, capture: true, allowFailure: true });
|
|
45
|
+
if (whoami?.code === 0) return { authenticated: true, login_performed: false };
|
|
46
|
+
if (surface === EXECUTION_SURFACE.managedJob) throw workerAuthenticationRequiredError(
|
|
47
|
+
"persistent activation cannot start interactive Wrangler login from a detached managed job; authenticate Cloudflare Wrangler in an ordinary owner terminal before retrying",
|
|
48
|
+
);
|
|
49
|
+
await runWrangler(["login"], shared);
|
|
50
|
+
const verified = await runWrangler(["whoami"], { ...shared, capture: true, allowFailure: true });
|
|
51
|
+
if (verified?.code !== 0) throw workerAuthenticationRequiredError(
|
|
52
|
+
"Cloudflare Wrangler login completed without a verifiable authenticated session; the persistent activation was not started",
|
|
53
|
+
);
|
|
54
|
+
return { authenticated: true, login_performed: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function persistentCandidateFailureMessage(output, { cli, stateRoot, previousRuntime = null } = {}) {
|
|
58
|
+
const detail = String(output || "").trim() || "activation subprocess exited unsuccessfully";
|
|
59
|
+
if (!/foreground daemon is active/i.test(detail)) return `persistent candidate activation failed: ${detail}`;
|
|
60
|
+
const quotedCli = JSON.stringify(String(cli || "machine-mcp"));
|
|
61
|
+
const quotedStateRoot = JSON.stringify(String(stateRoot || ""));
|
|
62
|
+
const recovery = previousRuntime?.cli && previousRuntime?.pid
|
|
63
|
+
? [
|
|
64
|
+
`Verified foreground runtime: ${previousRuntime.version || "unknown"} (pid ${previousRuntime.pid}).`,
|
|
65
|
+
"Stop that foreground daemon, then restore its existing login service with:",
|
|
66
|
+
`node ${JSON.stringify(previousRuntime.cli)} service start`,
|
|
67
|
+
`node ${quotedCli} service status --workspace ${JSON.stringify(previousRuntime.workspace)} --state-dir ${quotedStateRoot}`,
|
|
68
|
+
"Retry candidate activation only after status reports provider active and a verified service daemon for that workspace.",
|
|
69
|
+
]
|
|
70
|
+
: [
|
|
71
|
+
"The foreground runtime could not be independently resolved to a trusted installed CLI.",
|
|
72
|
+
"Keep it running and inspect its daemon lock and command line before attempting a manual service recovery.",
|
|
73
|
+
];
|
|
74
|
+
return [
|
|
75
|
+
`persistent candidate activation failed: ${detail}`,
|
|
76
|
+
"No Worker deployment or service replacement was started.",
|
|
77
|
+
...recovery,
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function validateActivationRecoveryPayload(value) {
|
|
82
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
83
|
+
throw new Error("persistent activation result is invalid");
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return normalizeActivationRecovery({
|
|
87
|
+
recovered: value.activation_recovered,
|
|
88
|
+
reason: value.activation_recovery_reason,
|
|
89
|
+
detail: value.activation_recovery_detail,
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new Error(`persistent ${String(error?.message || "activation recovery metadata is invalid")}`, { cause: error });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function workerAuthenticationRequiredError(message) {
|
|
97
|
+
const error = new Error(message);
|
|
98
|
+
error.code = "worker_authentication_required";
|
|
99
|
+
error.sideEffectsStarted = false;
|
|
100
|
+
return error;
|
|
101
|
+
}
|
|
@@ -1,89 +1,32 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
1
|
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
2
|
import { tmpdir } from "node:os";
|
|
5
3
|
import { join } from "node:path";
|
|
6
|
-
|
|
7
|
-
const NPM_VERSION = "12.0.1";
|
|
8
|
-
const NPM_TARBALL_URL = "https://registry.npmjs.org/npm/-/npm-12.0.1.tgz";
|
|
9
|
-
const NPM_TARBALL_INTEGRITY = "sha512-L5T9i/YAQWQWqTS/xZxJkei/9zcu99hCeE4qi41IyBVV7mRQad3qc2JfuOktwmH+qwGI/V2rbCL+/UYxb1+RQA==";
|
|
10
|
-
const MAX_TARBALL_BYTES = 20 * 1024 * 1024;
|
|
4
|
+
import { prepareHardenedNpm } from "../src/local/hardened-npm.mjs";
|
|
11
5
|
|
|
12
6
|
const githubPath = process.env.GITHUB_PATH;
|
|
13
7
|
if (!githubPath) throw new Error("GITHUB_PATH is required; this bootstrap is intended for GitHub Actions");
|
|
14
8
|
|
|
15
9
|
const root = mkdtempSync(join(process.env.RUNNER_TEMP || tmpdir(), "mbm-npm-bootstrap-"));
|
|
16
10
|
try {
|
|
17
|
-
const
|
|
18
|
-
const extracted = join(root, "extracted");
|
|
11
|
+
const hardened = join(root, "hardened");
|
|
19
12
|
const bin = join(root, "bin");
|
|
20
|
-
mkdirSync(extracted, { recursive: true });
|
|
21
13
|
mkdirSync(bin, { recursive: true });
|
|
22
|
-
|
|
23
|
-
const response = await fetch(NPM_TARBALL_URL, { redirect: "error" });
|
|
24
|
-
if (!response.ok) throw new Error(`failed to download pinned npm tarball: HTTP ${response.status}`);
|
|
25
|
-
const declaredBytes = Number(response.headers.get("content-length"));
|
|
26
|
-
if (Number.isFinite(declaredBytes) && declaredBytes > MAX_TARBALL_BYTES) throw new Error("pinned npm tarball exceeds 20 MiB");
|
|
27
|
-
const bytes = await readBoundedBody(response, MAX_TARBALL_BYTES);
|
|
28
|
-
verifyIntegrity(bytes, NPM_TARBALL_INTEGRITY);
|
|
29
|
-
writeFileSync(archive, bytes, { mode: 0o600 });
|
|
30
|
-
|
|
31
|
-
run("tar", ["-xzf", archive, "-C", extracted]);
|
|
32
|
-
const cli = join(extracted, "package", "bin", "npm-cli.js");
|
|
33
|
-
const version = run(process.execPath, [cli, "--version"], { capture: true }).trim();
|
|
34
|
-
if (version !== NPM_VERSION) throw new Error(`pinned npm tarball reported ${version}, expected ${NPM_VERSION}`);
|
|
14
|
+
const prepared = await prepareHardenedNpm(hardened);
|
|
35
15
|
|
|
36
16
|
const posixWrapper = join(bin, "npm");
|
|
37
|
-
writeFileSync(posixWrapper, `#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(cli)} "$@"\n`, { mode: 0o755 });
|
|
17
|
+
writeFileSync(posixWrapper, `#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(prepared.cli)} "$@"\n`, { mode: 0o755 });
|
|
38
18
|
chmodSync(posixWrapper, 0o755);
|
|
39
|
-
writeFileSync(join(bin, "npm.cmd"), `@echo off\r\n"${cmdQuote(process.execPath)}" "${cmdQuote(cli)}" %*\r\n`);
|
|
19
|
+
writeFileSync(join(bin, "npm.cmd"), `@echo off\r\n"${cmdQuote(process.execPath)}" "${cmdQuote(prepared.cli)}" %*\r\n`);
|
|
40
20
|
writeFileSync(githubPath, `${bin}\n`, { flag: "a" });
|
|
41
|
-
console.log(`Prepared integrity-verified npm ${
|
|
21
|
+
console.log(`Prepared integrity-verified hardened npm ${prepared.version} (undici ${prepared.undiciVersion}; brace-expansion ${prepared.braceExpansionVersion}) at ${bin}`);
|
|
42
22
|
} catch (error) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
async function readBoundedBody(response, maximumBytes) {
|
|
49
|
-
if (!response.body) throw new Error("pinned npm tarball response has no body");
|
|
50
|
-
const reader = response.body.getReader();
|
|
51
|
-
const chunks = [];
|
|
52
|
-
let total = 0;
|
|
53
|
-
try {
|
|
54
|
-
while (true) {
|
|
55
|
-
const { done, value } = await reader.read();
|
|
56
|
-
if (done) break;
|
|
57
|
-
total += value.byteLength;
|
|
58
|
-
if (total > maximumBytes) throw new Error("pinned npm tarball exceeds 20 MiB");
|
|
59
|
-
chunks.push(Buffer.from(value));
|
|
60
|
-
}
|
|
61
|
-
} finally {
|
|
62
|
-
reader.releaseLock();
|
|
23
|
+
let cleanupError = null;
|
|
24
|
+
try { rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }); }
|
|
25
|
+
catch (failure) { cleanupError = failure; }
|
|
26
|
+
if (cleanupError) {
|
|
27
|
+
throw new AggregateError([error, cleanupError], "pinned npm bootstrap failed and temporary cleanup was incomplete");
|
|
63
28
|
}
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function verifyIntegrity(bytes, integrity) {
|
|
68
|
-
const match = /^sha512-(.+)$/.exec(integrity);
|
|
69
|
-
if (!match) throw new Error("pinned npm integrity must be SHA-512 SRI");
|
|
70
|
-
const actual = createHash("sha512").update(bytes).digest();
|
|
71
|
-
const expected = Buffer.from(match[1], "base64");
|
|
72
|
-
if (actual.length !== expected.length || !actual.equals(expected)) throw new Error("pinned npm tarball failed SHA-512 verification");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function run(command, args, options = {}) {
|
|
76
|
-
const result = spawnSync(command, args, {
|
|
77
|
-
encoding: options.capture ? "utf8" : undefined,
|
|
78
|
-
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
79
|
-
shell: false,
|
|
80
|
-
});
|
|
81
|
-
if (result.error) throw result.error;
|
|
82
|
-
if (result.status !== 0) {
|
|
83
|
-
const detail = options.capture ? String(result.stderr || result.stdout || "").trim() : "";
|
|
84
|
-
throw new Error(`${command} exited ${result.status}${detail ? `: ${detail}` : ""}`);
|
|
85
|
-
}
|
|
86
|
-
return options.capture ? String(result.stdout || "") : "";
|
|
29
|
+
throw error;
|
|
87
30
|
}
|
|
88
31
|
|
|
89
32
|
function shellQuote(value) {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { defaultStateRoot, expandHome } from "../src/local/state.mjs";
|
|
4
|
+
import { replaceFileAtomicallySync } from "../src/local/exclusive-file.mjs";
|
|
5
|
+
import { ensureOwnerOnlyDirectorySync, readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
|
|
6
|
+
import { normalizeActivationRecovery as normalizeSharedActivationRecovery } from "../src/shared/activation-recovery.mjs";
|
|
7
|
+
import { assertSoakEligiblePrerelease } from "./release-channel.mjs";
|
|
8
|
+
|
|
9
|
+
export const ACTIVATION_SCHEMA_VERSION = 2;
|
|
10
|
+
const MAX_ACTIVATION_BYTES = 64 * 1024;
|
|
11
|
+
const SOURCES = new Set(["local-candidate", "npm-prerelease"]);
|
|
12
|
+
const ACTIVATION_FIELDS = new Set([
|
|
13
|
+
"schema_version", "package_name", "package_version", "source", "shasum", "integrity",
|
|
14
|
+
"promotion_content_sha256", "activated_at", "npm_dist_tag", "published_at", "workspace_hash",
|
|
15
|
+
"runtime_entry", "activation_recovered", "activation_recovery_reason", "activation_recovery_detail",
|
|
16
|
+
"global_package_rollback_baseline",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export function prereleaseActivationPath(version, stateRoot = defaultStateRoot()) {
|
|
20
|
+
const parsed = assertSoakEligiblePrerelease(version);
|
|
21
|
+
return join(resolve(expandHome(stateRoot)), "release-channels", "activations", `v${parsed.raw}.json`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function writePrereleaseActivation(record, stateRoot = defaultStateRoot()) {
|
|
25
|
+
const normalized = validatePrereleaseActivation(record);
|
|
26
|
+
const file = prereleaseActivationPath(normalized.package_version, stateRoot);
|
|
27
|
+
ensureOwnerOnlyDirectorySync(dirname(file));
|
|
28
|
+
replaceFileAtomicallySync(file, `${JSON.stringify(normalized, null, 2)}\n`, { mode: 0o600 });
|
|
29
|
+
return file;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function readPrereleaseActivation(version, stateRoot = defaultStateRoot(), options = {}) {
|
|
33
|
+
const file = prereleaseActivationPath(version, stateRoot);
|
|
34
|
+
const readRecord = options.readBoundedRegularFileSync || readBoundedRegularFileSync;
|
|
35
|
+
let bytes;
|
|
36
|
+
try {
|
|
37
|
+
bytes = readRecord(file, MAX_ACTIVATION_BYTES, "prerelease activation record", {
|
|
38
|
+
verifyPathIdentity: true,
|
|
39
|
+
rejectMultipleLinks: true,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error?.code === "ENOENT") throw new Error(`prerelease activation record is missing: ${file}`, { cause: error });
|
|
43
|
+
throw new Error("prerelease activation record is unavailable", { cause: error });
|
|
44
|
+
}
|
|
45
|
+
let value;
|
|
46
|
+
try { value = JSON.parse(bytes.toString("utf8")); }
|
|
47
|
+
catch (error) { throw new Error("prerelease activation record is invalid", { cause: error }); }
|
|
48
|
+
return validatePrereleaseActivation(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function assertPrereleaseActivationRuntimeRoot(activation, runtimeRoot, options = {}) {
|
|
52
|
+
const runtimeEntry = String(activation?.runtime_entry || "");
|
|
53
|
+
if (!runtimeEntry || !isAbsolute(runtimeEntry)) throw new Error("prerelease activation runtime entry is missing");
|
|
54
|
+
const executingRoot = String(runtimeRoot || "");
|
|
55
|
+
if (!executingRoot || !isAbsolute(executingRoot)) throw new Error("executing canary runtime root is invalid");
|
|
56
|
+
const canonicalize = options.realpathSync || realpathSync;
|
|
57
|
+
let activatedRoot;
|
|
58
|
+
let canonicalExecutingRoot;
|
|
59
|
+
try {
|
|
60
|
+
activatedRoot = canonicalize(resolve(dirname(runtimeEntry), ".."));
|
|
61
|
+
canonicalExecutingRoot = canonicalize(resolve(executingRoot));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error("prerelease activation runtime package identity is unavailable", { cause: error });
|
|
64
|
+
}
|
|
65
|
+
const platform = String(options.platform || process.platform);
|
|
66
|
+
const same = platform === "win32"
|
|
67
|
+
? activatedRoot.toLowerCase() === canonicalExecutingRoot.toLowerCase()
|
|
68
|
+
: activatedRoot === canonicalExecutingRoot;
|
|
69
|
+
if (!same) throw new Error("prerelease activation runtime package does not match the executing canary");
|
|
70
|
+
return canonicalExecutingRoot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validatePrereleaseActivation(value) {
|
|
74
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("prerelease activation record must be an object");
|
|
75
|
+
const unknown = Object.keys(value).filter((key) => !ACTIVATION_FIELDS.has(key));
|
|
76
|
+
if (unknown.length) throw new Error(`prerelease activation record contains unsupported fields: ${unknown.join(", ")}`);
|
|
77
|
+
const schemaVersion = value.schema_version;
|
|
78
|
+
if (schemaVersion !== ACTIVATION_SCHEMA_VERSION) {
|
|
79
|
+
throw new Error("unsupported prerelease activation schema");
|
|
80
|
+
}
|
|
81
|
+
const parsed = assertSoakEligiblePrerelease(value.package_version);
|
|
82
|
+
if (value.package_name !== "machine-bridge-mcp") throw new Error("prerelease activation package name is invalid");
|
|
83
|
+
if (!SOURCES.has(value.source)) throw new Error("prerelease activation source is invalid");
|
|
84
|
+
if (!/^[0-9a-f]{40}$/.test(String(value.shasum || ""))) throw new Error("prerelease activation SHA-1 is invalid");
|
|
85
|
+
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(String(value.integrity || ""))) throw new Error("prerelease activation integrity is invalid");
|
|
86
|
+
if (!/^[0-9a-f]{64}$/.test(String(value.promotion_content_sha256 || ""))) throw new Error("prerelease activation promotion digest is invalid");
|
|
87
|
+
const activatedAt = Date.parse(String(value.activated_at || ""));
|
|
88
|
+
if (!Number.isFinite(activatedAt)) throw new Error("prerelease activation timestamp is invalid");
|
|
89
|
+
if (value.source === "npm-prerelease") {
|
|
90
|
+
if (value.npm_dist_tag !== parsed.npmTag) throw new Error("prerelease activation npm dist-tag is invalid");
|
|
91
|
+
const publishedAt = Date.parse(String(value.published_at || ""));
|
|
92
|
+
if (!Number.isFinite(publishedAt) || publishedAt > activatedAt + 5 * 60 * 1000) throw new Error("prerelease activation publication timestamp is invalid");
|
|
93
|
+
}
|
|
94
|
+
const workspaceHash = String(value.workspace_hash || "");
|
|
95
|
+
if (workspaceHash && !/^[0-9a-f]{24}$/.test(workspaceHash)) throw new Error("prerelease activation workspace hash is invalid");
|
|
96
|
+
const runtimeEntry = String(value.runtime_entry || "");
|
|
97
|
+
if (runtimeEntry && !isAbsolute(runtimeEntry)) throw new Error("prerelease activation runtime entry must be absolute");
|
|
98
|
+
const activationRecovery = normalizeActivationRecovery(value);
|
|
99
|
+
const globalPackageRollbackBaseline = value.global_package_rollback_baseline === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: validateGlobalPackageRollbackBaseline(value.global_package_rollback_baseline);
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
schema_version: ACTIVATION_SCHEMA_VERSION,
|
|
104
|
+
package_name: value.package_name,
|
|
105
|
+
package_version: parsed.raw,
|
|
106
|
+
source: value.source,
|
|
107
|
+
shasum: value.shasum,
|
|
108
|
+
integrity: value.integrity,
|
|
109
|
+
promotion_content_sha256: value.promotion_content_sha256,
|
|
110
|
+
activated_at: new Date(activatedAt).toISOString(),
|
|
111
|
+
...(value.source === "npm-prerelease" ? {
|
|
112
|
+
npm_dist_tag: value.npm_dist_tag,
|
|
113
|
+
published_at: new Date(Date.parse(value.published_at)).toISOString(),
|
|
114
|
+
} : {}),
|
|
115
|
+
...(workspaceHash ? { workspace_hash: workspaceHash } : {}),
|
|
116
|
+
...(runtimeEntry ? { runtime_entry: runtimeEntry } : {}),
|
|
117
|
+
...activationRecovery,
|
|
118
|
+
...(globalPackageRollbackBaseline ? { global_package_rollback_baseline: globalPackageRollbackBaseline } : {}),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeActivationRecovery(value) {
|
|
123
|
+
if (value.activation_recovered !== undefined && typeof value.activation_recovered !== "boolean") {
|
|
124
|
+
throw new Error("prerelease activation recovery flag is invalid");
|
|
125
|
+
}
|
|
126
|
+
if (value.activation_recovered !== true
|
|
127
|
+
&& (String(value.activation_recovery_reason || "") || String(value.activation_recovery_detail || ""))) {
|
|
128
|
+
throw new Error("prerelease activation recovery metadata requires a recovered activation");
|
|
129
|
+
}
|
|
130
|
+
let normalized;
|
|
131
|
+
try {
|
|
132
|
+
normalized = normalizeSharedActivationRecovery({
|
|
133
|
+
recovered: value.activation_recovered === true,
|
|
134
|
+
reason: value.activation_recovery_reason,
|
|
135
|
+
detail: value.activation_recovery_detail,
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const message = String(error?.message || "activation recovery metadata is invalid")
|
|
139
|
+
.replace(/^activation recovery /, "prerelease activation recovery ");
|
|
140
|
+
throw new Error(message, { cause: error });
|
|
141
|
+
}
|
|
142
|
+
if (!normalized.recovered) return Object.freeze({});
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
activation_recovered: true,
|
|
145
|
+
activation_recovery_reason: normalized.reason,
|
|
146
|
+
activation_recovery_detail: normalized.detail,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function validateGlobalPackageRollbackBaseline(value) {
|
|
151
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
152
|
+
throw new Error("prerelease activation global package rollback baseline is invalid");
|
|
153
|
+
}
|
|
154
|
+
const version = String(value.version || "");
|
|
155
|
+
const entry = String(value.entry || "");
|
|
156
|
+
if (!version || !entry || !isAbsolute(entry)) {
|
|
157
|
+
throw new Error("prerelease activation global package rollback baseline is invalid");
|
|
158
|
+
}
|
|
159
|
+
return Object.freeze({ version, entry });
|
|
160
|
+
}
|