machine-bridge-mcp 1.2.8 → 1.2.10

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 (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/CONTRIBUTING.md +16 -5
  3. package/README.md +115 -416
  4. package/SECURITY.md +2 -0
  5. package/browser-extension/manifest.json +2 -2
  6. package/docs/ARCHITECTURE.md +19 -9
  7. package/docs/AUDIT.md +20 -0
  8. package/docs/ENGINEERING.md +2 -2
  9. package/docs/LOGGING.md +1 -1
  10. package/docs/MULTI_ACCOUNT.md +2 -2
  11. package/docs/OPERATIONS.md +2 -2
  12. package/docs/OVERVIEW.md +113 -0
  13. package/docs/PROJECT_STANDARDS.md +4 -4
  14. package/docs/RELEASING.md +25 -12
  15. package/docs/TESTING.md +12 -8
  16. package/docs/THREAT_MODEL.md +142 -0
  17. package/package.json +10 -3
  18. package/scripts/check-plan.mjs +91 -0
  19. package/scripts/coverage-check.mjs +22 -0
  20. package/scripts/github-push.mjs +1 -1
  21. package/scripts/github-release.mjs +1 -1
  22. package/scripts/local-release-acceptance.mjs +56 -6
  23. package/scripts/release-acceptance.mjs +15 -3
  24. package/scripts/release-state.mjs +1 -1
  25. package/scripts/run-checks.mjs +29 -0
  26. package/scripts/start-release-candidate.mjs +113 -0
  27. package/src/local/agent-context-projection.mjs +158 -0
  28. package/src/local/agent-context.mjs +23 -332
  29. package/src/local/agent-skill-discovery.mjs +230 -0
  30. package/src/local/agent-text-file.mjs +41 -0
  31. package/src/local/browser-bridge-http.mjs +48 -0
  32. package/src/local/browser-bridge.mjs +48 -222
  33. package/src/local/browser-broker-routes.mjs +136 -0
  34. package/src/local/browser-broker-server.mjs +59 -0
  35. package/src/local/browser-request-registry.mjs +67 -0
  36. package/src/local/managed-job-lock.mjs +99 -0
  37. package/src/local/managed-job-projection.mjs +68 -0
  38. package/src/local/managed-job-runner.mjs +73 -0
  39. package/src/local/managed-job-storage.mjs +93 -0
  40. package/src/local/managed-jobs.mjs +12 -297
  41. package/src/local/relay-call-recovery.mjs +148 -0
  42. package/src/local/relay-connection.mjs +5 -0
  43. package/src/local/runtime-paths.mjs +107 -0
  44. package/src/local/runtime-relay.mjs +89 -0
  45. package/src/local/runtime-tool-handlers.mjs +66 -0
  46. package/src/local/runtime.mjs +80 -242
  47. package/src/local/windows-launcher.mjs +57 -0
  48. package/src/local/windows-service.mjs +7 -56
  49. package/src/worker/daemon-sockets.ts +10 -1
  50. package/src/worker/index.ts +51 -124
  51. package/src/worker/mcp-jsonrpc.ts +94 -0
  52. package/src/worker/oauth-authorization-page.ts +70 -0
  53. package/src/worker/oauth-controller.ts +9 -58
  54. package/src/worker/pending-call-contract.ts +26 -0
  55. package/src/worker/pending-calls.ts +41 -25
  56. package/src/worker/websocket-protocol.ts +24 -0
  57. package/tsconfig.local.json +7 -1
package/SECURITY.md CHANGED
@@ -27,6 +27,8 @@ CodeQL and OpenSSF Scorecard are enforced as gates, not merely uploaded as advis
27
27
 
28
28
  ## Core trust model
29
29
 
30
+ The concise security model is in [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md), including assets, attacker classes, explicit non-goals, and residual risks.
31
+
30
32
  Trusted components are:
31
33
 
32
34
  1. the local OS user running the runtime;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Machine Bridge Browser",
4
- "version": "1.2.8",
4
+ "version": "1.2.10",
5
5
  "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -30,5 +30,5 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "1.2.8"
33
+ "version_name": "1.2.10"
34
34
  }
@@ -1,5 +1,7 @@
1
1
  # Architecture
2
2
 
3
+ For a compact component map, read [System overview](OVERVIEW.md). Security assumptions, attacker models, non-goals, and residual risks are defined in [Threat model](THREAT_MODEL.md).
4
+
3
5
  ## Design goal
4
6
 
5
7
  The system separates three questions that are often conflated:
@@ -32,15 +34,20 @@ A canonical workspace receives an independent profile, Worker name, secret set,
32
34
  - `runtime-reporting.mjs` builds privacy-aware runtime and project snapshots;
33
35
  - `runtime-diagnostics.mjs` owns fixed local probes and their stable interpretation;
34
36
  - `runtime-capabilities.mjs` composes agent, application, and browser capability results;
37
+ - `runtime-tool-handlers.mjs` owns catalog-to-handler registration;
38
+ - `runtime-relay.mjs` owns relay construction and inbound envelope normalization, while `relay-call-recovery.mjs` owns the bounded disconnect grace, result queue, authoritative resumed-call reconciliation, replay, and expiry cleanup;
39
+ - `runtime-paths.mjs` owns runtime-directory creation, containment checks, and error-path redaction;
40
+ - `managed-job-lock.mjs`, `managed-job-runner.mjs`, `managed-job-storage.mjs`, and `managed-job-projection.mjs` separate transition ownership, detached runner identity, private persistence/diagnostics, and public result shaping from the managed-job lifecycle;
41
+ - `browser-request-registry.mjs`, `browser-broker-routes.mjs`, `browser-broker-server.mjs`, and `browser-bridge-http.mjs` separate direct request ownership, runtime-client proxy routing, authenticated loopback WebSocket upgrades/listening, and loopback HTTP handling from broker startup and extension handover;
35
42
  - managed jobs, local resources, application automation, and browser automation remain separate managers.
36
43
 
37
- Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, or capability-scoring logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, `hello_ack` authentication, end-to-end `relay_probe`/`ready_ack` readiness, heartbeat liveness, reconnect backoff, outage logging, and a monotonically increasing in-memory session generation. Every relayed tool result is bound to the generation that delivered its call; a disconnected generation cannot send a late result over its replacement socket. Inbound WebSocket dispatch captures that generation at receive time and passes it to the runtime handler, so a successful local tool execution can publish only on the same generation. Stdio mode invokes `LocalRuntime` directly without that adapter.
44
+ Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, or capability-scoring logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, `hello_ack` authentication, end-to-end `relay_probe`/`ready_ack` readiness, heartbeat liveness, reconnect backoff, outage logging, and a monotonically increasing in-memory transport generation. The generation still protects the pre-ready probe and prevents arbitrary use of a stale socket. Ordinary tool calls additionally bind to an ephemeral identifier generated once per local daemon process. If a ready socket drops, the Worker detaches its pending calls for at most thirty seconds; only a replacement socket that presents the same daemon-process identifier and completes the full readiness probe can reclaim them. The local runtime keeps those calls alive and queues completed results during the same bounded interval, then replays them after readiness. A different process instance, an explicit cancellation, or grace expiry cannot receive those results. Stdio mode invokes `LocalRuntime` directly without that adapter.
38
45
 
39
46
  `daemon-process.mjs` owns workspace-daemon inspection and takeover. It distinguishes platform service state from the lock-owning Node process, validates PID and process-start identity, canonicalizes workspace/state paths before comparison, parses bounded process command lines without executing them, and accepts lock-backed `--daemon-only` recovery processes that omit repeated path flags. Stop/takeover sends `SIGTERM` only to a verified same-workspace service daemon. If it remains alive after the grace period, the code revalidates PID, process-start identity, command line, entrypoint, daemon mode, workspace, and state root before sending `SIGKILL`; a foreground, replaced-PID, or otherwise unverifiable process remains untouched. CLI orchestration never treats a missing launchd/systemd job as proof that the process exited.
40
47
 
41
48
  ### Agent context and capability resolver
42
49
 
