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.
Files changed (473) hide show
  1. package/CHANGELOG.md +1142 -0
  2. package/CONTRIBUTING.md +23 -25
  3. package/GOVERNANCE.md +2 -2
  4. package/README.md +58 -20
  5. package/SECURITY.md +161 -120
  6. package/SUPPORT.md +1 -1
  7. package/browser-extension/broker-auth.js +83 -0
  8. package/browser-extension/broker-liveness.js +35 -0
  9. package/browser-extension/browser-error-boundary.js +96 -0
  10. package/browser-extension/browser-operations.js +1456 -101
  11. package/browser-extension/devtools-input.js +195 -34
  12. package/browser-extension/devtools-observation.js +429 -0
  13. package/browser-extension/devtools-session.js +48 -0
  14. package/browser-extension/manifest.json +4 -3
  15. package/browser-extension/page-automation.js +578 -118
  16. package/browser-extension/pairing-bootstrap.js +42 -0
  17. package/browser-extension/pairing.js +28 -12
  18. package/browser-extension/service-worker.js +126 -106
  19. package/docs/AGENT_CONTEXT.md +13 -10
  20. package/docs/ARCHITECTURE.md +159 -67
  21. package/docs/AUDIT.md +1302 -4
  22. package/docs/CLIENTS.md +19 -6
  23. package/docs/COMPUTER_USE.md +297 -0
  24. package/docs/ENGINEERING.md +72 -13
  25. package/docs/GETTING_STARTED.md +18 -20
  26. package/docs/LOCAL_AUTHORIZATION.md +115 -72
  27. package/docs/LOCAL_AUTOMATION.md +27 -21
  28. package/docs/LOGGING.md +40 -19
  29. package/docs/MANAGED_JOBS.md +37 -34
  30. package/docs/MULTI_ACCOUNT.md +98 -37
  31. package/docs/OPERATIONS.md +145 -51
  32. package/docs/OVERVIEW.md +14 -11
  33. package/docs/POLICY_REFERENCE.md +71 -3
  34. package/docs/PRIVACY.md +41 -2
  35. package/docs/PROJECT_STANDARDS.md +29 -6
  36. package/docs/RELEASING.md +238 -64
  37. package/docs/TESTING.md +150 -39
  38. package/docs/THREAT_MODEL.md +164 -67
  39. package/docs/TOOL_REFERENCE.md +653 -60
  40. package/docs/UPGRADING.md +78 -18
  41. package/native/macos/MachineBridgeBackgroundInput.swift +1051 -0
  42. package/native/macos/MachineBridgeBackgroundInputSmokeFixture.swift +295 -0
  43. package/native/macos/MachineBridgeTrustBroker.swift +233 -0
  44. package/package.json +94 -16
  45. package/scripts/accepted-candidate-tarball.mjs +77 -0
  46. package/scripts/candidate-runtime-store.mjs +93 -0
  47. package/scripts/check-plan.mjs +73 -3
  48. package/scripts/check-runner.mjs +129 -17
  49. package/scripts/commit-message-check.mjs +5 -2
  50. package/scripts/consumer-package-security.mjs +293 -0
  51. package/scripts/coverage-check.mjs +251 -15
  52. package/scripts/coverage-generation.mjs +41 -0
  53. package/scripts/coverage-range-merge.mjs +41 -0
  54. package/scripts/foreground-daemon-recovery.mjs +88 -0
  55. package/scripts/generate-policy-reference.mjs +28 -5
  56. package/scripts/generate-tool-reference.mjs +1 -1
  57. package/scripts/generate-worker-types.mjs +21 -11
  58. package/scripts/github-backlog.mjs +11 -10
  59. package/scripts/github-push.mjs +62 -16
  60. package/scripts/github-release-asset.mjs +58 -0
  61. package/scripts/github-release.mjs +241 -132
  62. package/scripts/global-package-installation.mjs +68 -0
  63. package/scripts/hardened-npm-session.mjs +42 -0
  64. package/scripts/install-published-prerelease.mjs +198 -0
  65. package/scripts/local-release-acceptance.mjs +103 -45
  66. package/scripts/macos-background-input-smoke.mjs +515 -0
  67. package/scripts/network-retry.mjs +8 -1
  68. package/scripts/npm-global-prefix.mjs +27 -0
  69. package/scripts/npm-publication-policy.mjs +17 -0
  70. package/scripts/official-mcp-conformance.mjs +267 -0
  71. package/scripts/persistent-activation-process.mjs +101 -0
  72. package/scripts/prepare-pinned-npm.mjs +12 -69
  73. package/scripts/prerelease-activation.mjs +160 -0
  74. package/scripts/privacy-check.mjs +14 -6
  75. package/scripts/promotion-digest.mjs +128 -0
  76. package/scripts/publish-npm.mjs +260 -0
  77. package/scripts/published-release.mjs +132 -0
  78. package/scripts/release-acceptance.mjs +62 -54
  79. package/scripts/release-browser-extension-store.mjs +172 -0
  80. package/scripts/release-candidate-manifest.mjs +52 -0
  81. package/scripts/release-channel.mjs +106 -0
  82. package/scripts/release-diagnostic.mjs +41 -0
  83. package/scripts/release-impact-check.mjs +12 -22
  84. package/scripts/release-oauth-canary-core.mjs +325 -0
  85. package/scripts/release-oauth-canary-evidence.mjs +100 -0
  86. package/scripts/release-oauth-canary.mjs +127 -0
  87. package/scripts/release-publication-guard.mjs +67 -0
  88. package/scripts/release-soak.mjs +284 -0
  89. package/scripts/release-state.mjs +1 -1
  90. package/scripts/run-checks.mjs +44 -7
  91. package/scripts/run-worker-dry-run.mjs +21 -0
  92. package/scripts/sarif-security-gate.mjs +2 -1
  93. package/scripts/sbom-check.mjs +106 -0
  94. package/scripts/start-release-candidate.mjs +267 -44
  95. package/scripts/syntax-check.mjs +36 -14
  96. package/scripts/verification-generation-guard.mjs +15 -0
  97. package/scripts/verification-idle-sleep-guard.mjs +37 -0
  98. package/scripts/verification-state.mjs +108 -0
  99. package/scripts/wrangler-command-lifecycle.mjs +115 -0
  100. package/src/local/account-access.mjs +13 -1
  101. package/src/local/account-admin-response.mjs +92 -0
  102. package/src/local/account-admin.mjs +52 -19
  103. package/src/local/agent-context-projection.mjs +26 -7
  104. package/src/local/agent-context.mjs +88 -49
  105. package/src/local/agent-skill-discovery.mjs +16 -5
  106. package/src/local/app-automation-macos-jxa.mjs +350 -0
  107. package/src/local/app-automation.mjs +873 -163
  108. package/src/local/application-capability-projection.mjs +24 -0
  109. package/src/local/authority-context.mjs +106 -0
  110. package/src/local/autostart-log-maintenance.mjs +36 -0
  111. package/src/local/browser-bridge-http.mjs +5 -3
  112. package/src/local/browser-bridge.mjs +109 -31
  113. package/src/local/browser-broker-auth-http.mjs +67 -0
  114. package/src/local/browser-broker-auth.mjs +98 -0
  115. package/src/local/browser-broker-routes.mjs +63 -20
  116. package/src/local/browser-broker-server.mjs +17 -7
  117. package/src/local/browser-command.mjs +79 -30
  118. package/src/local/browser-computer-observation-service.mjs +266 -0
  119. package/src/local/browser-extension-identity.mjs +50 -0
  120. package/src/local/browser-extension-path.mjs +22 -0
  121. package/src/local/browser-extension-protocol.mjs +50 -11
  122. package/src/local/browser-operation-service.mjs +147 -117
  123. package/src/local/browser-pairing-grant.mjs +109 -0
  124. package/src/local/browser-pairing-http.mjs +36 -0
  125. package/src/local/browser-pairing-launch.mjs +58 -0
  126. package/src/local/browser-pairing-store.mjs +85 -48
  127. package/src/local/browser-request-registry.mjs +40 -14
  128. package/src/local/browser-request-settlement.mjs +78 -0
  129. package/src/local/browser-resource-input.mjs +74 -0
  130. package/src/local/browser-trusted-input-health.mjs +63 -0
  131. package/src/local/call-authority.mjs +21 -0
  132. package/src/local/call-capacity.mjs +45 -0
  133. package/src/local/call-registry-drain.mjs +54 -0
  134. package/src/local/call-registry.mjs +30 -15
  135. package/src/local/capability-observer.mjs +5 -0
  136. package/src/local/capability-ranking.mjs +3 -2
  137. package/src/local/child-process-settlement.mjs +132 -0
  138. package/src/local/cli-account-admin.mjs +40 -6
  139. package/src/local/cli-activate.mjs +162 -0
  140. package/src/local/cli-local-admin.mjs +20 -45
  141. package/src/local/cli-options.mjs +12 -13
  142. package/src/local/cli-ready-output.mjs +52 -0
  143. package/src/local/cli-service.mjs +91 -11
  144. package/src/local/cli.mjs +232 -150
  145. package/src/local/computer-use-application-observation.mjs +224 -0
  146. package/src/local/computer-use-arguments.mjs +270 -0
  147. package/src/local/computer-use-deadline.mjs +50 -0
  148. package/src/local/computer-use-dispatch-settlement.mjs +40 -0
  149. package/src/local/computer-use-expectation.mjs +171 -0
  150. package/src/local/computer-use-observation.mjs +769 -0
  151. package/src/local/computer-use-recovery.mjs +82 -0
  152. package/src/local/computer-use-result-budget.mjs +145 -0
  153. package/src/local/computer-use-snapshot-store.mjs +67 -0
  154. package/src/local/computer-use.mjs +2255 -0
  155. package/src/local/daemon-http-relay-auth.mjs +26 -0
  156. package/src/local/daemon-http-relay-connection.mjs +298 -0
  157. package/src/local/daemon-http-relay-request.mjs +64 -0
  158. package/src/local/daemon-http-relay-sequence.mjs +49 -0
  159. package/src/local/daemon-process.mjs +40 -7
  160. package/src/local/delegated-process-sandbox.mjs +148 -0
  161. package/src/local/device-identity.mjs +156 -49
  162. package/src/local/device-root-provider.mjs +81 -0
  163. package/src/local/directory-metadata.mjs +47 -0
  164. package/src/local/doctor-reporting.mjs +23 -0
  165. package/src/local/durable-process-spec.mjs +80 -0
  166. package/src/local/errors.mjs +25 -15
  167. package/src/local/exclusive-file.mjs +74 -26
  168. package/src/local/execution-limits.mjs +9 -0
  169. package/src/local/execution-routing.mjs +276 -0
  170. package/src/local/execution-surface.mjs +23 -0
  171. package/src/local/file-mutation-coordinator.mjs +47 -0
  172. package/src/local/file-snapshot-preservation.mjs +24 -0
  173. package/src/local/filesystem-identity.mjs +54 -0
  174. package/src/local/fixed-process-environment.mjs +21 -0
  175. package/src/local/full-access-test.mjs +31 -13
  176. package/src/local/git-commit.mjs +69 -0
  177. package/src/local/git-config-safety.mjs +55 -0
  178. package/src/local/git-log-parser.mjs +41 -0
  179. package/src/local/git-metadata-boundary.mjs +77 -0
  180. package/src/local/git-metadata-tree-safety.mjs +59 -0
  181. package/src/local/git-operation-state.mjs +19 -0
  182. package/src/local/git-service.mjs +165 -30
  183. package/src/local/hardened-npm-download-timeout.mjs +46 -0
  184. package/src/local/hardened-npm-download.mjs +109 -0
  185. package/src/local/hardened-npm-extract.mjs +20 -0
  186. package/src/local/hardened-npm-verification.mjs +104 -0
  187. package/src/local/hardened-npm.mjs +199 -0
  188. package/src/local/job-runner.mjs +244 -89
  189. package/src/local/lifecycle.mjs +6 -0
  190. package/src/local/log.mjs +19 -45
  191. package/src/local/macos-background-input.mjs +398 -0
  192. package/src/local/macos-idle-sleep-assertion.mjs +65 -0
  193. package/src/local/macos-trust-broker.mjs +404 -0
  194. package/src/local/managed-job-cancellation.mjs +42 -0
  195. package/src/local/managed-job-capacity.mjs +16 -0
  196. package/src/local/managed-job-directory-generation.mjs +60 -0
  197. package/src/local/managed-job-directory.mjs +75 -0
  198. package/src/local/managed-job-durable-process.mjs +49 -0
  199. package/src/local/managed-job-lock.mjs +46 -14
  200. package/src/local/managed-job-plan.mjs +38 -14
  201. package/src/local/managed-job-projection.mjs +23 -15
  202. package/src/local/managed-job-retention.mjs +128 -0
  203. package/src/local/managed-job-runner-claim.mjs +70 -0
  204. package/src/local/managed-job-runner.mjs +35 -10
  205. package/src/local/managed-job-storage.mjs +22 -6
  206. package/src/local/managed-job-terminal-maintenance.mjs +40 -0
  207. package/src/local/managed-job-terminal.mjs +150 -0
  208. package/src/local/managed-jobs.mjs +424 -287
  209. package/src/local/npm-cli.mjs +42 -0
  210. package/src/local/npm-environment.mjs +37 -0
  211. package/src/local/operation-authorization.mjs +68 -324
  212. package/src/local/operation-risk.mjs +51 -4
  213. package/src/local/owner-state-lock.mjs +118 -0
  214. package/src/local/package-identity.mjs +9 -0
  215. package/src/local/patch.mjs +20 -18
  216. package/src/local/path-inspection.mjs +23 -0
  217. package/src/local/policy.mjs +19 -0
  218. package/src/local/private-toolchain-integrity.mjs +54 -0
  219. package/src/local/process-execution.mjs +87 -75
  220. package/src/local/process-foreground-timeout.mjs +50 -0
  221. package/src/local/process-identity.mjs +41 -7
  222. package/src/local/process-nonreplayable-settlement.mjs +50 -0
  223. package/src/local/process-result-projection.mjs +22 -0
  224. package/src/local/process-session-events.mjs +47 -0
  225. package/src/local/process-session-remote-activity.mjs +10 -0
  226. package/src/local/process-session-termination.mjs +73 -0
  227. package/src/local/process-sessions.mjs +144 -112
  228. package/src/local/process-tracker.mjs +140 -13
  229. package/src/local/process-tree-ownership-types.d.ts +37 -0
  230. package/src/local/process-tree-ownership.mjs +57 -0
  231. package/src/local/process-tree-signal.mjs +34 -0
  232. package/src/local/process-tree-snapshot.mjs +77 -0
  233. package/src/local/process-tree-supervisor.mjs +69 -0
  234. package/src/local/process-tree.mjs +11 -53
  235. package/src/local/project-metadata.mjs +19 -6
  236. package/src/local/relay-call-recovery.mjs +58 -38
  237. package/src/local/relay-connection-classification.mjs +131 -0
  238. package/src/local/relay-connection-support.mjs +91 -0
  239. package/src/local/relay-connection.mjs +179 -272
  240. package/src/local/relay-diagnostics.mjs +91 -0
  241. package/src/local/relay-heartbeat.mjs +119 -0
  242. package/src/local/relay-liveness.mjs +109 -0
  243. package/src/local/relay-peer-diagnostics.mjs +29 -0
  244. package/src/local/release-runtime-lock.mjs +18 -0
  245. package/src/local/remote-activity-idle-sleep-guard.mjs +77 -0
  246. package/src/local/remote-configuration.mjs +48 -0
  247. package/src/local/resilient-relay-connection.mjs +178 -0
  248. package/src/local/resource-admission-diagnostics.mjs +52 -0
  249. package/src/local/resource-admission-policy.mjs +115 -0
  250. package/src/local/resource-admission.mjs +339 -0
  251. package/src/local/resource-build-root.mjs +83 -0
  252. package/src/local/resource-cargo-concurrency.mjs +38 -0
  253. package/src/local/resource-cmake-concurrency.mjs +42 -0
  254. package/src/local/resource-command-concurrency.mjs +37 -0
  255. package/src/local/resource-command-profile.mjs +176 -0
  256. package/src/local/resource-coordinator-accounting.mjs +21 -0
  257. package/src/local/resource-cpu-window.mjs +20 -0
  258. package/src/local/resource-disk-headroom.mjs +13 -0
  259. package/src/local/resource-elastic-memory.mjs +19 -0
  260. package/src/local/resource-elastic-request.mjs +36 -0
  261. package/src/local/resource-foreground-wait.mjs +29 -0
  262. package/src/local/resource-go-concurrency.mjs +43 -0
  263. package/src/local/resource-gradle-concurrency.mjs +57 -0
  264. package/src/local/resource-host-cache.mjs +39 -0
  265. package/src/local/resource-host-darwin.mjs +54 -0
  266. package/src/local/resource-host-linux.mjs +35 -0
  267. package/src/local/resource-host-sample-file.mjs +23 -0
  268. package/src/local/resource-host-snapshot.mjs +99 -0
  269. package/src/local/resource-lease-accounting.mjs +119 -0
  270. package/src/local/resource-light-command.mjs +6 -0
  271. package/src/local/resource-make-concurrency.mjs +42 -0
  272. package/src/local/resource-maven-concurrency.mjs +38 -0
  273. package/src/local/resource-ninja-command-concurrency.mjs +20 -0
  274. package/src/local/resource-ninja-concurrency.mjs +28 -0
  275. package/src/local/resource-operations.mjs +26 -10
  276. package/src/local/resource-pressure.mjs +67 -0
  277. package/src/local/resource-probe-command.mjs +35 -0
  278. package/src/local/resource-process-admission.mjs +59 -0
  279. package/src/local/resource-process-ancestry-cache.mjs +43 -0
  280. package/src/local/resource-process-ancestry.mjs +32 -0
  281. package/src/local/resource-process-priority.mjs +15 -0
  282. package/src/local/resource-project-key.mjs +53 -0
  283. package/src/local/resource-pytest-concurrency.mjs +33 -0
  284. package/src/local/resource-release-control-classification.mjs +14 -0
  285. package/src/local/resource-release-control-executable.mjs +70 -0
  286. package/src/local/resource-release-control-workspace.mjs +41 -0
  287. package/src/local/resource-request-contract.mjs +30 -0
  288. package/src/local/resource-script-classification.mjs +44 -0
  289. package/src/local/resource-shell-analysis.mjs +58 -0
  290. package/src/local/resource-staging-recovery.mjs +97 -0
  291. package/src/local/resource-swift-concurrency.mjs +31 -0
  292. package/src/local/resource-transaction-lock.mjs +189 -0
  293. package/src/local/resource-wait.mjs +48 -0
  294. package/src/local/resource-waiters.mjs +139 -0
  295. package/src/local/resource-xcode-command.mjs +7 -0
  296. package/src/local/resource-xcode-concurrency.mjs +34 -0
  297. package/src/local/resource-xcode-non-build.mjs +19 -0
  298. package/src/local/runtime-activation.mjs +700 -0
  299. package/src/local/runtime-activity-projection.mjs +48 -0
  300. package/src/local/runtime-capabilities.mjs +48 -15
  301. package/src/local/runtime-diagnostic-state.mjs +14 -0
  302. package/src/local/runtime-diagnostics.mjs +40 -13
  303. package/src/local/runtime-info-projection.mjs +60 -0
  304. package/src/local/runtime-info-relay-projection.mjs +21 -0
  305. package/src/local/runtime-paths.mjs +2 -1
  306. package/src/local/runtime-process-routing.mjs +33 -0
  307. package/src/local/runtime-relay-connection-options.mjs +44 -0
  308. package/src/local/runtime-relay-control.mjs +98 -0
  309. package/src/local/runtime-relay.mjs +26 -50
  310. package/src/local/runtime-reporting.mjs +37 -21
  311. package/src/local/runtime-resource-service.mjs +77 -0
  312. package/src/local/runtime-tool-handlers.mjs +13 -10
  313. package/src/local/runtime.mjs +244 -208
  314. package/src/local/secure-file.mjs +73 -20
  315. package/src/local/security-audit-dispatch.mjs +35 -0
  316. package/src/local/security-audit-log.mjs +215 -0
  317. package/src/local/security-audit-state.mjs +139 -0
  318. package/src/local/security-audit-storage.mjs +135 -0
  319. package/src/local/security-audit-warning.mjs +31 -0
  320. package/src/local/security-audit-worker.mjs +87 -0
  321. package/src/local/service-convergence.mjs +42 -1
  322. package/src/local/service-definition.mjs +24 -0
  323. package/src/local/service-environment.mjs +18 -10
  324. package/src/local/service-owner.mjs +146 -0
  325. package/src/local/service-ownership.mjs +17 -0
  326. package/src/local/service-restart-handoff.mjs +64 -0
  327. package/src/local/service-restart-scheduler.mjs +49 -0
  328. package/src/local/service-runtime-convergence.mjs +52 -0
  329. package/src/local/service-runtime.mjs +105 -0
  330. package/src/local/service-status.mjs +60 -0
  331. package/src/local/service.mjs +385 -92
  332. package/src/local/shell.mjs +39 -19
  333. package/src/local/ssh-key.mjs +158 -42
  334. package/src/local/state-inventory.mjs +50 -24
  335. package/src/local/state-owner-lock-inventory.mjs +23 -0
  336. package/src/local/state-root-owned-namespaces.mjs +83 -0
  337. package/src/local/state-root-retirement.mjs +89 -0
  338. package/src/local/state.mjs +386 -96
  339. package/src/local/stdio.mjs +96 -66
  340. package/src/local/support-state-projection.mjs +30 -0
  341. package/src/local/system-network-route.mjs +76 -0
  342. package/src/local/systemd-removal.mjs +20 -0
  343. package/src/local/tool-executor.mjs +85 -20
  344. package/src/local/tool-result-boundary.mjs +51 -0
  345. package/src/local/toolchain-operation-lock.mjs +41 -0
  346. package/src/local/tools.mjs +7 -5
  347. package/src/local/trusted-executable.mjs +53 -0
  348. package/src/local/trusted-git-executable.mjs +34 -0
  349. package/src/local/trusted-github-cli.mjs +24 -0
  350. package/src/local/windows-service-convergence.mjs +49 -0
  351. package/src/local/windows-service.mjs +52 -45
  352. package/src/local/worker-deployment-fingerprint.mjs +102 -0
  353. package/src/local/worker-deployment.mjs +48 -60
  354. package/src/local/worker-health.mjs +12 -4
  355. package/src/local/worker-secret-file.mjs +37 -17
  356. package/src/local/workspace-file-service.mjs +156 -239
  357. package/src/local/workspace-file-transaction.mjs +189 -0
  358. package/src/local/workspace-search.mjs +55 -0
  359. package/src/local/wrangler-toolchain/package-lock.json +1505 -0
  360. package/src/local/wrangler-toolchain/package.json +23 -0
  361. package/src/local/wrangler-toolchain-verification.mjs +135 -0
  362. package/src/local/wrangler-toolchain.mjs +188 -0
  363. package/src/shared/access-contract.json +7 -1
  364. package/src/shared/activation-recovery.mjs +49 -0
  365. package/src/shared/admin-auth.d.mts +2 -1
  366. package/src/shared/admin-auth.mjs +3 -3
  367. package/src/shared/authority-revocation.d.mts +9 -0
  368. package/src/shared/authority-revocation.mjs +28 -0
  369. package/src/shared/daemon-auth.d.mts +11 -0
  370. package/src/shared/daemon-auth.mjs +17 -0
  371. package/src/shared/device-session-auth.d.mts +20 -0
  372. package/src/shared/device-session-auth.mjs +51 -0
  373. package/src/shared/foreground-timeout.d.mts +15 -0
  374. package/src/shared/foreground-timeout.mjs +70 -0
  375. package/src/shared/log-redaction.d.mts +9 -0
  376. package/src/shared/log-redaction.mjs +51 -0
  377. package/src/shared/mcp-protocol.d.mts +27 -0
  378. package/src/shared/mcp-protocol.mjs +275 -0
  379. package/src/shared/project-overview-projection.d.mts +4 -0
  380. package/src/shared/project-overview-projection.mjs +96 -0
  381. package/src/shared/relay-contract.json +28 -0
  382. package/src/shared/result-projection.d.mts +2 -1
  383. package/src/shared/result-projection.mjs +13 -2
  384. package/src/shared/server-metadata.json +15 -9
  385. package/src/shared/tool-argument-validation.d.mts +17 -0
  386. package/src/shared/tool-argument-validation.mjs +325 -0
  387. package/src/shared/tool-call-capacity.d.mts +41 -0
  388. package/src/shared/tool-call-capacity.mjs +59 -0
  389. package/src/shared/tool-catalog.json +626 -57
  390. package/src/worker/access.ts +12 -0
  391. package/src/worker/account-admin.ts +55 -33
  392. package/src/worker/authority-revocations.ts +174 -0
  393. package/src/worker/authority.ts +52 -6
  394. package/src/worker/daemon-auth.ts +49 -68
  395. package/src/worker/daemon-channel.ts +36 -0
  396. package/src/worker/daemon-http-auth.ts +63 -0
  397. package/src/worker/daemon-http-channel.ts +120 -0
  398. package/src/worker/daemon-http-controller.ts +163 -0
  399. package/src/worker/daemon-http-protocol.ts +62 -0
  400. package/src/worker/daemon-http-registry.ts +82 -0
  401. package/src/worker/daemon-last-observation.ts +30 -0
  402. package/src/worker/daemon-liveness.ts +8 -6
  403. package/src/worker/daemon-ready-dispatch.ts +17 -0
  404. package/src/worker/daemon-ready-messages.ts +73 -0
  405. package/src/worker/daemon-ready-waiters.ts +79 -0
  406. package/src/worker/daemon-recovery-budget.ts +13 -0
  407. package/src/worker/daemon-registry.ts +58 -0
  408. package/src/worker/daemon-relay-diagnostics.ts +127 -0
  409. package/src/worker/daemon-socket-attachment.ts +55 -0
  410. package/src/worker/daemon-sockets.ts +72 -62
  411. package/src/worker/daemon-status.ts +38 -0
  412. package/src/worker/device-session-verifier.ts +129 -0
  413. package/src/worker/dpop.ts +157 -0
  414. package/src/worker/durable-process-timeout.ts +29 -0
  415. package/src/worker/errors.ts +57 -0
  416. package/src/worker/http.ts +112 -26
  417. package/src/worker/index.ts +446 -440
  418. package/src/worker/mcp-access.ts +57 -0
  419. package/src/worker/mcp-controller.ts +188 -0
  420. package/src/worker/mcp-http-accept.ts +11 -0
  421. package/src/worker/mcp-http-contract.ts +322 -0
  422. package/src/worker/mcp-initialization-compat.ts +141 -0
  423. package/src/worker/mcp-jsonrpc.ts +11 -29
  424. package/src/worker/mcp-removed-protocol.ts +17 -0
  425. package/src/worker/mcp-response-proxy.ts +135 -0
  426. package/src/worker/mcp-response-stream.ts +65 -0
  427. package/src/worker/mcp-stale-schema-compat.ts +57 -0
  428. package/src/worker/mcp-stream-proxy-contract.ts +48 -0
  429. package/src/worker/mcp-tool-call-input.ts +23 -0
  430. package/src/worker/nonce-store.ts +33 -35
  431. package/src/worker/oauth-authorization-page.ts +21 -1
  432. package/src/worker/oauth-client-admin.ts +52 -0
  433. package/src/worker/oauth-client-contract.ts +38 -0
  434. package/src/worker/oauth-controller.ts +107 -47
  435. package/src/worker/oauth-field-contract.ts +31 -0
  436. package/src/worker/oauth-record-contract.ts +7 -0
  437. package/src/worker/oauth-refresh-authority.ts +27 -0
  438. package/src/worker/oauth-refresh-exchange.ts +162 -0
  439. package/src/worker/oauth-refresh-families.ts +130 -27
  440. package/src/worker/oauth-refresh-persistence.ts +116 -0
  441. package/src/worker/oauth-state.ts +33 -18
  442. package/src/worker/oauth-store-validation.ts +131 -0
  443. package/src/worker/oauth-token-derivation.ts +33 -0
  444. package/src/worker/oauth-token-issuance.ts +119 -0
  445. package/src/worker/oauth-tokens.ts +56 -190
  446. package/src/worker/observability.ts +38 -5
  447. package/src/worker/pending-admission.ts +15 -0
  448. package/src/worker/pending-call-capacity.ts +108 -0
  449. package/src/worker/pending-call-contract.ts +42 -5
  450. package/src/worker/pending-call-deadlines.ts +75 -0
  451. package/src/worker/pending-calls.ts +131 -125
  452. package/src/worker/runtime-alarm-storage.ts +37 -0
  453. package/src/worker/runtime-alarm.ts +136 -0
  454. package/src/worker/server-info-activity.ts +36 -0
  455. package/src/worker/server-info-tool-delivery.ts +15 -0
  456. package/src/worker/server-info.ts +168 -0
  457. package/src/worker/tool-catalog.ts +47 -1
  458. package/src/worker/tool-timeout.ts +75 -14
  459. package/src/worker/websocket-protocol.ts +26 -2
  460. package/src/worker/worker-edge-guard.ts +90 -0
  461. package/src/worker/worker-edge-log.ts +65 -0
  462. package/src/worker/worker-entry.ts +52 -0
  463. package/src/worker/worker-mcp-config.ts +23 -0
  464. package/src/worker/worker-metadata.ts +48 -0
  465. package/src/worker/worker-rate-limit-key.ts +30 -0
  466. package/src/worker/worker-runtime-config.ts +20 -0
  467. package/src/worker/worker-static-routes.ts +54 -0
  468. package/src/worker/worker-task-lifetime.ts +18 -0
  469. package/tsconfig.local.json +17 -1
  470. package/wrangler.jsonc +13 -0
  471. package/src/local/cli-approval.mjs +0 -117
  472. package/src/local/operation-state-lock.mjs +0 -92
  473. package/src/worker/mcp-session.ts +0 -72