43
- `AgentContextManager` discovers the nearest Git/workspace scope, applies the user configuration and hierarchical `.machine-bridge/agent.json` files, selects built-in/user/root-to-target instructions, discovers bounded filesystem skills, and resolves registered commands. `agent-contract.mjs` is the strict checked-JavaScript boundary for configuration shape, registered-command normalization, encoded-size limits, and configured-path containment; the manager does not maintain a second parser. `project-package.mjs` owns no-follow package metadata parsing, package-manager selection (including fail-closed conflicting-lockfile handling), script-name normalization, bounded workflow-intent aliases, and automatic `package.*` command construction so instruction rendering and command execution do not duplicate package parsing. `default-instructions.mjs` supplies a versioned in-package working-agreement block and derives a small virtual project-context block from root filenames and bounded metadata. It reads package script names but not bodies, does not inspect dependency values or source contents, executes nothing, and writes no user/repository files. A global `model_instructions_file` is a separate user-designated session source and cannot be overridden by a project.
50
+ `AgentContextManager` discovers the nearest Git/workspace scope, applies the user configuration and hierarchical `.machine-bridge/agent.json` files, selects built-in/user/root-to-target instructions, discovers bounded filesystem skills, and resolves registered commands. `agent-context-projection.mjs` owns capability fingerprints, privacy-aware public projections, bounded skill summaries, command rendering, and effective-instruction rendering. `agent-skill-discovery.mjs` owns bounded skill-root traversal, symlink containment, metadata parsing, warnings, and file inventory; `agent-text-file.mjs` owns no-follow bounded UTF-8 reads. Filesystem/config discovery no longer maintains those output and skill-scanning mechanics inline. `agent-contract.mjs` is the strict checked-JavaScript boundary for configuration shape, registered-command normalization, encoded-size limits, and configured-path containment; the manager does not maintain a second parser. `project-package.mjs` owns no-follow package metadata parsing, package-manager selection (including fail-closed conflicting-lockfile handling), script-name normalization, bounded workflow-intent aliases, and automatic `package.*` command construction so instruction rendering and command execution do not duplicate package parsing. `default-instructions.mjs` supplies a versioned in-package working-agreement block and derives a small virtual project-context block from root filenames and bounded metadata. It reads package script names but not bodies, does not inspect dependency values or source contents, executes nothing, and writes no user/repository files. A global `model_instructions_file` is a separate user-designated session source and cannot be overridden by a project.
44
51
 
45
52
  `session_bootstrap` is requested during both stdio and remote MCP initialization. The Worker delegates this read to the connected daemon with a short bounded timeout; failure falls back to static server instructions rather than blocking initialization indefinitely. `resolve_task_capabilities` performs a fresh deterministic scan, rebuilds automatic project facts, and ranks skill/command metadata for the current task. Application and browser capability metadata is added by `LocalRuntime`; installed application inventory uses a short bounded cache. `CapabilityObserver` records only counts, timestamps, source flags, selected metadata, match counts, recommended tool names, and a runtime-keyed task fingerprint so operators can verify routing without creating a task-content log.
46
53
 
@@ -103,7 +110,7 @@ All requests for a deployed Worker route to one named Durable Object. It owns:
103
110
  - policy/tool metadata attached to the active socket;
104
111
  - a bounded in-memory map of pending daemon calls.
105
112
 
106
- `BridgeRoom` owns Durable Object routing, MCP dispatch, daemon WebSocket lifecycle, and pending relay calls. `OAuthController` owns OAuth-store pruning, registration throttling, authorization pages/submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime for focused state-machine tests as well as bundled by Wrangler.
113
+ `BridgeRoom` owns Durable Object routing, MCP dispatch, daemon WebSocket lifecycle, and pending relay calls. `mcp-jsonrpc.ts` owns JSON-RPC shape validation, result/error framing, MCP tool-result projection, session-instruction bounds, and protocol-header validation. `websocket-protocol.ts` owns record validation plus best-effort send/close/rejection helpers. `OAuthController` owns OAuth-store pruning, registration throttling, authorization submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. `oauth-authorization-page.ts` owns escaped authorization-page rendering and redirect-origin CSP input. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime for focused state-machine tests as well as bundled by Wrangler.
107
114
 
108
115
  The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates cancellation by access-token hash and JSON-RPC ID, and formats text/structured/image results. Pending calls also bind the incoming request `AbortSignal`: an HTTP client disconnect removes the pending indexes and sends a best-effort daemon cancellation. The deployment contract explicitly enables Cloudflare `enable_request_signal` and `request_signal_passthrough` so the signal reaches the named Durable Object. It has no local filesystem or process API.
109
116
 
@@ -153,11 +160,12 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
153
160
  7. The MCP client initializes against the sole current protocol version; an obsolete client must upgrade rather than enter a legacy execution path. The Worker returns a stateless HMAC-bound `MCP-Session-Id`, and later request/cancellation correlation is scoped by OAuth token, MCP session, JSON-RPC id type, and id value. Two clients may therefore reuse the same JSON-RPC id concurrently without collision. Sessionless POSTs remain independent and are not inserted into a token-global cancellation index. When the daemon advertises `session_bootstrap`, the Worker requests bounded local instructions and appends them to the initialization result; failure degrades to static instructions.
154
161
  8. A new daemon first authenticates as a bounded `probing` socket. The Worker sends a random `relay_probe`; the local runtime returns it through the normal session-bound result-delivery path; only the matching result produces `ready_ack`, promotion to the active daemon, and safe replacement of an incumbent connection.
155
162
  9. `tools/list` is derived only from the active end-to-end-verified daemon; without one, only `server_info` is advertised.
156
- 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, authenticated client request key, incoming HTTP abort signal, and local relay-session generation.
163
+ 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, that daemon process's ephemeral instance identifier, the authenticated client request key, and the incoming HTTP abort signal.
157
164
  11. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
158
- 12. The Durable Object accepts a result only from the daemon socket that received that call, and the local runtime sends it only through the relay generation that delivered its call.
159
- 13. A matching cancellation notification or incoming HTTP client disconnect removes the pending indexes, tells the daemon to cancel, and terminates ordinary child processes bound to the call. If local completion races with cancellation, the eventual result is discarded rather than treated as a user-visible delivery failure.
160
- 14. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
165
+ 12. If the socket remains ready, the Durable Object accepts the result only from that socket. If it drops, the Worker detaches the pending call for at most thirty seconds and accepts completion only after a replacement socket with the same daemon-process identifier has passed the end-to-end readiness probe. The local runtime preserves the operation and queues a completion over the same interval.
166
+ 13. A matching cancellation notification or incoming HTTP client disconnect removes the pending indexes and sends best-effort cancellation to a connected daemon. Local completion that races with cancellation is discarded. On every readiness handover, the Worker first sends an authoritative bounded `resume_calls` set; the runtime cancels active calls and queued results absent from that set before accepting `ready_ack`. A request cancelled while disconnected therefore cannot be revived by a fast reconnect.
167
+ 14. If same-instance readiness does not return before the grace deadline, the Worker rejects the detached request and the local runtime cancels ordinary calls, terminates their process trees, and discards queued results. A newly started daemon has a different instance identifier and cannot inherit prior calls.
168
+ 15. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
161
169
 
162
170
  Duplicate in-flight JSON-RPC IDs are rejected only within the same authenticated MCP session. The request key includes OAuth token identity, the HMAC-bound MCP session, JSON-RPC id type, and id value, so separate initialized clients may safely reuse the same numeric id.
163
171
 
@@ -227,7 +235,9 @@ A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After op
227
235
 
228
236
  Reconnect uses bounded exponential backoff with jitter. Brief self-healing interruptions are debug-only. An unresolved outage is promoted to a rate-limited warning after a grace period, and recovery produces one summary. Raw close codes and reason strings remain debug-only.
229
237
 
230
- The Worker stores socket transitions in `DaemonSocketRegistry`: `candidate` before hello, `probing` after authentication, `daemon` only after the end-to-end result probe, and `expired` after terminal failure. Durable Object alarms enforce separate hello, readiness, and steady-state liveness deadlines across hibernation. A healthy incumbent remains active while a replacement is probed; a malformed, silent, or incompatible replacement is closed without displacing it. Only a verified candidate receives `ready_ack` and then replaces the old socket. Ready daemons stay live only while inbound traffic refreshes `lastSeenAt`; silent half-open or hibernation-restored sockets are reclaimed instead of advertising `daemon.connected` while tool calls time out. Pending calls retain their originating socket reference. A stale socket cannot complete or cancel replacement-socket calls. Closing a socket rejects only calls assigned to it. The local relay generation supplies the symmetric boundary: work accepted by an older authenticated connection cannot publish its result through a newer connection even when the call IDs are otherwise well formed.
238
+ The Worker stores socket transitions in `DaemonSocketRegistry`: `candidate` before hello, `probing` after authentication, `daemon` only after the end-to-end result probe, and `expired` after terminal failure. Durable Object alarms enforce separate hello, readiness, and steady-state liveness deadlines across hibernation. A healthy incumbent remains active while a replacement is probed; a malformed, silent, incompatible, or identity-mismatched replacement is closed without displacing it. Only a verified candidate receives `ready_ack` and then replaces the old socket. Ready daemons stay live only while inbound traffic refreshes `lastSeenAt`; silent half-open or hibernation-restored sockets are reclaimed instead of advertising `daemon.connected` while tool calls time out.
239
+
240
+ Each daemon process generates a random bounded `instance_id` at startup and includes it in every reconnect hello. Pending calls normally retain their assigned socket. On an unexpected socket loss, only those records are detached and a thirty-second timer bounds recovery. A verified socket with the same instance ID rebinds them; another process or socket cannot resolve them. The local runtime mirrors that state machine by preserving active calls and completed-result envelopes until relay readiness returns. Before `ready_ack`, the Worker sends the exact IDs that still have remote waiters; the runtime cancels everything else and only then replays retained results through the verified socket. Grace expiry restores the terminal behavior: reject remote waiters, cancel local ordinary calls, terminate process trees, and discard undeliverable results. This does not make calls durable across daemon restart or machine failure; managed jobs remain the separate durable mechanism.
231
241
 
232
242
  ## Persistence
233
243
 
@@ -255,7 +265,7 @@ Cloudflare sampling is size control rather than an audit log. The project intent
255
265
 
256
266
  ## Release integrity
257
267
 
258
- Repository-local automated checks are necessary but cannot prove that the maintainer's ordinary installation path works. `scripts/local-release-acceptance.mjs` builds the exact npm tarball, requires an explicit repository-owner confirmation after local testing, and records both npm hashes outside the package. `scripts/github-push.mjs`, pull-request CI, and `scripts/github-release.mjs` rebuild the package and reject any mismatch. The release helper also requires `HEAD === origin/main`; it cannot silently push `main`.
268
+ Repository-local automated checks are necessary but cannot prove that the maintainer's ordinary installation path works. `scripts/local-release-acceptance.mjs` builds the exact npm tarball, while `scripts/start-release-candidate.mjs` installs that tarball into an ignored isolated prefix and starts it in the foreground. The helper requires `--allow-worker-deploy` because normal startup may update the configured same-name Worker before relay verification. The repository owner starts the candidate; the coding agent verifies its live Worker version/hash, health, relay connection, local version, readiness, and representative behavior through Machine Bridge before recording both npm hashes outside the package. `scripts/github-push.mjs`, pull-request CI, and `scripts/github-release.mjs` rebuild the package and reject any mismatch. The release helper also requires `HEAD === origin/main`; it cannot silently push `main`.
259
269
 
260
270
  Cross-platform evidence remains independent. `scripts/github-release.mjs` queries CI, CodeQL, Governance, and Scorecard for the exact `origin/main` commit and requires the newest push-triggered run for each workflow to be completed with `success` before it creates or verifies a version tag, GitHub Release, or package asset. Pull-request runs, older successful runs, pending runs, and successful runs for another SHA do not satisfy the gate. The workflow selection policy is isolated in `scripts/release-ci.mjs` and tested independently.
261
271
 
package/docs/AUDIT.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Security and privacy audit notes
2
2
 
3
+ ## 2026-07-20 version 1.2.10 transient-relay recovery audit
4
+
5
+ Repeated ChatGPT web executions were terminating even though the local daemon and Worker were healthy immediately afterward. The causal defect was not the tool implementation or its configured timeout. A brief WebSocket loss—made more likely when the daemon relay traverses an environment-selected local proxy—was promoted into a terminal request failure at both ends: the Worker rejected every pending call assigned to the closed socket, while the local runtime cancelled all relay-origin calls and killed their ordinary child processes. Automatic WebSocket reconnection restored future traffic but could not recover the request already abandoned by both state machines.
6
+
7
+ The correction introduces a bounded continuation identity rather than weakening cancellation globally. Every local daemon process creates one random ephemeral `instance_id` and presents it in the current-version hello. On socket loss, the Worker detaches only calls owned by that socket and retains their OAuth/session request indexes for thirty seconds. A replacement socket may rebind them only after presenting the same process identifier and completing the existing end-to-end readiness probe. The local runtime mirrors this interval: active calls continue, completed result envelopes are retained in memory, and readiness triggers replay. A different process instance cannot claim the pending IDs; daemon restart remains terminal rather than pretending to provide durable execution.
8
+
9
+ The recovery path remains bounded and fail closed. Explicit MCP cancellation and HTTP client abandonment still remove the remote receiver immediately. A connected daemon receives cancellation as before. If cancellation occurs while disconnected, the next same-instance handshake carries an authoritative `resume_calls` set before `ready_ack`; the local runtime cancels any active or completed call absent from that set, so a fast reconnect cannot revive a request whose HTTP receiver disappeared. Timeout remains authoritative. At thirty seconds without same-instance readiness, the Worker rejects detached requests and the runtime cancels calls, discards queued results, and enters the existing persistent-outage reporting path. No tool arguments, commands, paths, outputs, proxy endpoint, or result content are added to logs or durable storage.
10
+
11
+ Regression evidence covers three layers: deterministic pending-registry detach/rebind/expiry tests; local runtime queue/replay, resumed-call reconciliation, cancellation, and grace behavior; and real workerd OAuth/MCP/WebSocket fault injection that both completes a detached request after same-instance reconnect and proves a request cancelled while detached is excluded from the next `resume_calls` set. Malformed or missing daemon-instance identifiers are rejected as protocol errors. The version remains a current-only Worker/daemon protocol unit and requires normal candidate convergence before live use.
12
+
13
+ ## 2026-07-18 version 1.2.9 interactive release-handoff correction
14
+
15
+ The release-integrity mechanism correctly bound acceptance to exact package bytes, but the operational role split was wrong. Documentation and repository guards required the owner to run the acceptance command personally and prohibited the coding agent from recording it. That contradicted the established working process: the owner should perform the one action the agent cannot perform honestly—start the exact candidate on the maintainer machine—while the agent should connect to that candidate, verify its version, readiness, and representative functionality, then record acceptance and complete source release operations.
16
+
17
+ Version 1.2.9 codifies that flow. `npm run release:candidate:start -- --allow-worker-deploy` verifies and installs the pending tarball into an ignored isolated prefix and launches it in the foreground without replacing the normal global installation. The explicit flag acknowledges that normal candidate startup may update the configured same-name Worker in place. The owner runs that command and leaves the process active. The coding agent must observe the live candidate through Machine Bridge; automated checks or an unobserved process are insufficient. After successful observation, the agent records the new `owner-started-agent-verified-local-candidate` marker, commits, pushes through the guarded command, completes the pull request, and runs `npm run release` to create or verify the annotated tag and GitHub Release. The old 1.2.8 owner-recorded marker remains accepted only for historical compatibility.
18
+
19
+ npm publication and Worker deployment remain owner-operated live release actions. The coding agent does not perform either as part of `npm run release`. Any packaged-byte change after candidate preparation invalidates the pending or recorded acceptance and requires another owner-started, agent-observed candidate run.
20
+
21
+ The first PR run exposed two release-infrastructure defects that local macOS validation could not certify. The layered runner spawned `npm.cmd` directly and failed with `EINVAL` under Node 26 on Windows; it now executes the integrity-pinned npm CLI through the current Node binary on every platform. The acceptance recorder also omitted the portable Git-content digest required by CI. It now constructs a temporary Git index from `HEAD` plus the complete working tree, computes the same canonical package digest used by CI, deletes the temporary index without changing the maintainer staging area, and makes local acceptance verification reject a missing digest. Both failures invalidate the earlier candidate and require a regenerated, re-observed candidate before release.
22
+
3
23
  ## 2026-07-17 version 1.2.8 release-integrity and dependency-automation audit