@@ -8,40 +8,31 @@ import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
11
+ import { parseReleaseVersion } from "./release-channel.mjs";
12
+ import { nestedNpmEnvironment } from "../src/local/npm-environment.mjs";
13
+ import { releaseCommandFailure } from "./release-diagnostic.mjs";
11
14
 
12
15
  export const ACCEPTANCE_SCHEMA_VERSION = 1;
13
- export const ACCEPTANCE_POLICY_VERSION = "1.2.8";
14
- export const AGENT_VERIFIED_ACCEPTANCE_VERSION = "1.2.9";
15
- export const AGENT_OPERATED_ACCEPTANCE_VERSION = "2.0.0";
16
- export const ACCEPTANCE_CONFIRMATION = "owner-authorized-agent-operated-local-candidate";
17
- export const OWNER_STARTED_ACCEPTANCE_CONFIRMATION = "owner-started-agent-verified-local-candidate";
18
- export const LEGACY_ACCEPTANCE_CONFIRMATION = "repository-owner-local-test";
16
+ export const ACCEPTANCE_CONFIRMATION = "owner-activated-agent-verified-persistent-candidate";
19
17
  const MAX_ACCEPTANCE_BYTES = 64 * 1024;
20
18
  const MAX_RELEASE_TARBALL_BYTES = 64 * 1024 * 1024;