4
24
 
5
25
  The core failure was procedural rather than a missing unit test: the repository treated a green automated suite as sufficient evidence for source publication, and `release:publish` could push a locally tested commit directly to `main`. That model cannot satisfy the reported history of formally released builds failing on the maintainer's real installation. It also assigned the coding agent authority to infer release acceptance, even though only the repository owner can observe the ordinary local environment and decide whether the product is usable.
@@ -9,7 +9,7 @@ This document records project-wide decisions that must survive individual fixes,
9
9
  3. **Machine Bridge authority and host authority are separate.** `full` removes Machine Bridge's own policy, path, shell, and environment restrictions. It cannot override an MCP host, connector gateway, operating system, endpoint-security product, cloud IAM, remote authentication, or `sudo`.
10
10
  4. **Publication surfaces contain no real environment metadata.** Source, tests, fixtures, examples, release notes, filenames, package contents, tags, and release assets use synthetic identifiers and reserved example domains.
11
11
  5. **Secrets are never operational log data.** Tool arguments, command text, stdin, stdout, stderr, file content, OAuth bodies, credentials, and local resource values are not logged.
12
- 6. **A release is one owner-tested package with successful cross-platform evidence.** The repository owner must test the exact npm tarball and record its hashes before the first GitHub push of an npm-package change. Package metadata, Worker version, browser-extension version/name, acceptance record, Git tag, GitHub Release, npm version, documentation, and deployed health version must agree, and the exact `origin/main` commit must have completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs before a tag or release is created.
12
+ 6. **A release is one owner-started, agent-verified package with successful cross-platform evidence.** The repository owner starts the exact npm candidate on the maintainer machine; the coding agent verifies the connected candidate version, readiness, and representative functionality through Machine Bridge and then records the package hashes before the first GitHub push. Package metadata, Worker version, browser-extension version/name, acceptance record, Git tag, GitHub Release, npm version, documentation, and deployed health version must agree, and the exact `origin/main` commit must have completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs before a tag or release is created.
13
13
  7. **Generic local automation is structured, not arbitrary evaluation.** Browser/application features may expose broad user authority under canonical `full`, but must not accept caller-provided JavaScript, AppleScript, JXA, or extension code.
14
14
  8. **Daily-browser integration uses the existing profile.** The supported primary browser path is the packaged authenticated extension and machine-level loopback broker, preserving current tabs/login state; a separate automation profile is not an equivalent replacement.
15
15
  9. **Pairing and resource secrets are not conversation or log data.** Tokens and injected local-resource values must not be returned, embedded in URLs, or written to operational logs.
@@ -24,7 +24,7 @@ A proposed change that conflicts with an invariant requires an explicit owner de
24
24
 
25
25
  ## Change and release-operation ownership
26
26
 
27
- Repository implementation, owner acceptance, source release completion, and live release operations are separate responsibilities. Under `AGENTS.md`, coding automation may edit, test, and commit locally, then generate an exact candidate tarball. It must stop before the first GitHub push until the repository owner personally tests that artifact and records `release-acceptance/v<version>.json`. The agent may then push only through `npm run github:push`, complete the pull request through local `git`/`gh`, and create the annotated version tag plus final GitHub Release only after the accepted package hash and exact `main` checks pass. `release:publish` never pushes `main`. Automation must not assert owner acceptance, publish or alter npm packages, install globally, deploy or reconfigure a Worker, rotate credentials, mutate live deployment state, or replace daemon/service state without explicit user authorization.
27
+ Repository implementation, interactive candidate acceptance, source release completion, and live release operations are separate responsibilities. Under `AGENTS.md`, coding automation may edit, test, and commit locally, generate an exact candidate tarball, and give the owner `npm run release:candidate:start -- --allow-worker-deploy`. The owner explicitly authorizes the in-place update of the configured same-name Worker, starts that candidate, and leaves it running. The agent verifies the deployed Worker version/hash, remote health, relay readiness, and connected local candidate through Machine Bridge and may then record `release-acceptance/v<version>.json`, push only through `npm run github:push`, complete the pull request through local `git`/`gh`, and create the annotated version tag plus final GitHub Release with `npm run release` after the accepted package hash and exact `main` checks pass. Automated checks without an observed live candidate do not authorize acceptance. `npm run release` never pushes `main`. Automation must not publish or alter npm packages, install globally outside the isolated candidate prefix, deploy or reconfigure a Worker as a release operation, rotate credentials, mutate live deployment state, or replace daemon/service state without explicit user authorization.
28
28
 
29
29
  The normal handoff is: the repository owner publishes the reviewed npm version, then runs `npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest && machine-mcp`. The npm command updates the global CLI but cannot hot-reload an existing Node process. The subsequent normal foreground startup validates the Worker deployment hash, expected version, and health, requests shutdown of an active autostart daemon, waits a bounded interval for its lock, redeploys when necessary, and then takes over with the installed version. Live operations require explicit authorization even when they appear to be the obvious next release step.
30
30
 
package/docs/LOGGING.md CHANGED
@@ -79,7 +79,7 @@ With `--verbose`, the same incident additionally includes bounded structured fie
79
79
 
80
80
  All per-tool starts, successes, failures, cancellations, timing, and expected late-result disposal are debug-only. The MCP response already reports the outcome to the caller; duplicating routine tool traffic at default levels creates noise and can reveal activity patterns.
81
81
 
82
- A completed local result is bound to the authenticated relay session that delivered its call. If the caller cancelled, the relay disconnected, or a replacement relay session became active, the result is discarded rather than sent to a different session. This is a normal terminal race and does not emit a warning. Debug output records only a shortened call ID and a coarse reason such as `caller_cancelled`, `relay_disconnected`, or `session_ended`. A synchronous send failure on the still-current socket is different: the socket is invalidated because delivery is ambiguous, but the per-call detail remains debug-only while the normal relay-outage state machine decides whether a user-visible warning is warranted.
82
+ A completed local result is normally sent on the ready relay connection. If that socket disappears, the runtime queues the bounded result envelope during the thirty-second same-daemon reconnect window rather than logging a terminal delivery failure. Debug output records only a shortened call ID and queue/reconnect counts. After the same daemon process completes readiness, replay emits one recovery event; a different process cannot inherit the result. Explicit caller cancellation suppresses eventual delivery. If the relay does not recover before the grace deadline, ordinary calls are cancelled, queued results are discarded, and the existing outage state machine determines whether the persistent failure warrants a warning. Tool arguments, commands, and result content are never logged.
83
83
 
84
84
  Debug per-tool fields may include tool name, duration, coarse outcome class, and a shortened random call identifier. The identifier is for correlating adjacent local events and is not a stable audit identifier.
85
85
 
@@ -93,9 +93,9 @@ The first start of a new deployment creates an `owner` account automatically and
93
93
 
94
94
  ## Concurrency and revocation
95
95
 
96
- Pending calls remain bound to the OAuth access token and JSON-RPC request ID. Duplicate in-flight IDs under one token are rejected. Account revocation blocks new requests immediately; calls already relayed remain subject to ordinary cancellation, deadlines, relay disconnect cleanup, and local role validation.
96
+ Pending calls remain bound to the OAuth access token and JSON-RPC request ID. Duplicate in-flight IDs under one token are rejected. Account revocation blocks new requests immediately; calls already relayed remain subject to ordinary cancellation, deadlines, local role validation, and the bounded same-daemon reconnect state machine. A replacement process cannot inherit a detached call.
97
97
 
98
- Remote relay loss cancels every relay-owned local call and terminates its child process tree. Process promises settle on cancellation even when a child does not emit `close`; process ownership remains tracked until actual exit.
98
+ A relay interruption keeps ordinary relay-owned calls alive only inside the bounded same-daemon reconnect window. Reconciliation or grace expiry cancels calls that no longer have a remote receiver and terminates their child process trees. Process promises settle on cancellation even when a child does not emit `close`; process ownership remains tracked until actual exit.
99
99
 
100
100
  ## Audit and privacy
101
101
 
@@ -51,7 +51,7 @@ A successful diagnostic result applies only to that probe. An MCP host can still
51
51
 
52
52
  Machine Bridge supports concurrent calls: the Worker admits up to 32 pending daemon calls and the local runtime admits up to 16 active tool calls. These are capacity limits, not a single global execution queue. Each successful MCP initialization receives a signed `MCP-Session-Id`; JSON-RPC ids and cancellation are scoped to that session, so separate chat windows may reuse the same numeric ids safely even when they share one OAuth account and token.
53
53
 
54
- `server_info.worker.pending_calls` reports `active`, `request_keys`, `maximum`, `oldest_ms`, and `by_tool`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and tool advertisement. A nonzero `active` count means work is in flight, not that the bridge is locked. Calls for simple reads and probes should continue while another independent process call runs. Explicit MCP cancellation, an incoming HTTP client disconnect, timeout, and daemon-socket closure remove the pending record and its session request key. Local results are bound to the authenticated relay session that originated the call, so a result that finishes after cancellation or reconnection is discarded instead of being sent over a replacement socket. Refreshing a chat page is not the recovery mechanism and should not be required.
54
+ `server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, and `by_tool`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and tool advertisement. A nonzero `active` count means work is in flight, not that the bridge is locked. `detached > 0` means a daemon socket was lost and those requests are inside the bounded thirty-second same-instance reconnect window. Calls for simple reads and probes should continue while another independent process call runs. Explicit MCP cancellation, an incoming HTTP client disconnect, and timeout remove the pending record and its session request key. A daemon-socket closure detaches only calls assigned to that socket; the same daemon process can reclaim them after completing readiness, while another process cannot. Grace expiry rejects the request and cancels the local ordinary operation. Refreshing a chat page is not the recovery mechanism and should not be required.
55
55
 
56
56
  `server_info.worker.observability.calls.unmatched_results` counts results that reached the Worker after their pending record was already removed. A small increase can accompany cancellation or timeout races, especially during mixed-version upgrade convergence; sustained growth together with old pending calls indicates incompatible components or a lifecycle defect. The counter contains no tool arguments or result data.
57
57
 
@@ -147,7 +147,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
147
147
 
148
148
  ### Lifecycle and pending-call diagnosis
149
149
 
150
- `server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports both the internal-call index and client request-key index; they must return to zero after a terminal result, explicit cancellation, client disconnect, timeout, send failure, or daemon disconnect. Nonzero request-key counts after active calls reach zero indicate a lifecycle defect rather than normal load. `worker.observability.calls.unmatched_results` is the bounded counter for late results that no longer have a receiver.
150
+ `server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports the internal-call index, client request-key index, and detached-call count. All three must return to zero after a terminal result, explicit cancellation, client disconnect, timeout, or reconnect-grace expiry. During a brief daemon interruption, `active` and `request_keys` may remain nonzero while `detached` identifies the recoverable subset; after same-instance readiness, `detached` returns to zero without losing those requests. Nonzero request-key counts after active calls reach zero indicate a lifecycle defect rather than normal load. `worker.observability.calls.unmatched_results` is the bounded counter for late results that no longer have a receiver.
151
151
 
152
152
  Stable errors include `policy_denied`, `invalid_request`, `timeout`, `cancelled`, `network_error`, `unavailable`, `limit_exceeded`, and `integrity_error`, with retryability metadata. Diagnose by code first; free-form messages are guidance, not an API contract.
153
153
 
@@ -0,0 +1,113 @@
1
+ # System overview
2
+
3
+ Machine Bridge is one local authority surface with two MCP transports. The architecture is organized around three independent questions:
4
+
5
+ 1. **Who may request an operation?** Remote OAuth account identity and role, or the local process identity that launched stdio.
6
+ 2. **Which tools are exposed?** The shared tool catalog, policy profile, and account-role intersection.
7
+ 3. **What can an exposed tool actually do?** The local runtime, workspace/path rules, operating-system user authority, and platform security controls.
8
+
9
+ Transport authentication does not create an operating-system sandbox. The local daemon executes with the authority of its OS user.
10
+
11
+ ## Components
12
+
13
+ ```mermaid
14
+ flowchart LR
15
+ HC[Hosted MCP client] -->|HTTPS + OAuth 2.1 / PKCE| W[Cloudflare Worker]
16
+ W --> DO[Durable Object room]
17
+ DO -->|Authenticated outbound WebSocket| R[LocalRuntime]
18
+
19
+ LC[Local MCP client] -->|stdio| S[stdio adapter]
20
+ S --> R
21
+
22
+ R --> PG[PolicyGate + account access]
23
+ PG --> TE[ToolExecutor]
24
+ TE --> FS[Workspace file service]
25
+ TE --> PS[Process services]
26
+ TE --> MJ[Managed jobs]
27
+ TE --> AC[Agent context]
28
+ TE --> BA[Browser/app automation]
29
+
30
+ BA --> BB[Loopback browser broker]
31
+ BB --> EX[Packaged Chromium extension]
32
+
33
+ R --> OS[Local OS user authority]
34
+ MJ --> ST[Owner-only local state]
35
+ ```
36
+
37
+ ## Authority flow
38
+
39
+ ### Remote transport
40
+
41
+ 1. The Worker validates OAuth client, token, account state, role, resource binding, and MCP session state.
42
+ 2. The Worker filters advertised tools by account role and the daemon-reported capability ceiling.
43
+ 3. The Durable Object relays a bounded tool envelope over the authenticated daemon socket.
44
+ 4. The local runtime revalidates account authorization, policy, call lifecycle, timeout, and cancellation.
45
+ 5. The selected local service executes with the daemon OS user's authority.
46
+
47
+ The Worker never receives ambient filesystem or process authority. It receives only explicit request/result messages.
48
+
49
+ ### Local stdio transport
50
+
51
+ The MCP host launches the package as a subprocess. There is no remote OAuth layer. The selected local policy still controls catalog exposure and execution, while the OS process identity supplies the underlying filesystem and process authority.
52
+
53
+ ## Shared contracts
54
+
55
+ The main protocol and policy single sources of truth are:
56
+
57
+ - `src/shared/tool-catalog.json` — tool names, schemas, policy requirements, and annotations;
58
+ - `src/shared/policy-contract.json` — canonical profiles and capability composition;
59
+ - `src/shared/server-metadata.json` — server identity and protocol metadata.
60
+
61
+ Local and Worker code consume these contracts. Generated references and drift tests prevent documentation and implementation from silently diverging.
62
+
63
+ ## Local runtime boundaries
64
+
65
+ `LocalRuntime` is an orchestrator, not a low-level implementation module. It composes:
66
+
67
+ - policy and account authorization;
68
+ - call registration, timeout, cancellation, and observability;
69
+ - workspace and path services;
70
+ - direct and shell process execution;
71
+ - Git operations;
72
+ - managed jobs and local resources;
73
+ - Agent context and capability resolution;
74
+ - application and browser automation;
75
+ - remote relay adaptation.
76
+
77
+ Low-level responsibilities remain in focused modules. Architecture tests enforce an acyclic local import graph, domain/adapter direction, and module-size budgets. Behavior and fault-injection tests remain authoritative; source-shape checks are supplementary.
78
+
79
+ ## State and lifecycle
80
+
81
+ Each canonical workspace has independent profile state, Worker identity, credentials, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
82
+
83
+ The relay distinguishes connectivity, authentication, readiness probing, and active service. A candidate daemon must complete an end-to-end probe before replacing an incumbent. Disconnect cancels relay-owned calls and terminates associated local processes.
84
+
85
+ Managed jobs use a separate durable lifecycle. Their plans are integrity-bound, runners are process-identity checked, transitions are lock-protected, terminal plans are scrubbed, and recovery is bounded.
86
+
87
+ ## Browser execution model
88
+
89
+ The local browser broker binds only to loopback and authenticates the packaged extension with local pairing state and a versioned capability handshake. Additional runtime processes may proxy through the machine-level broker rather than opening competing extension connections.
90
+
91
+ The extension controls the Chromium profile into which it is loaded. Machine Bridge does not launch, isolate, or prove the identity of that profile. Browser cookies, sessions, tabs, and page content therefore remain part of the local user's browser authority.
92
+
93
+ ## Verification layers
94
+
95
+ The repository uses several complementary layers:
96
+
97
+ - unit and behavior tests for policy, parsing, state, lifecycle, and services;
98
+ - fault injection and property tests for security-sensitive boundaries;
99
+ - live stdio and Worker/OAuth/MCP integration tests;
100
+ - cross-platform service and installation tests;
101
+ - per-module critical coverage thresholds;
102
+ - architecture/import/line-budget checks;
103
+ - privacy, package, release-impact, interactive-candidate-acceptance, CodeQL, dependency, and Scorecard gates.
104
+
105
+ `npm run check:fast` is the development feedback loop. `npm run check` runs the complete release-relevant suite.
106
+
107
+ ## Read next
108
+
109
+ - [Architecture](ARCHITECTURE.md) for detailed invariants and protocol flow
110
+ - [Threat model](THREAT_MODEL.md) for assets, attackers, non-goals, and residual risks
111
+ - [Security policy](../SECURITY.md) for reporting and supported security boundaries
112
+ - [Engineering](ENGINEERING.md) for implementation rules
113
+ - [Testing](TESTING.md) for verification design
@@ -20,13 +20,13 @@ Direct pushes to `main`, force pushes, and branch deletion are blocked by reposi
20
20
 
21
21
  ### Completion ownership and local acceptance
22
22
 
23
- Repository automation owns implementation, local validation, and pull-request completion, but it does not own the maintainer's release acceptance decision. For every npm-package change, automation must generate the exact candidate tarball with `npm run release:candidate` and stop before the first GitHub push. The repository owner must install or otherwise exercise that exact tarball on the maintainer machine through the normal user path and record a passing decision with `npm run release:accept -- --confirm "I TESTED machine-bridge-mcp <version> LOCALLY AND IT WORKS"`. Automation must not create that assertion on the owner's behalf.
23
+ Repository automation owns implementation, local validation, observed candidate verification, acceptance recording, and pull-request completion. For every npm-package change, automation generates the exact candidate tarball with `npm run release:candidate` and stops before the first GitHub push. The repository owner explicitly authorizes the live candidate Worker update and runs `npm run release:candidate:start -- --allow-worker-deploy` on the maintainer machine and leaves the candidate running. The coding agent verifies the deployed Worker version/hash, remote health, relay readiness, connected local version, and representative functionality through Machine Bridge, then records a passing decision with `npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp <version> CANDIDATE ON THE OWNER MACHINE AND IT WORKS"`. Automated checks or an unobserved process are not sufficient evidence.
24
24
 