21
19
 
22
- export function requiresLocalAcceptance(version) {
23
- return compareVersions(parseVersion(version), parseVersion(ACCEPTANCE_POLICY_VERSION)) >= 0;
24
- }
25
-
26
- export function acceptanceConfirmationForVersion(version) {
27
- const parsed = parseVersion(version);
28
- if (compareVersions(parsed, parseVersion(AGENT_OPERATED_ACCEPTANCE_VERSION)) >= 0) return ACCEPTANCE_CONFIRMATION;
29
- if (compareVersions(parsed, parseVersion(AGENT_VERIFIED_ACCEPTANCE_VERSION)) >= 0) return OWNER_STARTED_ACCEPTANCE_CONFIRMATION;
30
- return LEGACY_ACCEPTANCE_CONFIRMATION;
31
- }
32
-
33
20
  export function acceptancePath(root, version) {
34
21
  return join(root, "release-acceptance", `v${version}.json`);
35
22
  }
36
23
 
37
- export function packProject(root, destination) {
38
- const npmCli = process.env.npm_execpath;
24
+ export function packProject(root, destination, options = {}) {
25
+ const npmCli = options.npmCli || process.env.npm_execpath;
39
26
  if (!npmCli) {
40
27
  throw new Error("release acceptance commands must run through npm so npm_execpath is available");
41
28
  }
42
29
  const result = spawnSync(process.execPath, [
43
30
  npmCli,
44
31
  "pack",
32
+ "--dry-run=false",
33
+ "--workspaces=false",
34
+ "--global=false",
35
+ "--prefix", root,
45
36
  "--ignore-scripts",
46
37
  "--silent",
47
38
  "--json",
@@ -50,13 +41,13 @@ export function packProject(root, destination) {
50
41
  ], {
51
42
  cwd: root,
52
43
  encoding: "utf8",
53
- env: process.env,
44
+ env: nestedNpmEnvironment(options.env || process.env),
45
+ timeout: 5 * 60 * 1000,
46
+ killSignal: "SIGKILL",
47
+ maxBuffer: 8 * 1024 * 1024,
54
48
  windowsHide: true,
55
49
  });
56
- if (result.error) throw result.error;
57
- if (result.status !== 0) {
58
- throw new Error(`npm pack failed: ${String(result.stderr || result.stdout).trim()}`);
59
- }
50
+ if (result.error || result.status !== 0) throw new Error(releaseCommandFailure("npm", ["pack"], result));
60
51
  let value;
61
52
  try {
62
53
  value = JSON.parse(result.stdout);
@@ -84,12 +75,12 @@ export function readAcceptance(root, version) {
84
75
  try {
85
76
  bytes = readBoundedRegularFileSync(
86
77
  path, MAX_ACCEPTANCE_BYTES, "release acceptance record",
87
- { verifyPathIdentity: true },
78
+ { verifyPathIdentity: true, rejectMultipleLinks: true },
88
79
  );
89
80
  } catch (error) {
90
81
  const message = String(error?.message || error);
91
82
  if (message.includes("exceeds")) throw new Error(`release acceptance record exceeds ${MAX_ACCEPTANCE_BYTES} bytes`);
92
- if (message.includes("regular file") || message.includes("symbolic link") || message.includes("identity changed")) {
83
+ if (message.includes("regular file") || message.includes("symbolic link") || message.includes("multiple links") || message.includes("identity changed")) {
93
84
  throw new Error(`release acceptance record must be a regular file: ${path}`);
94
85
  }
95
86
  throw error;
@@ -105,12 +96,17 @@ export function verifyAcceptanceRecord(record, metadata) {
105
96
  if (!record || typeof record !== "object" || Array.isArray(record)) {
106
97
  throw new Error("release acceptance record must be an object");
107
98
  }
99
+ const allowed = new Set([
100
+ "schema_version", "result", "confirmation", "package_name", "package_version", "filename",
101
+ "shasum", "integrity", "accepted_at", "package_content_sha256", "promotion_content_sha256",
102
+ ]);
103
+ const unknown = Object.keys(record).filter((key) => !allowed.has(key));
104
+ if (unknown.length) throw new Error(`release acceptance record contains unsupported fields: ${unknown.join(", ")}`);
108
105
  if (record.schema_version !== ACCEPTANCE_SCHEMA_VERSION) {
109
106
  throw new Error(`unsupported release acceptance schema: ${record.schema_version}`);
110
107
  }
111
108
  if (record.result !== "passed") throw new Error("local release acceptance result is not passed");
112
- const expectedConfirmation = acceptanceConfirmationForVersion(metadata.package_version);
113
- if (record.confirmation !== expectedConfirmation) {
109
+ if (record.confirmation !== ACCEPTANCE_CONFIRMATION) {
114
110
  throw new Error("local release acceptance confirmation is missing or does not match the active verification workflow");
115
111
  }
116
112
  const acceptedAt = Date.parse(String(record.accepted_at || ""));
@@ -118,28 +114,53 @@ export function verifyAcceptanceRecord(record, metadata) {
118
114
  if (!/^[0-9a-f]{64}$/.test(String(record.package_content_sha256 || ""))) {
119
115
  throw new Error("local release acceptance portable package-content digest is missing or invalid");
120
116
  }
117
+ if (!/^[0-9a-f]{64}$/.test(String(record.promotion_content_sha256 || ""))) {
118
+ throw new Error("local release acceptance promotion-content digest is missing or invalid");
119
+ }
121
120
  for (const key of ["package_name", "package_version", "filename", "shasum", "integrity"]) {
122
121
  if (record[key] !== metadata[key]) {
123
122
  throw new Error(`local release acceptance ${key} does not match the current npm package`);
124
123
  }
125
124
  }
126
- return record;
125
+ return Object.freeze({
126
+ schema_version: ACCEPTANCE_SCHEMA_VERSION,
127
+ result: "passed",
128
+ confirmation: ACCEPTANCE_CONFIRMATION,
129
+ package_name: metadata.package_name,
130
+ package_version: metadata.package_version,
131
+ filename: metadata.filename,
132
+ shasum: metadata.shasum,
133
+ integrity: metadata.integrity,
134
+ accepted_at: new Date(acceptedAt).toISOString(),
135
+ package_content_sha256: String(record.package_content_sha256),
136
+ ...(record.promotion_content_sha256 ? { promotion_content_sha256: String(record.promotion_content_sha256) } : {}),
137
+ });
127
138
  }
128
139
 
129
- export function verifyCurrentReleaseAcceptance(root) {
140
+ export function verifyCurrentReleaseAcceptance(root, options = {}) {
130
141
  const pkg = readPackage(root);
131
- if (!requiresLocalAcceptance(pkg.version)) {
132
- return { required: false, version: pkg.version };
133
- }
134
142
  const temp = mkdtempSync(join(tmpdir(), "mbm-release-acceptance-"));
143
+ let result = null;
144
+ let primaryError = null;
135
145
  try {
136
- const metadata = packProject(root, temp);
146
+ const metadata = packProject(root, temp, options);
137
147
  const record = readAcceptance(root, pkg.version);
138
148
  verifyAcceptanceRecord(record, metadata);
139
- return { required: true, metadata, record };
140
- } finally {
141
- rmSync(temp, { recursive: true, force: true });
149
+ const bytes = verifyTarball(join(temp, metadata.filename), metadata);
150
+ const artifactSha256 = createHash("sha256").update(bytes).digest("hex");
151
+ result = { required: true, metadata, record, artifactSha256 };
152
+ } catch (error) {
153
+ primaryError = error;
154
+ }
155
+ let cleanupError = null;
156
+ try { rmSync(temp, { recursive: true, force: true }); }
157
+ catch (error) { cleanupError = error; }
158
+ if (primaryError && cleanupError) {
159
+ throw new AggregateError([primaryError, cleanupError], "release acceptance verification failed and temporary cleanup was incomplete");
142
160
  }
161
+ if (primaryError) throw primaryError;
162
+ if (cleanupError) throw cleanupError;
163
+ return result;
143
164
  }
144
165
 
145
166
  export function verifyTarball(path, metadata) {
@@ -147,11 +168,11 @@ export function verifyTarball(path, metadata) {
147
168
  try {
148
169
  bytes = readBoundedRegularFileSync(
149
170
  path, MAX_RELEASE_TARBALL_BYTES, "release candidate tarball",
150
- { verifyPathIdentity: true },
171
+ { verifyPathIdentity: true, rejectMultipleLinks: true },
151
172
  );
152
173
  } catch (error) {
153
174
  const message = String(error?.message || error);
154
- if (message.includes("regular file") || message.includes("symbolic link") || message.includes("identity changed")) {
175
+ if (message.includes("regular file") || message.includes("symbolic link") || message.includes("multiple hard links") || message.includes("identity changed")) {
155
176
  throw new Error("release candidate tarball is not a regular file");
156
177
  }
157
178
  throw error;
@@ -161,6 +182,7 @@ export function verifyTarball(path, metadata) {
161
182
  if (shasum !== metadata.shasum || integrity !== metadata.integrity) {
162
183
  throw new Error("release candidate tarball hash does not match npm pack metadata");
163
184
  }
185
+ return bytes;
164
186
  }
165
187
 
166
188
  export function normalizePackRecord(value, packageName) {
@@ -173,7 +195,7 @@ export function normalizePackRecord(value, packageName) {
173
195
  function readPackage(root) {
174
196
  const value = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
175
197
  if (typeof value.name !== "string" || !value.name) throw new Error("package.json name is invalid");
176
- if (!parseVersion(value.version)) throw new Error("package.json version is invalid");
198
+ parseReleaseVersion(value.version);
177
199
  return value;
178
200
  }
179
201
 
@@ -184,17 +206,3 @@ function validatePackMetadata(metadata) {
184
206
  if (!/^[0-9a-f]{40}$/.test(metadata.shasum)) throw new Error("npm pack returned an invalid SHA-1 shasum");
185
207
  if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(metadata.integrity)) throw new Error("npm pack returned an invalid SHA-512 integrity value");
186
208
  }
187
-
188
- function parseVersion(value) {
189
- const match = /^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(String(value || ""));
190
- if (!match) return null;
191
- return [Number(match[1]), Number(match[2]), Number(match[3])];
192
- }
193
-
194
- function compareVersions(left, right) {
195
- if (!left || !right) throw new Error("version comparison requires semantic versions");
196
- for (let index = 0; index < 3; index += 1) {
197
- if (left[index] !== right[index]) return left[index] - right[index];
198
- }
199
- return 0;
200
- }
@@ -0,0 +1,172 @@
1
+ // @ts-check
2
+ import {
3
+ closeSync, constants as fsConstants, fsyncSync, lstatSync, readSync,
4
+ readdirSync, rmdirSync, unlinkSync, writeSync,
5
+ } from "node:fs";
6
+ import { randomBytes } from "node:crypto";
7
+ import { dirname, join, relative, resolve, sep } from "node:path";
8
+ import { replaceFileSync } from "../src/local/atomic-fs.mjs";
9
+ import { releaseBrowserExtensionPath } from "../src/local/browser-extension-path.mjs";
10
+ import { ensureOwnerOnlyDirectorySync, inspectPathIfPresentSync, openRegularFileSync } from "../src/local/secure-file.mjs";
11
+
12
+ const MANIFEST = "manifest.json";
13
+ const MAX_EXTENSION_FILES = 256;
14
+ const MAX_EXTENSION_FILE_BYTES = 8 * 1024 * 1024;
15
+ const MAX_EXTENSION_TOTAL_BYTES = 32 * 1024 * 1024;
16
+
17
+ export function publishReleaseBrowserExtension({ stateRoot, sourceDirectory, expectedVersion = "", beforeManifestCommit = null } = {}) {
18
+ const source = resolve(String(sourceDirectory || ""));
19
+ const destination = releaseBrowserExtensionPath(stateRoot);
20
+ const snapshot = snapshotExtensionTree(source);
21
+ if (!snapshot.files.some((entry) => entry.relative === MANIFEST)) throw new Error("release browser extension is missing manifest.json");
22
+ const manifest = parseManifest(snapshot.files.find((entry) => entry.relative === MANIFEST)?.bytes);
23
+ if (expectedVersion && manifest.version !== expectedVersion) {
24
+ throw new Error(`release browser extension version ${manifest.version} does not match ${expectedVersion}`);
25
+ }
26
+
27
+ ensureOwnerOnlyDirectorySync(dirname(destination));
28
+ ensureOwnerOnlyDirectorySync(destination);
29
+ validateDestinationTree(destination);
30
+ for (const directory of snapshot.directories) ensureOwnerOnlyDirectorySync(join(destination, directory));
31
+ for (const entry of snapshot.files.filter((item) => item.relative !== MANIFEST)) {
32
+ replaceStableFile(destination, entry.relative, entry.bytes);
33
+ }
34
+ removeStaleEntries(destination, snapshot);
35
+ if (typeof beforeManifestCommit === "function") beforeManifestCommit({ destination, version: manifest.version });
36
+ const manifestEntry = snapshot.files.find((entry) => entry.relative === MANIFEST);
37
+ replaceStableFile(destination, MANIFEST, manifestEntry.bytes);
38
+ verifyPublishedTree(destination, snapshot);
39
+ return { path: destination, version: manifest.version, file_count: snapshot.files.length };
40
+ }
41
+
42
+ function snapshotExtensionTree(source) {
43
+ const rootInfo = inspectPathIfPresentSync(source, "release browser extension source");
44
+ if (!rootInfo || rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) {
45
+ throw new Error("release browser extension source must be a real directory");
46
+ }
47
+ const files = [];
48
+ const directories = [];
49
+ let totalBytes = 0;
50
+ const walk = (directory, relativeDirectory = "") => {
51
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
52
+ const relativePath = relativeDirectory ? join(relativeDirectory, entry.name) : entry.name;
53
+ const absolutePath = join(directory, entry.name);
54
+ const info = lstatSync(absolutePath);
55
+ if (info.isSymbolicLink()) throw new Error("release browser extension source must not contain symbolic links");
56
+ if (info.isDirectory()) {
57
+ directories.push(relativePath);
58
+ walk(absolutePath, relativePath);
59
+ continue;
60
+ }
61
+ if (!info.isFile()) throw new Error("release browser extension source contains an unsupported entry type");
62
+ if (files.length >= MAX_EXTENSION_FILES) throw new Error("release browser extension contains too many files");
63
+ const bytes = readRegularFile(absolutePath, MAX_EXTENSION_FILE_BYTES, "release browser extension source file");
64
+ totalBytes += bytes.length;
65
+ if (totalBytes > MAX_EXTENSION_TOTAL_BYTES) throw new Error("release browser extension exceeds the total byte budget");
66
+ files.push({ relative: relativePath, bytes });
67
+ }
68
+ };
69
+ walk(source);
70
+ return { files, directories };
71
+ }
72
+
73
+ function replaceStableFile(destination, relativePath, bytes) {
74
+ const target = join(destination, relativePath);
75
+ const targetInfo = inspectPathIfPresentSync(target, "stable browser extension target");
76
+ if (targetInfo && (targetInfo.isSymbolicLink() || !targetInfo.isFile())) {
77
+ throw new Error("stable browser extension target must be a regular file");
78
+ }
79
+ ensureOwnerOnlyDirectorySync(dirname(target));
80
+ const temporary = join(dirname(target), `.mbm-extension-${process.pid}-${randomBytes(8).toString("hex")}.tmp`);
81
+ let fd;
82
+ try {
83
+ const opened = openRegularFileSync(
84
+ temporary,
85
+ Number(fsConstants.O_WRONLY) | Number(fsConstants.O_CREAT) | Number(fsConstants.O_EXCL),
86
+ { label: "stable browser extension staging file", mode: 0o600, chmod: 0o600, rejectMultipleLinks: true },
87
+ );
88
+ fd = opened.fd;
89
+ let offset = 0;
90
+ while (offset < bytes.length) offset += writeSync(fd, bytes, offset, bytes.length - offset, offset);
91
+ fsyncSync(fd);
92
+ } finally {
93
+ if (fd !== undefined) closeSync(fd);
94
+ }
95
+ try { replaceFileSync(temporary, target); }
96
+ catch (error) { try { unlinkSync(temporary); } catch { /* Preserve the primary publication error; temp cleanup is best-effort. */ } throw error; }
97
+ }
98
+
99
+ function removeStaleEntries(destination, snapshot) {
100
+ const expectedFiles = new Set(snapshot.files.map((entry) => entry.relative));
101
+ const expectedDirectories = new Set(snapshot.directories);
102
+ const staleDirectories = [];
103
+ const walk = (directory, relativeDirectory = "") => {
104
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
105
+ const relativePath = relativeDirectory ? join(relativeDirectory, entry.name) : entry.name;
106
+ const absolutePath = join(directory, entry.name);
107
+ const info = lstatSync(absolutePath);
108
+ if (info.isSymbolicLink()) throw new Error("stable browser extension must not contain symbolic links");
109
+ if (info.isDirectory()) {
110
+ walk(absolutePath, relativePath);
111
+ if (!expectedDirectories.has(relativePath)) staleDirectories.push(absolutePath);
112
+ continue;
113
+ }
114
+ if (!info.isFile()) throw new Error("stable browser extension contains an unsupported entry type");
115
+ if (!expectedFiles.has(relativePath)) unlinkSync(absolutePath);
116
+ }
117
+ };
118
+ walk(destination);
119
+ staleDirectories.sort((left, right) => right.length - left.length);
120
+ for (const directory of staleDirectories) rmdirSync(directory);
121
+ }
122
+
123
+ function validateDestinationTree(destination) {
124
+ const root = resolve(destination);
125
+ const walk = (directory) => {
126
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
127
+ const absolutePath = join(directory, entry.name);
128
+ const info = lstatSync(absolutePath);
129
+ if (info.isSymbolicLink()) throw new Error("stable browser extension must not contain symbolic links");
130
+ if (info.isDirectory()) { walk(absolutePath); continue; }
131
+ if (!info.isFile()) throw new Error("stable browser extension contains an unsupported entry type");
132
+ const rel = relative(root, absolutePath);
133
+ if (!rel || rel === ".." || rel.startsWith(`..${sep}`)) throw new Error("stable browser extension entry escaped its root");
134
+ }
135
+ };
136
+ walk(root);
137
+ }
138
+
139
+ function verifyPublishedTree(destination, snapshot) {
140
+ const actual = snapshotExtensionTree(destination);
141
+ const expectedFiles = new Map(snapshot.files.map((entry) => [entry.relative, entry.bytes]));
142
+ if (actual.files.length !== expectedFiles.size) throw new Error("stable browser extension file inventory does not match the candidate");
143
+ for (const entry of actual.files) {
144
+ const expected = expectedFiles.get(entry.relative);
145
+ if (!expected || !entry.bytes.equals(expected)) throw new Error("stable browser extension bytes do not match the candidate");
146
+ }
147
+ }
148
+
149
+ function readRegularFile(file, maximum, label) {
150
+ const opened = openRegularFileSync(file, fsConstants.O_RDONLY, { label, verifyPathIdentity: true });
151
+ try {
152
+ if (opened.info.size > maximum) throw new Error(`${label} exceeds ${maximum} bytes`);
153
+ const bytes = Buffer.alloc(opened.info.size);
154
+ let offset = 0;
155
+ while (offset < bytes.length) {
156
+ const count = readSync(opened.fd, bytes, offset, bytes.length - offset, offset);
157
+ if (!count) break;
158
+ offset += count;
159
+ }
160
+ return bytes.subarray(0, offset);
161
+ } finally {
162
+ closeSync(opened.fd);
163
+ }
164
+ }
165
+
166
+ function parseManifest(bytes) {
167
+ let value;
168
+ try { value = JSON.parse(String(bytes || "")); } catch { throw new Error("release browser extension manifest is invalid JSON"); }
169
+ const version = typeof value?.version_name === "string" && value.version_name ? value.version_name : value?.version;
170
+ if (typeof version !== "string" || !version) throw new Error("release browser extension manifest has no version");
171
+ return { version };
172
+ }
@@ -0,0 +1,52 @@
1
+ import { parseReleaseVersion } from "./release-channel.mjs";
2
+
3
+ export const CANDIDATE_MANIFEST_SCHEMA_VERSION = 1;
4
+ const FIELDS = new Set([
5
+ "schema_version", "result", "package_name", "package_version", "filename", "shasum", "integrity",
6
+ "promotion_content_sha256", "prepared_at",
7
+ ]);
8
+
9
+ export function validateCandidateManifest(value, expected = {}) {
10
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("release candidate manifest must be an object");
11
+ const unknown = Object.keys(value).filter((key) => !FIELDS.has(key));
12
+ if (unknown.length) throw new Error(`release candidate manifest contains unsupported fields: ${unknown.join(", ")}`);
13
+ if (value.schema_version !== CANDIDATE_MANIFEST_SCHEMA_VERSION || value.result !== "pending") {
14
+ throw new Error("release candidate manifest is not pending current-schema state");
15
+ }
16
+ const version = parseReleaseVersion(value.package_version).raw;
17
+ const packageName = String(value.package_name || "");
18
+ if (packageName !== "machine-bridge-mcp") throw new Error("release candidate manifest package is invalid");
19
+ if (expected.packageName && packageName !== expected.packageName) throw new Error("release candidate manifest package does not match the current package");
20
+ if (expected.packageVersion && version !== expected.packageVersion) throw new Error("release candidate manifest version does not match the current package");
21
+ const filename = String(value.filename || "");
22
+ const expectedFilename = `${packageName}-${version}.tgz`;
23
+ if (filename !== expectedFilename) throw new Error(`release candidate manifest filename is invalid; expected ${expectedFilename}`);
24
+ if (!/^[0-9a-f]{40}$/.test(String(value.shasum || ""))) throw new Error("release candidate manifest SHA-1 is invalid");
25
+ if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(String(value.integrity || ""))) throw new Error("release candidate manifest integrity is invalid");
26
+ if (!/^[0-9a-f]{64}$/.test(String(value.promotion_content_sha256 || ""))) throw new Error("release candidate promotion digest is invalid");
27
+ const preparedAt = Date.parse(String(value.prepared_at || ""));
28
+ if (!Number.isFinite(preparedAt)) throw new Error("release candidate preparation timestamp is invalid");
29
+ return Object.freeze({
30
+ schema_version: CANDIDATE_MANIFEST_SCHEMA_VERSION,
31
+ result: "pending",
32
+ package_name: packageName,
33
+ package_version: version,
34
+ filename,
35
+ shasum: String(value.shasum),
36
+ integrity: String(value.integrity),
37
+ promotion_content_sha256: String(value.promotion_content_sha256),
38
+ prepared_at: new Date(preparedAt).toISOString(),
39
+ });
40
+ }
41
+
42
+ export function assertCandidateMatchesCurrentSource(manifest, current) {
43
+ const packageName = String(current?.packageName || "");
44
+ const packageVersion = String(current?.packageVersion || "");
45
+ const promotionDigest = String(current?.promotionDigest || "");
46
+ if (manifest.package_name !== packageName || manifest.package_version !== packageVersion) {
47
+ throw new Error("release candidate is stale: package identity no longer matches the current source");
48
+ }
49
+ if (!/^[0-9a-f]{64}$/.test(promotionDigest) || manifest.promotion_content_sha256 !== promotionDigest) {
50
+ throw new Error("release candidate is stale: promotion content digest no longer matches the current source");
51
+ }
52
+ }
@@ -0,0 +1,106 @@
1
+ const CHANNEL_TAGS = Object.freeze({ dev: "dev", beta: "beta", rc: "next" });
2
+ const SOAK_SECONDS = Object.freeze({ major: 7 * 24 * 60 * 60, minor: 3 * 24 * 60 * 60, patch: 24 * 60 * 60 });
3
+
4
+ export const SOAK_POLICY_VERSION = "3.0.0";
5
+ export const SOAK_CONFIRMATION = "owner-reported-published-prerelease-soak-passed";
6
+
7
+ export function parseReleaseVersion(value) {
8
+ const raw = String(value || "").trim();
9
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z]+)\.(\d+))?$/.exec(raw);
10
+ if (!match) throw new Error(`release version must use x.y.z or x.y.z-(dev|beta|rc).n: ${raw || "<empty>"}`);
11
+ const channel = match[4] || "";
12
+ if (channel && !Object.hasOwn(CHANNEL_TAGS, channel)) {
13
+ throw new Error(`unsupported prerelease channel ${channel}; use dev, beta, or rc`);
14
+ }
15
+ const sequence = channel ? Number(match[5]) : 0;
16
+ if (channel && (!Number.isSafeInteger(sequence) || sequence < 1)) {
17
+ throw new Error("prerelease sequence must be a positive integer");
18
+ }
19
+ const major = Number(match[1]);
20
+ const minor = Number(match[2]);
21
+ const patch = Number(match[3]);
22
+ for (const part of [major, minor, patch]) {
23
+ if (!Number.isSafeInteger(part) || part < 0) throw new Error("release version contains an invalid numeric component");
24
+ }
25
+ return Object.freeze({
26
+ raw,
27
+ major,
28
+ minor,
29
+ patch,
30
+ channel,
31
+ sequence,
32
+ prerelease: Boolean(channel),
33
+ baseVersion: `${major}.${minor}.${patch}`,
34
+ npmTag: channel ? CHANNEL_TAGS[channel] : "latest",
35
+ });
36
+ }
37
+
38
+ export function compareReleaseVersions(leftValue, rightValue) {
39
+ const left = typeof leftValue === "string" ? parseReleaseVersion(leftValue) : leftValue;
40
+ const right = typeof rightValue === "string" ? parseReleaseVersion(rightValue) : rightValue;
41
+ for (const key of ["major", "minor", "patch"]) {
42
+ if (left[key] !== right[key]) return left[key] - right[key];
43
+ }
44
+ if (!left.prerelease && !right.prerelease) return 0;
45
+ if (!left.prerelease) return 1;
46
+ if (!right.prerelease) return -1;
47
+ const channelOrder = { dev: 0, beta: 1, rc: 2 };
48
+ if (left.channel !== right.channel) return channelOrder[left.channel] - channelOrder[right.channel];
49
+ return left.sequence - right.sequence;
50
+ }
51
+
52
+ export function requiresSoakForStable(version) {
53
+ const parsed = parseReleaseVersion(version);
54
+ return !parsed.prerelease && compareReleaseVersions(parsed, parseReleaseVersion(SOAK_POLICY_VERSION)) >= 0;
55
+ }
56
+
57
+ export function assertPublishTag(version, requestedTag) {
58
+ const parsed = parseReleaseVersion(version);
59
+ const actual = String(requestedTag || (parsed.prerelease ? "latest" : "latest")).trim().toLowerCase();
60
+ if (actual !== parsed.npmTag) {
61
+ throw new Error(`${parsed.raw} must be published with npm dist-tag ${parsed.npmTag}, not ${actual || "<empty>"}`);
62
+ }
63
+ return parsed.npmTag;
64
+ }
65
+
66
+ export function assertSoakEligiblePrerelease(version) {
67
+ const parsed = parseReleaseVersion(version);
68
+ if (!parsed.prerelease || !["beta", "rc"].includes(parsed.channel)) {
69
+ throw new Error("stable-release soak requires a published beta or rc version");
70
+ }
71
+ return parsed;
72
+ }
73
+
74
+ export function assertStablePromotion(stableVersion, prereleaseVersion) {
75
+ const stable = parseReleaseVersion(stableVersion);
76
+ const prerelease = assertSoakEligiblePrerelease(prereleaseVersion);
77
+ if (stable.prerelease) throw new Error("stable promotion target must not contain a prerelease suffix");
78
+ if (stable.baseVersion !== prerelease.baseVersion) {
79
+ throw new Error(`prerelease ${prerelease.raw} cannot promote to unrelated stable version ${stable.raw}`);
80
+ }
81
+ return { stable, prerelease };
82
+ }
83
+
84
+ export function minimumSoakSeconds(baseVersion, previousStableVersion = "") {
85
+ const target = parseReleaseVersion(baseVersion);
86
+ if (target.prerelease) throw new Error("minimum soak target must be a stable base version");
87
+ if (!previousStableVersion) {
88
+ if (target.minor === 0 && target.patch === 0) return SOAK_SECONDS.major;
89
+ if (target.patch === 0) return SOAK_SECONDS.minor;
90
+ return SOAK_SECONDS.patch;
91
+ }
92
+ const previous = parseReleaseVersion(previousStableVersion);
93
+ if (previous.prerelease) throw new Error("previous stable version must not be a prerelease");
94
+ if (compareReleaseVersions(target, previous) <= 0) throw new Error("target stable version must be newer than the previous stable version");
95
+ if (target.major !== previous.major) return SOAK_SECONDS.major;
96
+ if (target.minor !== previous.minor) return SOAK_SECONDS.minor;
97
+ return SOAK_SECONDS.patch;
98
+ }
99
+
100
+ export function formatSoakDuration(seconds) {
101
+ const value = Number(seconds);
102
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error("soak duration must be a non-negative integer");
103
+ if (value % (24 * 60 * 60) === 0) return `${value / (24 * 60 * 60)}d`;
104
+ if (value % (60 * 60) === 0) return `${value / (60 * 60)}h`;
105
+ return `${value}s`;
106
+ }
@@ -0,0 +1,41 @@
1
+ import os from "node:os";
2
+ import process from "node:process";
3
+ import { sanitizePortableLogText } from "../src/shared/log-redaction.mjs";
4
+
5
+ const EVENT_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,79}$/;
6
+
7
+ const HOME_PATHS = [...new Set([
8
+ process.env.HOME,
9
+ process.env.USERPROFILE,
10
+ safeHomeDirectory(),
11
+ ].filter((value) => typeof value === "string" && value.length > 1))];
12
+
13
+ export function releaseDiagnostic(value, maxChars = 1000) {
14
+ return sanitizePortableLogText(value, { maxChars, homePaths: HOME_PATHS }).trim();
15
+ }
16
+
17
+ export function releaseDiagnosticEvent(event, value, maxChars = 1000) {
18
+ const name = String(event || "");
19
+ if (!EVENT_NAME_PATTERN.test(name)) throw new Error("release diagnostic event name is invalid");
20
+ return Object.freeze({ event: name, error: releaseDiagnostic(value, maxChars) });
21
+ }
22
+
23
+ export function releaseCommandLabel(command, args = []) {
24
+ const executable = String(command || "command").split(/[\\/]/).filter(Boolean).at(-1) || "command";
25
+ const first = Array.isArray(args) && args.length ? String(args[0] || "") : "";
26
+ return releaseDiagnostic(first ? `${executable} ${first}` : executable, 160);
27
+ }
28
+
29
+ export function releaseCommandFailure(command, args, result, options = {}) {
30
+ const label = releaseCommandLabel(command, args);
31
+ const detail = releaseDiagnostic([
32
+ result?.stderr,
33
+ result?.stdout,
34
+ result?.error?.message,
35
+ ].filter(Boolean).join("\n"), options.maxChars || 1000);
36
+ return `${label} failed${detail ? `: ${detail}` : ""}`;
37
+ }
38
+
39
+ function safeHomeDirectory() {
40
+ try { return os.homedir(); } catch { return ""; }
41
+ }
@@ -2,12 +2,17 @@
2
2
 
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { readFileSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import { compareReleaseVersions, parseReleaseVersion } from "./release-channel.mjs";
7
+ import { createTrustedGitResolver } from "../src/local/trusted-git-executable.mjs";
5
8
 
6
9
  const VERSION_TAG = /^v(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
7
10
 
8
- const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
9
- const current = parseVersion(String(pkg.version || ""));
10
- if (!current) fail("package.json contains an invalid version");
11
+ const root = new URL("../", import.meta.url);
12
+ const pkg = JSON.parse(readFileSync(new URL("package.json", root), "utf8"));
13
+ const gitExecutable = createTrustedGitResolver({ workspace: fileURLToPath(root) });
14
+ let current;
15
+ try { current = parseReleaseVersion(String(pkg.version || "")); } catch { fail("package.json contains an invalid version"); }
11
16
 
12
17
  const tags = git(["tag", "--merged", "HEAD", "--sort=-version:refname", "--list", "v[0-9]*"])
13
18
  .split("\n")
@@ -19,7 +24,7 @@ if (!tags.length) {
19
24
  }
20
25
 
21
26
  const latestTag = tags[0];
22
- const latest = parseVersion(latestTag.slice(1));
27
+ const latest = parseReleaseVersion(latestTag.slice(1));
23
28
  const changed = new Set([
24
29
  ...lines(git(["diff", "--name-only", latestTag, "--"])),
25
30
  ...lines(git(["ls-files", "--others", "--exclude-standard"])),
@@ -35,11 +40,11 @@ if (!packageRelevant.length) {
35
40
  process.stderr.write(`release impact check ok: ${relevant.length} repository-only file(s) changed since ${latestTag}; npm package content is unchanged\n`);
36
41
  process.exit(0);
37
42
  }
38
- if (compareVersions(current, latest) <= 0) {
43
+ if (compareReleaseVersions(current, latest) <= 0) {
39
44
  fail(`npm-package changes exist since ${latestTag}, but package.json is still ${pkg.version}; bump the npm version and add a CHANGELOG section before merging`);
40
45
  }
41
46
 
42
- const changelog = readFileSync(new URL("../CHANGELOG.md", import.meta.url), "utf8");
47
+ const changelog = readFileSync(new URL("CHANGELOG.md", root), "utf8");
43
48
  const heading = new RegExp(`^## ${escapeRegExp(pkg.version)}(?:\\s+-[^\\n]*)?$`, "m");
44
49
  if (!heading.test(changelog)) fail(`CHANGELOG.md has no section for ${pkg.version}`);
45
50
 
@@ -58,7 +63,7 @@ export function isPackageRelevant(path, packageFiles = []) {
58
63
 
59
64
  function git(args) {
60
65
  try {
61
- return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
66
+ return execFileSync(gitExecutable(), args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
62
67
  } catch (error) {
63
68
  const detail = String(error?.stderr || error?.message || error).trim();
64
69
  fail(`git ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
@@ -69,21 +74,6 @@ function lines(value) {
69
74
  return value ? value.split("\n").map((item) => item.trim()).filter(Boolean) : [];
70
75
  }
71
76
 
72
- function parseVersion(value) {
73
- const match = String(value).match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
74
- return match ? { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]), prerelease: match[4] || "" } : null;
75
- }
76
-
77
- function compareVersions(left, right) {
78
- for (const key of ["major", "minor", "patch"]) {
79
- if (left[key] !== right[key]) return left[key] - right[key];
80
- }
81
- if (left.prerelease === right.prerelease) return 0;
82
- if (!left.prerelease) return 1;
83
- if (!right.prerelease) return -1;
84
- return left.prerelease.localeCompare(right.prerelease);
85
- }
86
-
87
77
  function escapeRegExp(value) {
88
78
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
89
79
  }