25
- The tracked `release-acceptance/v<version>.json` record binds the decision to the npm tarball SHA-1 and SHA-512 integrity value. The record is excluded from the package, so a content-preserving squash merge does not invalidate it; any source, executable script, package metadata, or packaged-documentation change produces a different package hash and requires a new local test. `npm run github:push`, pull-request CI, and `release:publish` verify the record. Raw direct pushes of release-relevant branches are prohibited.
25
+ The tracked `release-acceptance/v<version>.json` record binds the observed result to the npm tarball SHA-1 and SHA-512 integrity value. The record is excluded from the package, so a content-preserving squash merge does not invalidate it; any source, executable script, package metadata, or packaged-documentation change produces a different package hash and requires a regenerated candidate and another observed live verification. `npm run github:push`, pull-request CI, and `npm run release` verify the record. Raw direct pushes of release-relevant branches are prohibited.
26
26
 
27
- After owner acceptance, repository automation may push only through `npm run github:push`, open and complete the pull request, verify the resulting `main` commit, and remove the merged branch. Every npm-package change advances the package version and is completed by an annotated `v<version>` tag plus a final GitHub Release for the exact successful `main` commit. GitHub-only repository infrastructure changes that do not alter npm package contents do not require a synthetic package version or local runtime acceptance, but they still require review and their applicable CI/security checks.
27
+ After interactive acceptance, repository automation may push only through `npm run github:push`, open and complete the pull request, verify the resulting `main` commit, remove the merged branch, and run `npm run release`. Every npm-package change advances the package version and is completed by an annotated `v<version>` tag plus a final GitHub Release for the exact successful `main` commit. GitHub-only repository infrastructure changes that do not alter npm package contents do not require a synthetic npm version or local runtime acceptance, but they still require review and their applicable CI/security checks.
28
28
 
29
- The coding agent may perform the tag and GitHub Release work only after the recorded owner acceptance and exact-commit checks succeed. npm publication, Worker deployment, credential mutation, global installation, and daemon/service replacement remain separate live operations requiring explicit authorization.
29
+ The coding agent performs the acceptance-record, tag, and GitHub Release work only after observed live verification and exact-commit checks succeed. npm publication, Worker deployment, credential mutation, non-isolated global installation, and daemon/service replacement remain separate live operations performed by the repository owner.
30
30
 
31
31
  ### Local GitHub control plane
32
32
 
package/docs/RELEASING.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The release invariant is:
4
4
 
5
- - the repository owner tested the exact npm tarball represented by `release-acceptance/v<version>.json` on the maintainer machine and explicitly recorded a passing result;
5
+ - the repository owner started the exact npm tarball represented by `release-acceptance/v<version>.json` on the maintainer machine, and the coding agent observed the live candidate through Machine Bridge before recording a passing result;
6
6
  - the current package produces the same SHA-1 and SHA-512 integrity values as that accepted tarball;
7
7
  - `main` points to the release commit, and that exact commit has completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs;
8
8
  - `v<package version>` points to that same commit locally and on GitHub;
@@ -11,7 +11,7 @@ The release invariant is:
11
11
  - every npm-package change since the prior version tag has a higher package version and matching CHANGELOG section;
12
12
  - the same accepted package content is present on GitHub and in a new npm version.
13
13
 
14
- A green automated suite is necessary but is not evidence that the maintainer's ordinary installation path works. The repository therefore separates automated validation from owner acceptance and binds both to the final package bytes.
14
+ A green automated suite is necessary but is not evidence that the maintainer's ordinary installation path works. The repository therefore separates automated validation from observed live candidate verification and binds both to the final package bytes.
15
15
 
16
16
  ## Change classification
17
17
 
@@ -38,19 +38,32 @@ A repository-only change under paths such as `.github/` may be merged without a
38
38
 
39
39
  `release:candidate` runs the complete repository suite and then writes the exact npm tarball plus a bounded pending manifest under ignored `.release-candidate/`. Do not modify a packaged file after generating the candidate without regenerating and retesting it.
40
40
 
41
- ## Repository-owner local acceptance
41
+ ## Interactive local candidate acceptance
42
42
 
43
- The repository owner—not the coding agent—must test the exact `.release-candidate/*.tgz` artifact on the maintainer machine through the normal installation and startup path relevant to the change. For a general release, this includes installation from that tarball, zero-argument startup, ordinary connection/readiness, and at least one representative user operation. Changes to browser, application automation, service startup, proxying, credentials, or platform-specific behavior require the corresponding live or platform-specific test described in `docs/TESTING.md` and `docs/OPERATIONS.md`.
43
+ The repository owner starts the exact `.release-candidate/*.tgz` artifact on the maintainer machine with:
44
44
 
45
- After the owner confirms the candidate works, record the decision using the exact phrase printed by `release:candidate`, for example:
45
+ ```sh
46
+ npm run release:candidate:start -- --allow-worker-deploy
47
+ ```
48
+
49
+ This command verifies the pending tarball hashes, installs it into the ignored `.release-candidate/runtime/` prefix without replacing the normal global installation, explicitly authorizes startup to update the configured same-name Worker when its version or deployment hash differs, and starts the installed candidate in the foreground. This is an in-place live candidate deployment, not an isolated staging Worker. The owner leaves that process running. The coding agent then connects through Machine Bridge and verifies at minimum:
50
+
51
+ - the remote Worker reports the candidate version and the persisted deployment hash matches the candidate Worker bundle;
52
+ - the remote health route succeeds without redirect;
53
+ - the connected runtime reports the candidate package version;
54
+ - relay readiness is healthy through the deployed Worker for the intended transport;
55
+ - one representative read-only operation succeeds;
56
+ - representative changed functionality succeeds, including browser, application, service, proxy, credential, or platform-specific behavior when relevant.
57
+
58
+ The owner does not run the acceptance command. After the coding agent has observed the live candidate passing those checks, the agent records the decision using the exact phrase printed by `release:candidate`, for example:
46
59
 
47
60
  ```sh
48
- npm run release:accept -- --confirm "I TESTED machine-bridge-mcp 1.2.8 LOCALLY AND IT WORKS"
61
+ npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp 1.2.9 CANDIDATE ON THE OWNER MACHINE AND IT WORKS"
49
62
  ```
50
63
 
51
- This creates `release-acceptance/v<version>.json` containing only package identity, hashes, timestamp, result, and a fixed owner-confirmation marker. It does not store a personal name, machine path, command output, credential, or user content. The acceptance record is intentionally excluded from the npm package, so adding it does not change the tested tarball.
64
+ This creates `release-acceptance/v<version>.json` containing package identity, npm tarball hashes, a portable Git-content digest, timestamp, result, and a fixed observed-verification marker. The portable digest is computed with a temporary Git index containing `HEAD` plus the complete working tree; the real staging area is not modified. The record does not store a personal name, machine path, command output, credential, or user content. It is intentionally excluded from the npm package, so adding it does not change the tested tarball.
52
65
 
53
- The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. The assertion is a process gate backed by branch ownership and review, not a cryptographic identity signature; it must never be generated by automation merely because automated tests passed.
66
+ The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. Automated tests alone, a prepared tarball, a process the agent did not observe, or an ambiguous owner response do not authorize acceptance. Version 1.2.8 owner-recorded acceptance remains supported as a historical marker; version 1.2.9 and later require the owner-started, agent-observed workflow.
54
67
 
55
68
  ## Push and review
56
69
 
@@ -64,15 +77,15 @@ The command requires a clean non-`main` branch, verifies that the acceptance rec
64
77
 
65
78
  Open a pull request, satisfy the required checks, and squash-merge. Pull-request CI repeats the acceptance verification. A content-preserving squash changes the Git commit but not the package bytes, so the accepted hash remains valid.
66
79
 
67
- ## Publish the GitHub source release
80
+ ## Complete the GitHub source release
68
81
 
69
82
  From a clean, fast-forwarded `main` worktree whose `HEAD` exactly equals `origin/main`:
70
83
 
71
84
  ```sh
72
- npm run release:publish
85
+ npm run release
73
86
  ```
74
87
 
75
- `release:publish` never pushes `main`. It verifies the owner acceptance, runs the complete project and version checks, requires successful exact-commit push runs for CI, CodeQL, Governance, and Scorecard, creates or verifies the annotated version tag, pushes only the version tag when absent, builds the npm tarball, creates or updates the final GitHub Release, uploads the tarball, and verifies the resulting state.
88
+ `npm run release` never pushes `main`. `release:publish` remains a compatibility alias. It verifies the interactive candidate acceptance, runs the complete project and version checks, requires successful exact-commit push runs for CI, CodeQL, Governance, and Scorecard, creates or verifies the annotated version tag, pushes only the version tag when absent, builds the npm tarball, creates or updates the final GitHub Release, uploads the tarball, and verifies the resulting state.
76
89
 
77
90
  To verify an already completed source release without changing anything:
78
91
 
@@ -106,6 +119,6 @@ Required local access:
106
119
  - an authenticated GitHub CLI session with pull-request and release permission;
107
120
  - an npm account that owns the package or has maintainer permission for the separate registry publication step.
108
121
 
109
- GitHub Actions remains a required cross-platform boundary. Missing, pending, cancelled, skipped, failed, pull-request-only, stale, or wrong-commit runs do not satisfy release publication. Local owner acceptance and successful GitHub checks are complementary evidence; neither substitutes for the other.
122
+ GitHub Actions remains a required cross-platform boundary. Missing, pending, cancelled, skipped, failed, pull-request-only, stale, or wrong-commit runs do not satisfy release publication. Observed local candidate acceptance and successful GitHub checks are complementary evidence; neither substitutes for the other.
110
123
 
111
124
  The preferred registry target remains npm trusted publishing from a narrowly scoped GitHub Actions workflow using OIDC and a protected GitHub environment. Enabling it requires package-owner configuration in npm and a reviewed workflow change. Until then, never place an npm token in repository files, workflow YAML, logs, local acceptance records, or project notes.
package/docs/TESTING.md CHANGED
@@ -2,18 +2,22 @@
2
2
 
3
3
  The project treats transport, authorization, local authority, and state removal as separate failure domains.
4
4
 
5
- ## Required suite
5
+ ## Required suites
6
6
 
7
7
  ```sh
8
- npm run check
8
+ npm run check:fast # static, unit, contract, and architecture feedback
9
+ npm run check:platform # fast plan plus cross-platform behavior and real-machine tests
10
+ npm run check # complete suite; equivalent to check:full
9
11
  ```
10
12
 
13
+ The explicit task lists live in `scripts/check-plan.mjs`. The fast plan retains static analysis, policy, architecture, contracts, and focused unit behavior. The platform plan adds process, service, browser/application, state, managed-job, cryptographic, and real-machine behavior tests needed on macOS and Windows. The full-only plan adds coverage, browser-broker integration, package/install, stdio, Worker/OAuth, and real-browser navigation suites. `npm run check` remains the authoritative complete gate.
14
+
11
15
  The repository requires Node.js 26 and npm 12. `.node-version`, `.nvmrc`, `packageManager`, `devEngines`, and strict engine checks keep local and CI execution on the same baseline.
12
16
 
13
17
  The suite includes:
14
18
 
15
19
  - package-impact classification derived from the npm package manifest, allowing repository-only GitHub workflow maintenance without weakening version requirements for shipped content;
16
- - repository-owner local acceptance hashing, including exact tarball SHA-1/SHA-512 matching, acceptance-record exclusion from the package, and invalidation after any packaged-byte change;
20
+ - interactive candidate acceptance hashing, including owner-authorized in-place Worker deployment plus owner-started candidate verification, exact tarball SHA-1/SHA-512 matching, acceptance-record exclusion from the package, legacy 1.2.8 marker compatibility, and invalidation after any packaged-byte change;
17
21
  - release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
18
22
  - release-state diagnostics distinguishing missing local/remote version tags from tags that point to the wrong commit, plus a release-CI gate that rejects missing, pending, failed, pull-request-only, stale, or wrong-commit runs;
19
23
  - generated Cloudflare Worker types under ignored `.wrangler/` state and strict TypeScript checking, including unused-local and unused-parameter rejection; packaging rejects generated declarations;
@@ -81,11 +85,11 @@ For deterministic release validation, perform an isolated-profile smoke test wit
81
85
 
82
86
  ## Critical-module coverage gate
83
87
 
84
- `npm run coverage:test` runs selected in-process and lightweight entrypoint fixtures under V8 coverage and enforces per-module function and branch baselines. The measured set now includes policy, typed errors, call registration, execution middleware, lifecycle/observability, logging, Runtime/CLI orchestration, the independently injected service CLI adapter, Agent configuration, capability ranking, browser protocol/operation boundaries, runtime reporting/diagnostics/capability composition, and Worker pending/policy/error modules. The full stdio and workerd OAuth/MCP integration still runs separately; a focused direct Node test covers `OAuthController` registration throttling, authorization failure/success, resource-bound access tokens, expiry pruning, invalid-state rejection, and identity-key failure.
88
+ `npm run coverage:test` runs selected in-process and lightweight entrypoint fixtures under V8 coverage and enforces per-module function and branch baselines. The measured set includes policy, typed errors, call registration, execution middleware, lifecycle/observability, logging, Runtime/CLI orchestration, state persistence, relay lifecycle, managed-job lifecycle/runner/storage/projection, browser broker/direct-request/runtime-client routing and loopback upgrade handling, the independently injected service CLI adapter, Agent configuration/projection/skill discovery and bounded text reads, capability ranking, runtime path redaction, browser protocol/operation boundaries, runtime reporting/diagnostics/capability composition, and Worker OAuth state/authorization-page/pending/policy/error/JSON-RPC/WebSocket protocol modules. The full stdio and workerd OAuth/MCP integration still runs separately.
85
89
 
86
90
  The gate deliberately reports each module rather than one aggregate percentage. New extracted pure/domain modules carry explicit branch floors, while the broad CLI entrypoint remains reported and locked independently. Worker pending calls and policy now have branch minima instead of function-only gates. A refactor may raise a threshold, but must not lower one merely to make CI green without an explicit audit explanation.
87
91
 
88
- `npm run typecheck` combines strict Worker TypeScript with a focused `tsconfig.local.json` gate. High-risk JavaScript contracts opt in with `// @ts-check` and JSDoc types; the gate currently covers policy, call lifecycle, Agent configuration/path resolution, browser handshake parsing, capability ranking, monotonic deadlines, number/record normalization, and bounded metadata reads. `@ts-ignore`, implicit `any`, or unchecked parallel contract shapes are not accepted as migration shortcuts.
92
+ `npm run typecheck` combines strict Worker TypeScript with a focused `tsconfig.local.json` gate. High-risk JavaScript contracts opt in with `// @ts-check` and JSDoc types; the gate currently covers policy, call lifecycle, Agent configuration/path resolution, public projection, skill discovery, bounded Agent text reads, runtime path redaction, browser handshake parsing, capability ranking, monotonic deadlines, number/record normalization, and bounded metadata reads. `@ts-ignore`, implicit `any`, or unchecked parallel contract shapes are not accepted as migration shortcuts.
89
93
 
90
94
  ## Additional release checks
91
95
 
@@ -102,7 +106,7 @@ npm run release-impact:check
102
106
  npm run release:acceptance:verify
103
107
  ```
104
108
 
105
- GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI downloads the exact npm 12.0.1 tarball from the canonical registry, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH` before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and run OpenSSF Scorecard. The SARIF gate fails closed when rule metadata is absent, CodeQL permits only one exact, expiring non-shell process-boundary result, and Scorecard permits only exact reviewed governance/time-dependent findings; new dependency-pinning, fuzzing, or other supply-chain results fail before upload. Scorecard's signed analysis job contains only pinned `uses` steps, while a dependent gate job downloads the SARIF, enforces the accepted inventory, and uploads it to code scanning. Security property tests use a `.js` `fast-check` suite with deterministic seeds because the pinned Scorecard scanner recognizes JavaScript property testing only in `*.js`/`*.jsx`. The installed zero-argument startup smoke test also verifies that Windows creates and remembers `%USERPROFILE%\MachineBridge` while other platforms retain the package-free current directory. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot groups GitHub Action updates into one atomic PR so coupled suites such as CodeQL cannot drift across versions. Pull-request CI also rebuilds the package and verifies the tracked repository-owner acceptance record before running install/package audit stages. Source release creation additionally requires successful push-triggered CI, CodeQL, Governance, and Scorecard runs for the exact `main` commit.
109
+ GitHub Actions uses the pinned Node 26/npm 12 baseline in three execution paths. Ubuntu runs `check:full` and verifies interactive candidate acceptance. macOS and Windows run `check:platform` plus the installed-package smoke test, preserving platform coverage without repeating every Worker, browser, and package integration fixture three times. A separate package-audit job scans reachable Git history, audits complete and production dependency graphs, verifies registry signatures and attestations, validates a CycloneDX SBOM under the runner temporary directory, repeats the isolated installation test, and performs a package dry run. Because Node 26 currently bundles npm 11, every npm execution job downloads the exact npm 12.0.1 tarball, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH`. Separate pinned workflows validate governance titles, dependency changes, CodeQL, and OpenSSF Scorecard. Third-party Actions remain pinned to immutable commits, SARIF findings fail closed unless exactly reviewed, and source release creation requires successful push-triggered CI, CodeQL, Governance, and Scorecard runs for the exact `main` commit.
106
110
 
107
111
  ## Test design rules
108
112
 
@@ -123,7 +127,7 @@ Run `npm run privacy:check` before committing and before packaging. Run and revi
123
127
 
124
128
  ## Package manifest
125
129
 
126
- `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts, credential-like file classes, and generated Worker type declarations, validates every packaged mode as `0644` or `0755`, and verifies that privacy/engineering guidance, runtime/relay/secure-file/lock/service/browser/app modules, all package-script helpers, the packaged browser extension, contribution discipline, and privacy/release-impact checkers are present. `npm run install:test` requires npm 12, installs the real tarball from a package-free directory into an isolated global prefix with the documented options, verifies the packaged npm engine requirement, rejects blocked-script warnings, confirms optional `fsevents` is absent, verifies `--version`, then runs the installed CLI with zero arguments from an isolated workspace/state root. A fake Wrangler JavaScript entrypoint terminates at a controlled deployment boundary, so the probe executes default command normalization, state initialization, deployment fingerprint collection, and Wrangler dispatch without changing a live account; any undefined identifier or earlier crash fails the release. The test is part of `npm run check` and runs on Linux, macOS, and Windows CI.
130
+ `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts, credential-like file classes, and generated Worker type declarations, validates every packaged mode as `0644` or `0755`, and verifies that required runtime, script, browser-extension, privacy, governance, and release files are present. `npm run install:test` requires npm 12, installs the real tarball from a package-free directory into an isolated global prefix with the documented options, verifies the packaged npm engine requirement, rejects blocked-script warnings, confirms optional `fsevents` is absent, verifies `--version`, then runs the installed CLI with zero arguments from an isolated workspace/state root. A fake Wrangler JavaScript entrypoint terminates at a controlled deployment boundary, so the probe exercises startup without mutating a live account. Package testing is full-plan-only; installation smoke runs in the Ubuntu full plan, the macOS/Windows platform plan, and package audit.
127
131
 
128
132
  `npm run lint` uses ESLint as a semantic JavaScript correctness gate rather than a style formatter. It covers the Node CLI/runtime, repository scripts, tests, and packaged browser extension and rejects undefined identifiers in function bodies that `node --check` cannot detect. A dedicated lint-gate self-test proves that both Node and browser configurations reject a synthetic undefined binding while accepting the service-worker `importScripts` global. A focused `shell:test` requires Wrangler to run through the current Node executable and its package JavaScript entrypoint rather than a `.cmd` or shell shim. Architecture tests require `shell:test`, `lint:test`, `lint`, and `install:test` to remain in the complete check pipeline and reject non-exact direct dependency ranges.
129
133
 
@@ -131,4 +135,4 @@ The stdio integration test also sends an oversized line, verifies bounded reject
131
135
 
132
136
  ## Architecture and documentation regression checks
133
137
 
134
- `npm run architecture:test` runs independent module-boundary, repository-hygiene, browser/security-structure, and release/documentation-contract checks. It rejects movable GitHub Action references and loss of the CI history scan, release-gate script drift, local-module dependency cycles, missing static or dynamic relative imports, package scripts that reference missing files, drift from the recursive syntax scanner, incomplete executable package directories, inconsistent installation guidance, obsolete `LocalDaemon`/`daemon.mjs` naming, broken relative Markdown links, invisible ASCII control bytes in repository text, removal of the owner-required default-`full` engineering invariant, and accidental publication of `.project-local/` notes.
138
+ `npm run architecture:test` runs independent module-boundary, repository-hygiene, browser/security-structure, and release/documentation-contract checks. It validates the explicit fast/full check plans, local import graph, domain/adapter direction, module headroom budgets, immutable workflow references, package-script targets, documentation links, publication inventory, and selected security-shape invariants. These source-shape checks are deliberately supplementary: behavior, denial, race, and fault-injection tests remain authoritative for semantic guarantees. Tests must not depend on a fixed CI job count when the actual invariant is that every npm job uses the same verified bootstrap.