machine-bridge-mcp 3.0.0-beta.17 → 3.0.0-beta.21

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 (74) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +2 -0
  3. package/SECURITY.md +9 -7
  4. package/browser-extension/browser-error-boundary.js +45 -0
  5. package/browser-extension/browser-operations.js +1 -1
  6. package/browser-extension/manifest.json +1 -1
  7. package/browser-extension/service-worker.js +9 -8
  8. package/docs/ARCHITECTURE.md +17 -15
  9. package/docs/AUDIT.md +30 -0
  10. package/docs/ENGINEERING.md +3 -3
  11. package/docs/GETTING_STARTED.md +1 -1
  12. package/docs/LOCAL_AUTHORIZATION.md +2 -2
  13. package/docs/LOCAL_AUTOMATION.md +1 -1
  14. package/docs/LOGGING.md +3 -1
  15. package/docs/MULTI_ACCOUNT.md +2 -2
  16. package/docs/OPERATIONS.md +13 -5
  17. package/docs/OVERVIEW.md +2 -2
  18. package/docs/PRIVACY.md +1 -1
  19. package/docs/TESTING.md +17 -13
  20. package/docs/THREAT_MODEL.md +13 -8
  21. package/docs/UPGRADING.md +19 -1
  22. package/package.json +1 -1
  23. package/scripts/coverage-check.mjs +12 -0
  24. package/src/local/account-admin.mjs +68 -1
  25. package/src/local/agent-skill-discovery.mjs +16 -5
  26. package/src/local/app-automation.mjs +27 -7
  27. package/src/local/cli-options.mjs +1 -1
  28. package/src/local/path-inspection.mjs +23 -0
  29. package/src/local/process-tree-ownership.mjs +17 -5
  30. package/src/local/process-tree.mjs +6 -2
  31. package/src/local/relay-connection.mjs +24 -11
  32. package/src/local/resource-operations.mjs +37 -8
  33. package/src/local/runtime-activation.mjs +16 -0
  34. package/src/local/runtime-capabilities.mjs +15 -3
  35. package/src/local/runtime.mjs +7 -4
  36. package/src/local/worker-deployment.mjs +16 -6
  37. package/src/local/workspace-file-service.mjs +29 -11
  38. package/src/worker/daemon-socket-attachment.ts +52 -0
  39. package/src/worker/daemon-sockets.ts +17 -48
  40. package/src/worker/durable-stream-calls.ts +129 -0
  41. package/src/worker/durable-stream-result.ts +22 -0
  42. package/src/worker/http.ts +37 -15
  43. package/src/worker/index.ts +143 -146
  44. package/src/worker/mcp-pending-call-expiry.ts +20 -0
  45. package/src/worker/mcp-pending-call-inspection.ts +33 -0
  46. package/src/worker/mcp-pending-call-records.ts +58 -0
  47. package/src/worker/mcp-pending-call-storage.ts +18 -0
  48. package/src/worker/mcp-pending-call-store.ts +285 -0
  49. package/src/worker/mcp-resumption-index.ts +41 -0
  50. package/src/worker/mcp-resumption-records.ts +8 -2
  51. package/src/worker/mcp-resumption.ts +84 -86
  52. package/src/worker/mcp-stream-dispatch.ts +42 -54
  53. package/src/worker/mcp-stream-proxy.ts +7 -69
  54. package/src/worker/mcp-stream-subscription.ts +98 -0
  55. package/src/worker/oauth-controller.ts +11 -2
  56. package/src/worker/oauth-refresh-exchange.ts +147 -0
  57. package/src/worker/oauth-refresh-families.ts +59 -17
  58. package/src/worker/oauth-state.ts +13 -2
  59. package/src/worker/oauth-token-derivation.ts +33 -0
  60. package/src/worker/oauth-token-issuance.ts +106 -0
  61. package/src/worker/oauth-tokens.ts +12 -184
  62. package/src/worker/observability.ts +19 -0
  63. package/src/worker/pending-admission.ts +15 -0
  64. package/src/worker/pending-call-contract.ts +4 -7
  65. package/src/worker/pending-calls.ts +4 -10
  66. package/src/worker/runtime-alarm-storage.ts +35 -0
  67. package/src/worker/runtime-alarm.ts +30 -20
  68. package/src/worker/websocket-protocol.ts +4 -0
  69. package/src/worker/worker-edge-guard.ts +81 -0
  70. package/src/worker/worker-edge-log.ts +63 -0
  71. package/src/worker/worker-entry.ts +50 -0
  72. package/src/worker/worker-metadata.ts +39 -0
  73. package/src/worker/worker-static-routes.ts +38 -50
  74. package/wrangler.jsonc +8 -0
@@ -27,7 +27,7 @@ Effective authority is the intersection of:
27
27
 
28
28
  There is no temporary elevation path. A `reviewer`, `editor`, or `operator` cannot acquire `owner` capability through a local lease, approval ID, token refresh, or reconnect.
29
29
 
30
- The Worker filters `tools/list` and rejects unauthorized calls before relay. Every accepted call carries account ID, account version, OAuth client ID, refresh-family ID, and role. The local runtime validates those values again before dispatch.
30
+ The Worker filters the stable `tools/list` discovery catalog by account role. It separately rejects calls that are outside the current account role or the live end-to-end-ready daemon ceiling before relay. Every accepted call carries account ID, account version, OAuth client ID, refresh-family ID, and role. The local runtime validates those values again before dispatch.
31
31
 
32
32
  Authenticated `server_info.authorization.effective_policy` and `effective_tools` describe the current account. `daemon.policy` and `daemon.tools` describe only the local capability ceiling; a `full` daemon does not make an `editor` account full.
33
33
 
@@ -107,7 +107,7 @@ Authorization codes bind:
107
107
 
108
108
  Access and refresh tokens additionally bind to the deployment token version and refresh-family ID. Token values are stored as SHA-256 lookup keys.
109
109
 
110
- Access tokens last fifteen minutes. Refresh tokens rotate on every use, have a fourteen-day idle limit and thirty-day family limit, and leave bounded replay markers. Reuse of a consumed refresh token revokes the complete family, including active access tokens.
110
+ Access tokens last fifteen minutes. Refresh tokens rotate on every use, have a fourteen-day idle limit and thirty-day family limit, and leave bounded replay markers. To tolerate a lost response or concurrent hosted-client refresh, the same consumed token may return the same HMAC-derived replacement pair at most twice during a 30-second window. These retries do not create new credential branches or extend expiration. Further in-window attempts are rate-limited; reuse after that window revokes the complete family, including active access tokens.
111
111
 
112
112
  A refresh request also verifies that the client remains bound to the current account version and role.
113
113
 
@@ -30,16 +30,22 @@ machine-mcp --verbose
30
30
 
31
31
  Run these commands from the same environment used for startup so `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` are identical. After the foreground connection succeeds, run `machine-mcp service install` from that same PowerShell session. Installation stores only an allowlisted proxy/custom-CA environment snapshot in private local state so the logon task does not lose session-only `$env:` settings after reboot. A later start with no proxy variables does not erase the saved snapshot; set a variable explicitly to an empty value and reinstall to clear it. `machine-mcp service status` reports only the saved key names. Debug logs expose only the selected route and classified error; they never print a proxy endpoint or credentials.
32
32
 
33
+ ### Worker quota controls
34
+
35
+ The standard public endpoint remains the automatically provisioned `workers.dev` URL printed by Machine Bridge; no personal domain is required. The outer Worker serves health, discovery metadata, CORS preflight, and unknown 404s without Durable Object state. Stateful routes pass a 120-per-minute Cloudflare Rate Limiting binding before DO dispatch. This guard is per Cloudflare location and executes after the Worker starts; it protects the Durable Object from ordinary bursts but is not an exact account-wide daily Workers quota. Configure Cloudflare account usage alerts when available. A binding outage fails open so quota protection cannot become an authentication outage.
36
+
37
+ `server_info.worker.observability.oauth_refresh` distinguishes normal rotation, bounded retry issuance, exhausted retry budget, post-grace family revocation, and rejected grants. `durable_budget` reports estimated stream-row writes plus alarm sets, deletes, and no-ops. These are process-lifetime logical counters for diagnosis, not Cloudflare billing records.
38
+
33
39
  ### Blocking-layer decision table
34
40
 
35
41
  | Result | Interpretation |
36
42
  |---|---|
37
43
  | `authorization.effective_policy.profile` is `full` and the tool is in `authorization.effective_tools`, but the current session UI exposes fewer tools | Host/connector post-relay filtering; Machine Bridge cannot enumerate or override that subset |
38
44
  | `daemon.policy.profile` is `full` but `authorization.effective_policy.profile` is `review`, `edit`, or `agent` | Expected account-role narrowing; the daemon field is only a capability ceiling and must not be reported as the account permission |
39
- | `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed; no daemon tools are advertised and the candidate will be closed at the readiness deadline |
45
+ | `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed. The stable account catalog remains discoverable, while `authorization.effective_tools` contains no executable daemon tool and calls fail retryably until readiness |
40
46
  | `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
41
47
  | `task_resolution_observed` is false after a substantive task | The host/model did not call `resolve_task_capabilities`; server-side discovery cannot force that host decision |
42
- | Task resolution ran but all match counts are zero | The resolver ran successfully but found no sufficiently relevant local skill, command, or application |
48
+ | Task resolution ran but all match counts are zero | Check `application_discovery`: `available=false` or a nonzero warning count means application inventory was partial or unavailable; otherwise the resolver ran successfully but found no sufficiently relevant local skill, command, or application |
43
49
  | No structured result because the host rejects the call | Host/connector approval or safety layer, or transport before daemon delivery |
44
50
  | `mcp-host-to-daemon` passes but `local-filesystem` fails | Local state/runtime permissions, disk policy, sandbox, or endpoint security |
45
51
  | Filesystem passes but `local-process-spawn` fails | Local executable policy, endpoint security, OS permissions, or damaged Node runtime |
@@ -53,9 +59,11 @@ A successful diagnostic result applies only to that probe. An MCP host can still
53
59
 
54
60
  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.
55
61
 
56
- `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 two-minute same-instance reconnect window. Calls for simple reads and probes should continue while another independent process call runs. Only explicit session-scoped MCP cancellation, timeout, or reconnect-grace expiry removes the pending record and its request key; an HTTP response disconnect is not cancellation. 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. A verified same-instance replacement transfers both detached and still-attached calls before the incumbent closes. Normal and reconnect deadlines have three enforcement paths: monotonic in-event timers, a Durable Object alarm, and an overdue sweep at the next HTTP/WebSocket event. Therefore `detached > 0` with `oldest_ms` materially beyond the two-minute grace is a lifecycle defect rather than normal recovery. 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.
62
+ `server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, `by_tool`, `transient`, and `durable_streams`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and `authorization.effective_tools`. The stable role-filtered `tools/list` catalog does not disappear during a brief outage. 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 two-minute same-instance reconnect window. Calls for simple reads and probes should continue while another independent process call runs. Only explicit session-scoped MCP cancellation, timeout, or reconnect-grace expiry removes the pending record and its request key; an HTTP response disconnect is not cancellation. A daemon-socket closure detaches only calls assigned to its opaque connection generation; the same daemon process can reclaim them after completing readiness, while another process cannot. A verified same-instance replacement transfers both detached and still-attached calls before the incumbent closes, and delayed old-socket results are rejected. JSON-only and streamed calls pass through one FIFO admission gate and share the reported 32-call ceiling. JSON-only calls use an in-event timer plus the shared alarm/sweep backstop. Streamed calls persist their operation or reconnect deadline and use the Durable Object alarm plus event-entry sweep; no JavaScript timer is their durable owner. Each detach/rebind monotonically extends active-record expiry over the new reconnect and remaining-operation budgets. Therefore `detached > 0` with `oldest_ms` materially beyond the two-minute grace is a lifecycle defect rather than normal recovery. 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.
63
+
64
+ A Worker-requested `daemon_transport_error` or `daemon_liveness_timeout` is a retryable connection invalidation. The local daemon must close only the affected socket, preserve pending-call detach semantics, and reconnect; it must not enter the fatal `relay_protocol_error` path or exit for launchd to restart. The Worker uses close code 1012 for these transient cases. The daemon also recognizes the bounded close reasons `daemon pong failed`, `daemon send failed`, and `daemon liveness timeout` if the preceding error frame is not delivered. Unknown error codes, authentication rejection, and server identity/version mismatch remain permanent failures and require operator action.
57
65
 
58
- For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, the outer Worker returns an immediate sequence-zero SSE event identifier and a keepalive comment every ten seconds until the terminal sequence-one JSON-RPC result. `BridgeRoom` never owns the long-lived public stream or an unresolved terminal Promise. Stream initiation commits recovery state, registers the daemon call, sends it, and returns a descriptor; a later WebSocket result, explicit cancellation, timeout, send failure, or reconnect-grace expiry writes the terminal result. One internal hibernatable WebSocket subscription therefore coexists with concurrent `server_info`, recovery, and session-scoped `notifications/cancelled` requests while SSE remains open, without creating a request per poll interval. Caller-supplied internal stream headers are removed at the public boundary. If the client or an intermediary closes the stream, Machine Bridge keeps the bounded operation alive; only `notifications/cancelled` carries cancellation semantics. A compatible host resumes the original stream with authenticated `GET /mcp`, the original `MCP-Session-Id`, and `Last-Event-ID`; it must not repeat the POST. Recovery records are token/session-bound, retained for at most two minutes, limited to 64 streams, and persist at most 1.5 MiB of terminal JSON. Error `-32002` means the online result exceeded the replay budget; `-32003` means the Worker restarted before it could persist a terminal result and the operation may already have produced side effects; reconcile state before retrying. Error `-32005` means stored replay data failed integrity validation.
66
+ For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, the outer Worker returns an immediate sequence-zero SSE event identifier and a keepalive comment every ten seconds until the terminal sequence-one JSON-RPC result. `BridgeRoom` never owns the long-lived public stream or an unresolved terminal Promise. Stream initiation commits recovery state and then durably attaches the opaque call ID, daemon instance, WebSocket generation, request key, deadlines, and bounded transform metadata before sending work and returning a descriptor. A later WebSocket result, explicit cancellation, operation timeout, send failure, or reconnect-grace expiry writes the terminal result through one generation-checked transaction. One internal hibernatable WebSocket subscription therefore coexists with concurrent `server_info`, recovery, and session-scoped `notifications/cancelled` requests while SSE remains open, without creating a request per poll interval. Caller-supplied internal stream headers are removed at the public boundary. If the client or an intermediary closes the stream, Machine Bridge keeps the bounded operation alive; only `notifications/cancelled` carries cancellation semantics. A compatible host resumes the original stream with authenticated `GET /mcp`, the original `MCP-Session-Id`, and `Last-Event-ID`; it must not repeat the POST. Recovery records are token/session-bound, retained for at most two minutes, limited to 64 streams, and persist at most 1.5 MiB of terminal JSON. Error `-32002` means the online result exceeded the replay budget. Error `-32003` is reserved for a pending stream record that has no durable call owner after restart; a valid persisted call remains pending and recoverable. Error `-32005` means stored replay data failed integrity validation.
59
67
 
60
68
  `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.
61
69
 
@@ -165,7 +173,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
165
173
 
166
174
  ### Lifecycle and pending-call diagnosis
167
175
 
168
- `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.
176
+ `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, timeout, or reconnect-grace expiry. An HTTP/SSE client disconnect alone is transport disposal and may leave a recoverable call active until terminal delivery or the normal lifecycle boundary. 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.
169
177
 
170
178
  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.
171
179
 
package/docs/OVERVIEW.md CHANGED
@@ -41,7 +41,7 @@ flowchart LR
41
41
  ### Remote transport
42
42
 
43
43
  1. The Worker validates OAuth client, token, account state, role, resource binding, and MCP session state.
44
- 2. The Worker filters advertised tools by account role and the daemon-reported capability ceiling.
44
+ 2. The Worker exposes a stable account-role discovery catalog, then independently intersects each execution with the current end-to-end-ready daemon capability ceiling.
45
45
  3. The Durable Object relays a bounded tool envelope carrying account, account-version, OAuth-client, refresh-family, and role identity over the root-certified ephemeral daemon socket.
46
46
  4. The local runtime revalidates account authorization, policy, call lifecycle, timeout, and cancellation.
47
47
  5. The local authority gate permits work only inside the immutable role ceiling. Consequential effects are classified for hard enforcement and audit; they never create a temporary elevation path.
@@ -84,7 +84,7 @@ Low-level responsibilities remain in focused modules. Architecture tests enforce
84
84
 
85
85
  Each canonical workspace has independent profile state, Worker identity, trusted clients, account/token state, legacy-lease cleanup state, audit-chain state, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
86
86
 
87
- The relay distinguishes a root-certified ephemeral session, signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the certified session key and complete an end-to-end probe before replacing an incumbent. Disconnect cancels relay-owned calls and terminates associated local processes.
87
+ The relay distinguishes a root-certified ephemeral session, signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the certified session key and complete an end-to-end probe before replacing an incumbent. A transient socket loss detaches ordinary calls for the bounded same-daemon reconnect window; only explicit cancellation, timeout, reconnect-grace expiry, runtime shutdown, or a non-recoverable daemon replacement terminates their local process trees.
88
88
 
89
89
  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.
90
90
 
package/docs/PRIVACY.md CHANGED
@@ -61,4 +61,4 @@ For an accidental publication, remove the value from the current tree and releas
61
61
 
62
62
  For Streamable HTTP recovery, the workspace Durable Object may temporarily persist the terminal JSON-RPC response of a remote tool call. This response can contain source text, command output, file metadata, images encoded by the protocol, or other user data returned by the requested tool. It is operational delivery state, not anonymized telemetry and not publication-safe evidence.
63
63
 
64
- Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute retention window. Records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value, and are removed on expiry or completed-record eviction. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
64
+ Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute terminal-retention window. While a streamed call is active, the record also contains the tool name, opaque call and WebSocket-generation identifiers, daemon-process identifier, client request correlation, operation/reconnect deadlines, and bounded account metadata needed only to project `project_overview`. It does not persist tool arguments or an in-progress result. Records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value after terminal serialization, and are removed on expiry or completed-record eviction. Opaque call, connection, stream, and event identifiers are correlation values rather than bearer credentials. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
package/docs/TESTING.md CHANGED
@@ -24,7 +24,7 @@ The suite includes:
24
24
  - GitHub backlog enforcement that paginates all open issues and pull requests, permits only the current branch PR, and requires standard closing keywords for every open issue before a guarded push;
25
25
  - release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
26
26
  - strict dev/beta/rc/stable channel parsing and npm dist-tag enforcement;
27
- - persistent candidate relay/service handoff ordering and fault cleanup;
27
+ - persistent candidate relay/service handoff ordering, early-failure provider restoration after lock cleanup, and aggregated restoration failures;
28
28
  - owner-only local and registry prerelease activation records;
29
29
  - npm/GitHub prerelease metadata validation, minimum soak timing, blocking-issue rejection, and stable-promotion content/file-mode identity;
30
30
  - 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;
@@ -38,9 +38,9 @@ The suite includes:
38
38
  - foreground takeover of active and orphaned background daemons with current service-lock metadata, foreground-process protection, bounded final lock-handoff retry, actual-PID exit waiting, POSIX non-escalating timeout behavior, Windows verified-daemon stop semantics, daemon lock mode/version/process-start metadata, launchd service-target semantics, and silent idempotent duplicate service starts; daemon fixture subprocesses intentionally do not inherit V8 coverage because their purpose is ownership timing rather than code measurement;
39
39
  - fail-closed service lifecycle ordering for provider-stop, all-workspace daemon-stop, and definition removal, including platform/daemon/removal failure injection and normalized macOS/systemd/Windows results; service-PATH coverage reproduces two nested npm run-script prefixes plus a stale prior candidate runtime and proves the current Node/runtime and inherited user tools remain while npm-private and inactive-runtime entries are removed; Windows coverage reproduces an inline `/TR` command above 262 characters, proves the short launcher action remains bounded, verifies least-privilege logon registration, restart/log routing, language-independent `Ready`/`Running` observation, and state-observed stop/removal despite localized nonzero command output;
40
40
  - private service-environment capture/load coverage for exact allowlisting, value bounds and control-character rejection, non-proxy secret exclusion, runtime-value precedence, Windows case-insensitive replacement, explicit empty-value clearing, and preservation across a later environment-free startup;
41
- - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, direct loopback health despite hostile environment-proxy settings, stop-during-start generation invalidation, frozen-wall-clock browser deadline coverage, installed-application discovery caching, and name-based task matching;
42
- - Worker health direct/proxy routing through a real local HTTP CONNECT proxy, `NO_PROXY` bypass, exact `workers.dev` origin/name validation, redirect rejection, body/deadline bounds, error classification, bounded propagation retry, and invalid proxy fail-fast behavior without endpoint or credential disclosure;
43
- - Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a second start performs no upload, an actual process restart and disk-state reload still performs no upload, definitive stale-version evidence redeploys under the same name, accidental name changes are rejected, forced replacements clear current endpoint state, and prior names remain in uninstall inventory;
41
+ - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, direct loopback health despite hostile environment-proxy settings, stop-during-start generation invalidation, frozen-wall-clock browser deadline coverage, installed-application discovery caching, per-root warning projection, capability-resolution degradation reporting, and name-based task matching;
42
+ - Worker health direct/proxy routing through a real local HTTP CONNECT proxy, `NO_PROXY` bypass, exact `workers.dev` origin/name validation, redirect rejection, body/deadline bounds, error classification, an extended bounded post-deployment propagation window, and invalid proxy fail-fast behavior without endpoint or credential disclosure;
43
+ - Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a matching current-version fingerprint receives the full propagation budget and never uploads again without explicit `--force-worker`, an actual process restart and disk-state reload still performs no upload, definitive legacy or unversioned stale evidence redeploys under the same name, accidental name changes are rejected, forced replacements clear current endpoint state, and prior names remain in uninstall inventory;
44
44
  - relay environment-proxy direct/bypass/agent selection, unsupported proxy rejection, fail-fast invalid configuration, and route observability without endpoint or credential disclosure;
45
45
  - canonical path and symbolic-link escape tests;
46
46
  - relative-path privacy and error-path redaction tests;
@@ -53,7 +53,7 @@ The suite includes:
53
53
  - fixed internal command execution with validated argv, no shell, isolated HOME/temp/cache, bounded output/deadlines, and ordinary cancellation/process tracking while arbitrary delegated process tools remain sandbox-gated;
54
54
  - author-email privacy in `git_log`;
55
55
  - isolated command HOME/temp/cache behavior;
56
- - one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, cancellation, and process-session interaction;
56
+ - one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, post-`SIGTERM` ownership refresh, targeted PID identity fallback when full process-table inspection is unavailable, PID-reuse denial, repeated leak checks, cancellation, and process-session interaction;
57
57
  - layered fixed runtime diagnostics for filesystem, direct process, shell, managed-job storage, and resource availability; machine-readable execution guardrails that must keep CPU, memory, and network isolation marked unenforced unless an actual OS boundary is added;
58
58
  - local resource CLI registration, permission checks, dynamic reload, state-path redaction, content non-disclosure, and the extracted runtime-resource boundary for bounded binary/UTF-8 reads plus generation authorization;
59
59
  - real Ed25519 and RSA generation, idempotent reuse, public/private correspondence, mode enforcement, incomplete/mismatched/symlink rejection, and private-content non-disclosure;
@@ -65,7 +65,7 @@ The suite includes:
65
65
  - guarded state-root removal, unsafe state-root/workspace overlap rejection before creation, all-profile lock/daemon scanning, strict current-schema validation, corrupt-JSON isolation, and policy-origin persistence;
66
66
  - no filename-based sensitive-file denial under unrestricted policy;
67
67
  - shared local/Worker free-form log redaction, sensitive content under non-sensitive Worker keys, immutable local/Worker structured metadata, control-character handling, message/field bounds, suppression of both successful and failed per-tool events outside debug, service warning-level configuration, JSON-mode parity across event and direct logger methods with timestamp/stream/redaction assertions, current-schema reset, and bounded tail trimming;
68
- - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, separate `hello_ack` authentication and `ready_ack` end-to-end readiness, session-bound probe return, pre-ready tool rejection, premature-ready rejection, identity/version mismatch, retryable Worker hello/readiness errors, fatal protocol errors, autonomous outage-reminder backoff, handshake/readiness/heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
68
+ - deterministic relay connection lifecycle coverage for transport construction/error/deadline, failed `hello` delivery, pre-handshake `welcome` validation, separate `hello_ack` authentication and `ready_ack` end-to-end readiness, session-bound probe return and probe-delivery races, pre-ready tool rejection, premature-ready rejection, identity/version mismatch, retryable Worker hello/readiness/transport/liveness errors, retryable close-only transport/liveness delivery, fatal unknown protocol errors, autonomous outage-reminder backoff, handshake/readiness/heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
69
69
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
70
70
  - owner-only directory enforcement rejecting final symlinks, failing closed on POSIX chmod errors, verifying `0700`, and retaining Windows portability; Worker temporary-secret lifecycle coverage for process-start-bound names, valid stale-owner reclamation, ambiguous-owner retention, `0600` mode, deletion failures, and simultaneous deployment/cleanup failures;
71
71
  - SARIF security-gate behavior for unknown findings, exact accepted rule/path matches, path mismatch rejection, rationale quality, and exception expiry;
@@ -79,14 +79,16 @@ The suite includes:
79
79
  - live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
80
80
  - P-256 root generation, root-certified ephemeral session issuance, macOS trust-broker build/signature checks, signed WebSocket preflight, one-time transactional nonce consumption, challenge transcript binding, wrong-root/session/tamper/expiry/replay rejection, and prevention of unauthenticated candidate churn;
81
81
  - request-scoped effective authority and catalog-wide risk review; non-escalatable reviewer/editor/operator ceilings; authenticated-owner direct execution; control-plane root denial; external and sensitive path composition; persistence-target rejection; symbolic-link ancestor and patch-move canonicalization; owner-only browser/application/data-export and persistent-plan effects; account/client/refresh-family ownership of processes, output sessions, and jobs; delegated sandbox fail-closed behavior; legacy-lease non-consumption; and malformed-record rejection;
82
- - root-certified ephemeral P-256 account-administration requests with origin/method/path/body/key/time/nonce binding, transactional one-time nonce consumption, removal of the long-lived administration secret, certificate/signature/body tamper rejection, nonce replay rejection, and malformed nonce-state fail-closed behavior;
83
- - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, trusted single-account client binding, optional DPoP proof and token-family binding, unsupported critical-header rejection, proof-verification non-consumption, post-authorization replay consumption, invalid-grant cache-exhaustion resistance, independent client revocation, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation, stale refresh replay rejection with whole-family access/refresh revocation, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, SSE content negotiation including `q=0`, immediate stream priming, keepalive and terminal-event framing, HTTP abort without implicit cancellation, explicit cancellation after response disconnect, shared Worker/local timeout ceilings, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
82
+ - root-certified ephemeral P-256 account-administration requests with origin/method/path/body/key/time/nonce binding, transactional one-time nonce consumption, removal of the long-lived administration secret, certificate/signature/body tamper rejection, nonce replay rejection, malformed nonce-state fail-closed behavior, one-megabyte response bounds, immediate oversized-response cancellation, and strict successful JSON-object validation;
83
+ - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, trusted single-account client binding, optional DPoP proof and token-family binding, unsupported critical-header rejection, proof-verification non-consumption, post-authorization replay consumption, invalid-grant cache-exhaustion resistance, independent client revocation, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation, idempotent identity-equivalent concurrent refresh recovery with identical replacement credentials and expiration, retry-budget throttling, post-grace replay with whole-family access/refresh revocation, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, SSE content negotiation including `q=0`, immediate stream priming, keepalive and terminal-event framing, HTTP abort without implicit cancellation, explicit cancellation after response disconnect, shared Worker/local timeout ceilings, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
84
84
  - local runtime proof that one blocked tool handler does not serialize an independent handler, plus relay fault injection proving an undeliverable terminal result interrupts the ambiguous socket and enters reconnect backoff.
85
85
  - a real headless-Chrome OAuth navigation regression with four cases: `form-action 'self'` blocks the first cross-origin callback, allowing only the registered callback blocks the regional redirect, allowing the registered and regional callbacks blocks the final Copilot Studio redirect, and the complete policy preserves `code` and `state` through all three cross-origin hops. Linux CI fails if Chrome is unavailable; other environments skip only this browser executable check while retaining the Worker CSP assertions.
86
86
 
87
+ `npm run ssh-key:test` exercises Ed25519/RSA generation, private-file permissions, public/private matching, existing-pair reuse, incomplete-pair rejection, symbolic-link denial, atomic registration under the startup lock, registry conflicts/capacity, complete two-file rollback after state-write failure, and explicit failure when sensitive-key cleanup remains incomplete.
88
+
87
89
  ## Opt-in live desktop and browser validation
88
90
 
89
- The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Browser contract tests cover acknowledged protocol readiness, provisional pairing commit/rollback boundaries, strict extension IDs and matching loopback ports, stale-extension rejection, compatible replacement without premature displacement, clean rejection of in-flight direct and proxied requests, keepalive handling, trusted-input replay prevention after partial dispatch, focus-safe screenshots, aggregate 64-frame/source/element budgets, bounded hostile DOM/text processing, contenteditable-secret suppression, partial-form failure reporting, stable refs, combined waits, and actionability failures. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
91
+ The normal suite uses deterministic mocks for macOS Accessibility and an authenticated in-process extension peer for browser routing. Browser contract tests cover acknowledged protocol readiness, provisional pairing commit/rollback boundaries, strict extension IDs and matching loopback ports, stale-extension rejection, compatible replacement without premature displacement, clean rejection of in-flight direct and proxied requests, keepalive handling, trusted-input replay prevention after partial dispatch, fixed non-sensitive fallback reasons, privacy-safe public browser errors, an independent extension-side 32-operation ceiling, focus-safe screenshots, aggregate 64-frame/source/element budgets, bounded hostile DOM/text processing, contenteditable-secret suppression, partial-form failure reporting, stable refs, combined waits, and actionability failures. Before a release that changes local UI automation, run the macOS live smoke test on a machine where the invoking Node/terminal process has Accessibility permission:
90
92
 
91
93
  ```sh
92
94
  npm run app-automation:live-test
@@ -150,13 +152,15 @@ Run `npm run privacy:check` before committing and before packaging. Run and revi
150
152
 
151
153
  `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.
152
154
 
153
- The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed.
155
+ The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed. Worker request-body tests use pull-counted streams and require immediate `ReadableStream.cancel()` after a declared or observed limit violation, proving that byte retention and execution work are both bounded.
154
156
 
155
157
  ## Architecture and documentation regression checks
156
158
 
157
- `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.
159
+ `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. Critical coverage thresholds include every extracted OAuth refresh, token issuance, stream subscription, static metadata/routing, quota guard, edge logger, and filesystem-state module. 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.
158
160
  ## Resumable MCP delivery coverage
159
161
 
160
- `npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay, Worker-restart ambiguity, result-size fallback, SHA-256 tamper detection, transient persistence failure, expiry, capacity, and completed-record eviction.
162
+ `npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay, orphaned-stream restart ambiguity, persisted-call restart recovery, strict call-record validation, request-key uniqueness, operation/reconnect deadlines, repeated detach/rebind retention extension, stale-generation rejection, prototype-safe aggregation, exactly-once completion, result-size fallback, SHA-256 tamper detection, transient persistence failure, expiry, capacity, completed-record eviction, the four-row plain-stream budget, and the fixed six-row durable-call lifecycle budget.
163
+
164
+ `npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed request budgets, bounded transient subscription retries, outer metadata/404/invalid-method bypass, stateful burst limiting, structured and duplicate-suppressed gateway failures, coalesced alarm writes, sequence-zero/sequence-one framing, subscription-error closure, subscriber replacement, non-daemon socket isolation, and the shared two-minute/64-stream/1.5-MiB contract. It models the production durable-call boundary: initiation persists ownership without retaining a terminal Promise, later success or daemon rejection settles once, send failure cleans the record, capacity fails closed, and same-instance reconnect moves ownership to a new generation while rejecting the stale one. Deadline tests prove both JSON-only timer/sweep behavior and persisted-call alarm expiry without leaking request keys. A direct runtime-alarm coordinator test verifies the earliest transient-or-durable deadline, alarm removal when no deadline remains, event-entry expiry before rescheduling, and bounded reporting when Durable Object alarm storage fails. The same suite also proves that direct same-instance handover preserves the remaining timeout budget. `npm run worker:integration-test` performs the real Wrangler path: the role-filtered catalog remains stable before, during, and after daemon availability changes; execution still fails closed without a ready daemon; an open SSE stream coexists with concurrent `server_info` and explicit cancellation; verified same-instance replacement transfers an in-flight call before incumbent close; disconnect/recovery remains token/session isolated; and sequence-one acknowledgement is not delivered twice. Managed-job integration treats `__proto__`, `constructor`, `toString`, and `valueOf` environment/resource-map keys as ordinary own data while retaining duplicate-key rejection. Static architecture checks forbid a stream-initiation `dispatchJsonRpc` Promise, `resumption.attach`, Durable Object `waitUntil`, Promise-valued recovery state, or return of the obsolete transient `registerEvent` branch. The parser accumulates complete SSE events and does not assume network chunk boundaries. CORS coverage requires both `DPoP` and `Last-Event-ID`.
161
165
 
162
- `npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed two-request Durable Object budgets, sequence-zero/sequence-one framing, subscription-error closure, subscriber replacement, registration races, non-daemon socket isolation, and the shared two-minute/64-stream/1.5-MiB contract. It also models the production event-lifecycle boundary: event-mode registration must return without a terminal Promise or early settlement, while later success, daemon rejection, explicit cancellation, timeout, send failure, result transformation, persistence failure, and same-instance reconnect each produce one terminal result and remove pending indexes. Deadline tests deliberately use a scheduler that never fires callbacks, advance the monotonic clock, and prove that event-boundary sweeps expire both attached operation deadlines and detached reconnect deadlines without leaking request keys. A direct runtime-alarm coordinator test verifies earliest-pending scheduling, alarm removal when no deadline remains, event-entry expiry before rescheduling, and bounded reporting when Durable Object alarm storage fails. The same suite also proves that direct same-instance handover transfers an attached call and preserves its remaining timeout budget. `npm run worker:integration-test` performs the real Wrangler path: keep SSE open while a concurrent `server_info` succeeds and explicit cancellation reaches the matching daemon call; connect a verified same-instance replacement while the incumbent still owns an in-flight call and prove transfer occurs before incumbent close; disconnect after sequence zero; reject another session; recover with GET plus `Last-Event-ID`; and prove a sequence-one acknowledgement is not delivered twice. Managed-job integration treats `__proto__`, `constructor`, `toString`, and `valueOf` environment/resource-map keys as ordinary own data while retaining duplicate-key rejection. Static architecture checks forbid a stream-initiation `dispatchJsonRpc` Promise, `resumption.attach`, Durable Object `waitUntil`, or Promise-valued recovery state. The parser accumulates complete SSE events and does not assume network chunk boundaries. CORS coverage requires both `DPoP` and `Last-Event-ID`.
166
+ Beta.21 closeout requires a fresh pass of the complete 63-task fast plan and 91-task full plan after the final relay error-classification changes; an earlier green run cannot be reused as terminal evidence.
@@ -113,6 +113,7 @@ Machine Bridge considers:
113
113
  The implementation aims to preserve these invariants:
114
114
 
115
115
  - unknown, malformed, stale, replayed, duplicated, unauthorized, and over-limit input is rejected;
116
+ - the stable account-role discovery catalog is not treated as execution authority; every call is intersected with the current end-to-end-ready daemon policy and fails closed when that authority is absent;
116
117
  - remote authority is the intersection of daemon policy and account role, never the union;
117
118
  - no approval record, token refresh, or local migration state can elevate a delegated role;
118
119
  - OAuth clients are bound to one account/version/role and can be revoked independently;
@@ -122,10 +123,10 @@ The implementation aims to preserve these invariants:
122
123
  - delegated process execution is accepted only when an OS sandbox is behaviorally verified;
123
124
  - direct argv execution is used unless the explicit shell tool is authorized;
124
125
  - confined paths are canonicalized and symbolic-link write escape is rejected;
125
- - request bodies, files, messages, output, logs, state, retained results, and concurrency are bounded;
126
- - cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics;
126
+ - request bodies, files, messages, output, logs, state, retained results, concurrency, and error-cause traversal are bounded; byte limits stop further source consumption rather than merely truncating retained data;
127
+ - cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics; streamed calls persist ownership and deadlines across Durable Object hibernation, and per-WebSocket generations reject delayed results or close events from an obsolete connection;
127
128
  - candidate daemons and browser extensions cannot replace healthy incumbents before compatibility and readiness verification;
128
- - default logs and audit records omit secrets, arguments, contents, raw paths, form values, and output;
129
+ - default logs, audit records, discovery warnings, browser responses, and administration errors omit secrets, arguments, contents, raw paths, form values, raw local exception text, and output;
129
130
  - multi-stage mutations are atomic or recoverable and do not silently claim partial success;
130
131
  - device-root rotation is two-phase and does not promote an undeployed key;
131
132
  - supply-chain actions are pinned, minimally permissioned, reviewed, and separately gated.
@@ -159,9 +160,13 @@ A compromised active owner client can exercise the daemon ceiling without a seco
159
160
 
160
161
  Use a narrower profile, separate OS account, container, or VM when prompts, repositories, clients, or workloads are mutually untrusted.
161
162
 
163
+ ### Public Worker endpoint and quota guards
164
+
165
+ The default public endpoint remains the automatically provisioned `workers.dev` origin so ordinary users need no DNS zone or custom domain. The in-Worker Rate Limiting binding is deliberately a Durable Object burst guard, not an authentication boundary or an exact global quota accountant. It runs after a Worker invocation begins, is scoped by Cloudflare location, and currently uses one deployment-wide stateful bucket per location. A concentrated source can therefore cause a temporary localized denial for other clients sharing that location, while distributed traffic can still consume the account-wide Workers request allowance. Binding failure is fail-open to avoid turning a quota helper into an account outage. Operators who already control an external edge may add pre-Worker filtering independently, but the public package does not require or assume a private domain.
166
+
162
167
  ### Bearer clients
163
168
 
164
- DPoP is optional for interoperability. A Bearer token can be used by whoever possesses it until expiry or revocation. Short access-token lifetime, rotating refresh families, client binding, account versioning, and replay-family revocation reduce but do not eliminate token theft risk.
169
+ DPoP is optional for interoperability. A Bearer token can be used by whoever possesses it until expiry or revocation. Short access-token lifetime, rotating refresh families, client binding, account versioning, a bounded identity-equivalent concurrency window, and post-window replay-family revocation reduce but do not eliminate token theft risk.
165
170
 
166
171
  ### Same-user interference
167
172
 
@@ -185,7 +190,7 @@ Application-level limits bound many requests and outputs. An authorized owner pr
185
190
 
186
191
  ### System VPN/TUN and distributed activation
187
192
 
188
- A system VPN/TUN can remain administratively connected while its selected upstream route, synthetic DNS mapping, or transport path is temporarily unusable. Machine Bridge can detect missing inbound relay traffic, classify the socket close, bound retry delay, and report outage history, but it cannot select or repair a third-party VPN node. `system-network-stack` is therefore not a claim of direct routing.
193
+ A system VPN/TUN can remain administratively connected while its selected upstream route, synthetic DNS mapping, or transport path is temporarily unusable. Machine Bridge can detect missing inbound relay traffic, classify the socket close, bound retry delay, and report outage history, but it cannot select or repair a third-party VPN node. `system-network-stack` is therefore not a claim of direct routing. A Worker transport/liveness invalidation is treated as a retryable socket-generation failure; elevating it to a permanent protocol error would let an ordinary network fault restart the daemon and amplify the outage. Unknown protocol messages and authentication or version mismatch remain fail-closed.
189
194
 
190
195
  Candidate activation verifies the foreground candidate before service handoff and records exact package/deployment evidence. It does not provide an atomic transaction spanning Cloudflare deployment and every local service manager. If the Worker changes and the local handoff later fails, the operator may need the recorded previous runtime/deployment evidence to complete rollback. Local cleanup errors are aggregated rather than hidden, but remote rollback is not fabricated.
191
196
 
@@ -199,11 +204,11 @@ Regression suites cover:
199
204
 
200
205
  - account roles, trusted clients, account-version revocation, refresh-family rotation/replay, DPoP proofs, and non-escalatable effective authority;
201
206
  - root-certified ephemeral sessions, preflight nonce replay, daemon challenge binding, readiness, reconnect, and candidate replacement;
202
- - signed account administration, client revocation, and removal of the long-lived administration secret;
207
+ - signed account administration, client revocation, bounded strict-JSON administration responses, and removal of the long-lived administration secret;
203
208
  - control-plane path denial, path canonicalization, symlink handling, sensitive/persistence targets, and object ownership;
204
209
  - delegated sandbox behavior and fail-closed platform detection;
205
- - process/session cleanup, managed-job lifecycle and recovery, state locks, atomic persistence, and destructive removal;
206
- - browser pairing, version/capability handshake, broker routing, sensitive input, and navigation controls;
210
+ - process/session cleanup, generated-key rollback including cleanup failure, managed-job lifecycle and recovery, state locks, atomic persistence, and destructive removal;
211
+ - browser pairing, version/capability handshake, broker routing, independent concurrency limits, public-error redaction, sensitive input, and navigation controls;
207
212
  - audit-chain integrity, privacy redaction, package contents, installation, release impact, dependency integrity, CodeQL, and Scorecard findings;
208
213
  - malformed, over-limit, concurrent, replayed, stale, and fault-injected inputs.
209
214
 
package/docs/UPGRADING.md CHANGED
@@ -67,6 +67,12 @@ Legacy version 2 lease state may be listed, revoked, or cleared for cleanup, but
67
67
 
68
68
  `ACCOUNT_ADMIN_SECRET` is deleted from local state and is no longer deployed to the Worker. Account and OAuth-client administration uses the same root-certified ephemeral session established for daemon startup or an independently authorized local administration command.
69
69
 
70
+ ## Version 3 beta.21 relay-continuity change
71
+
72
+ Beta.21 changes the Worker-side stream-call record and MCP discovery contract. `tools/list` is stable for an authenticated account role; `server_info.authorization.effective_tools` remains the live execution authority. Streamed calls persist their daemon instance, WebSocket generation, request correlation, and deadlines so Durable Object hibernation or restart does not itself orphan an active call. JSON-only requests retain the prior bounded in-event path.
73
+
74
+ Treat beta.21 as a coordinated Worker and daemon candidate. A beta.20 Worker or daemon does not implement the same generation and persistence contract, so exact-version convergence is required before continuity claims are accepted. The change preserves calls only across a relay interruption while the same local daemon process and machine remain alive. A daemon-process restart, machine shutdown, or lost local execution state is still not durable execution; use managed jobs for that requirement.
75
+
70
76
  ## Normal upgrade
71
77
 
72
78
  1. Inspect or cancel interactive processes and managed jobs that should not survive daemon replacement.
@@ -101,7 +107,7 @@ A full daemon policy is not proof that a delegated account has full authority. U
101
107
 
102
108
  Machine Bridge rejects unreadable, malformed, foreign-schema, or ambiguous state rather than silently initializing replacement state.
103
109
 
104
- Worker deployment records upload success separately from health convergence. A post-upload network failure does not trigger an uncontrolled repeated write, and a pending root is not promoted merely because Wrangler returned success.
110
+ Worker deployment records upload success separately from health convergence. Post-deployment verification allows a longer bounded edge-propagation window. Once the current package fingerprint and version are recorded, ordinary retries verify that deployment without uploading again; only explicit `--force-worker` authorizes a duplicate deployment after diagnosis. A failed activation that stopped an active service restores the provider after releasing candidate and workflow locks, and a pending root is not promoted merely because Wrangler returned success.
105
111
 
106
112
  The packaged Swift broker source is a development and protocol-conformance fixture only. The local build is ad-hoc signed and is deliberately rejected by the production validator because it cannot obtain a provisioning-profile-validated data-protection Keychain access group. A production Secure Enclave broker must be shipped as an app-like, correctly signed and provisioned component outside the npm runtime build.
107
113
 
@@ -120,6 +126,18 @@ A rollback must restore together:
120
126
  - the prior browser extension.
121
127
 
122
128
  Do not roll back by editing version or schema fields, copying selected credential files, or restoring only the Worker. Prefer fixing forward when a complete backup is unavailable.
129
+ ## Version 3.0.0-beta.18
130
+
131
+ Beta.18 replaces beta.17 for hosted clients that experienced intermittent account connection loss during refresh rotation. Upgrade Worker, daemon/CLI, and browser-extension metadata together. Existing refresh state migrates from schema 2 to schema 3 without credential deletion.
132
+
133
+ Activate the candidate through the standard owner command:
134
+
135
+ ```sh
136
+ npm run release:candidate:activate -- --allow-worker-deploy
137
+ ```
138
+
139
+ The existing same-name `workers.dev` endpoint remains the public MCP URL, so users do not need to own a domain or update the hosted client endpoint solely for this upgrade.
140
+
123
141
  ## Version 3.0.0-beta.15
124
142
 
125
143
  Beta.15 replaces blocked beta.14. Upgrade Worker, daemon/CLI, and browser-extension metadata together through the normal candidate activation flow. Streamed daemon calls now use event-driven settlement: the initiating Durable Object request returns after registration and send, while later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events persist the terminal result. Clients that support standard resumption should reconnect and reinitialize so they send `MCP-Session-Id` and use `GET /mcp` with `Last-Event-ID`; older JSON-only clients retain single-response behavior but cannot recover a disposed response stream.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "3.0.0-beta.17",
3
+ "version": "3.0.0-beta.21",
4
4
  "description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,7 @@ const tests = [
39
39
  "tests/delegated-process-sandbox-test.mjs",
40
40
  "tests/dpop-test.mjs",
41
41
  "tests/worker-security-boundaries-test.mjs",
42
+ "tests/ssh-key-test.mjs",
42
43
  ];
43
44
 
44
45
  try {
@@ -76,6 +77,8 @@ try {
76
77
  "src/local/log.mjs": [60, 40],
77
78
  "src/local/runtime.mjs": [75, 55],
78
79
  "src/local/runtime-paths.mjs": [90, 50],
80
+ "src/local/resource-operations.mjs": [80, 50],
81
+ "src/local/account-admin.mjs": [90, 60],
79
82
  "src/local/cli.mjs": [48, 21.9],
80
83
  "src/local/cli-service.mjs": [100, 75],
81
84
  "src/local/service-convergence.mjs": [100, 75],
@@ -103,6 +106,7 @@ try {
103
106
  "src/local/runtime-diagnostics.mjs": [75, 65],
104
107
  "src/local/runtime-capabilities.mjs": [75, 45],
105
108
  "src/local/monotonic-deadline.mjs": [100, 100],
109
+ "src/local/path-inspection.mjs": [100, 60],
106
110
  "src/local/state.mjs": [85, 45],
107
111
  "src/local/relay-connection.mjs": [90, 55],
108
112
  "src/local/managed-jobs.mjs": [85, 50],
@@ -129,6 +133,14 @@ try {
129
133
  "src/worker/mcp-resumption-config.ts": [100, 80],
130
134
  "src/worker/mcp-resumption-records.ts": [90, 65],
131
135
  "src/worker/mcp-stream-proxy.ts": [85, 55],
136
+ "src/worker/mcp-stream-subscription.ts": [85, 60],
137
+ "src/worker/worker-static-routes.ts": [100, 90],
138
+ "src/worker/worker-metadata.ts": [100, null],
139
+ "src/worker/worker-edge-guard.ts": [100, 55],
140
+ "src/worker/worker-edge-log.ts": [100, 80],
141
+ "src/worker/oauth-token-issuance.ts": [100, 80],
142
+ "src/worker/oauth-token-derivation.ts": [100, 75],
143
+ "src/worker/oauth-refresh-exchange.ts": [95, 60],
132
144
  "src/worker/mcp-stream-dispatch.ts": [90, 60],
133
145
  "src/worker/mcp-resumption.ts": [90, 70],
134
146
  "src/worker/mcp-stream.ts": [90, 65],
@@ -5,6 +5,7 @@ import { encodeDeviceSessionCertificate, signWithDeviceSessionIdentity, validate
5
5
  import { BridgeError } from "./errors.mjs";
6
6
 
7
7
  const REQUEST_TIMEOUT_MS = 15_000;
8
+ const MAX_ADMIN_RESPONSE_BYTES = 1024 * 1024;
8
9
 
9
10
  export function generateAccountPassword() {
10
11
  return `account_password_${randomBytes(32).toString("base64url")}`;
@@ -84,7 +85,7 @@ export class AccountAdminClient {
84
85
  throw new BridgeError("network_error", "account administration request failed", { cause: error, retryable: true });
85
86
  });
86
87
  if (response.status === 204) return { removed: true };
87
- const payload = await response.json().catch(() => ({}));
88
+ const payload = await readAdminJsonResponse(response);
88
89
  if (!response.ok) {
89
90
  const message = typeof payload.message === "string" ? payload.message : typeof payload.error === "string" ? payload.error : `account administration failed (${response.status})`;
90
91
  throw new BridgeError(response.status === 404 ? "not_found" : response.status === 409 ? "conflict" : response.status === 401 ? "authentication_failed" : "invalid_request", message);
@@ -93,6 +94,72 @@ export class AccountAdminClient {
93
94
  }
94
95
  }
95
96
 
97
+
98
+ async function readAdminJsonResponse(response) {
99
+ const declared = Number(response.headers.get("content-length") || "0");
100
+ if (Number.isFinite(declared) && declared > MAX_ADMIN_RESPONSE_BYTES) {
101
+ await cancelResponseBody(response.body);
102
+ throw new BridgeError("invalid_response", "account administration response exceeded the size limit");
103
+ }
104
+ if (!response.body) {
105
+ if (response.ok) throw new BridgeError("invalid_response", "account administration response was empty");
106
+ return {};
107
+ }
108
+ const reader = response.body.getReader();
109
+ const chunks = [];
110
+ let bytes = 0;
111
+ try {
112
+ for (;;) {
113
+ const { done, value } = await reader.read();
114
+ if (done) break;
115
+ if (!value) continue;
116
+ bytes += value.byteLength;
117
+ if (bytes > MAX_ADMIN_RESPONSE_BYTES) {
118
+ await cancelReader(reader);
119
+ throw new BridgeError("invalid_response", "account administration response exceeded the size limit");
120
+ }
121
+ chunks.push(value);
122
+ }
123
+ } finally {
124
+ reader.releaseLock();
125
+ }
126
+ let payload;
127
+ try {
128
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(concatBytes(chunks, bytes));
129
+ payload = JSON.parse(text);
130
+ } catch (cause) {
131
+ if (response.ok) {
132
+ throw new BridgeError("invalid_response", "account administration response was not valid JSON", { cause });
133
+ }
134
+ return {};
135
+ }
136
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
137
+ if (response.ok) throw new BridgeError("invalid_response", "account administration response was not a JSON object");
138
+ return {};
139
+ }
140
+ return payload;
141
+ }
142
+
143
+ async function cancelResponseBody(body) {
144
+ if (!body) return;
145
+ const reader = body.getReader();
146
+ try { await cancelReader(reader); } finally { reader.releaseLock(); }
147
+ }
148
+
149
+ async function cancelReader(reader) {
150
+ try { await reader.cancel("response size limit reached"); } catch { /* cleanup only */ }
151
+ }
152
+
153
+ function concatBytes(chunks, bytes) {
154
+ const output = new Uint8Array(bytes);
155
+ let offset = 0;
156
+ for (const chunk of chunks) {
157
+ output.set(chunk, offset);
158
+ offset += chunk.byteLength;
159
+ }
160
+ return output;
161
+ }
162
+
96
163
  export function accountAdminRequestHeaders({
97
164
  sessionIdentity,
98
165
  origin,
@@ -4,6 +4,7 @@ import { dirname, join, relative, sep } from "node:path";
4
4
  import { assertAllowedPath, MAX_SKILL_ROOTS } from "./agent-contract.mjs";
5
5
  import { sha256 } from "./agent-context-projection.mjs";
6
6
  import { readRegularUtf8 } from "./agent-text-file.mjs";
7
+ import { classifyOperationalError } from "./log.mjs";
7
8
 
8
9
  export const MAX_SKILL_ENTRY_BYTES = 512 * 1024;
9
10
  export const MAX_SKILL_RESULTS = 500;
@@ -99,10 +100,17 @@ export async function discoverLocalSkills(options) {
99
100
  if (entry.isDirectory()) {
100
101
  stack.push({ directory: child, depth: current.depth + 1 });
101
102
  } else if (entry.isSymbolicLink()) {
102
- const target = await realpath(child).catch(() => "");
103
- if (!target) continue;
104
- const targetInfo = await stat(target).catch(() => null);
105
- if (!targetInfo?.isDirectory()) continue;
103
+ let target;
104
+ try { target = await realpath(child); } catch (error) {
105
+ if (warnings.length < 100) warnings.push({ entrypoint: child, message: boundedMessage(error) });
106
+ continue;
107
+ }
108
+ let targetInfo;
109
+ try { targetInfo = await stat(target); } catch (error) {
110
+ if (warnings.length < 100) warnings.push({ entrypoint: child, message: boundedMessage(error) });
111
+ continue;
112
+ }
113
+ if (!targetInfo.isDirectory()) continue;
106
114
  assertAllowedPath(target, options.workspace, options.unrestricted, "skill symlink target");
107
115
  stack.push({ directory: target, depth: current.depth + 1 });
108
116
  }
@@ -214,8 +222,11 @@ async function summarizeSkill(entrypoint, sourceRoot) {
214
222
 
215
223
  /** @param {unknown} error */
216
224
  function boundedMessage(error) {
225
+ if (error !== null && typeof error === "object" && "code" in error && error.code) {
226
+ return `local skill access failed (${classifyOperationalError(error)})`;
227
+ }
217
228
  const message = error instanceof Error ? error.message : String(error || "invalid local skill");
218
- return message.replace(/[\r\n]+/g, " ").slice(0, 1000);
229
+ return message.replace(/[\r\n\u0000-\u001f\u007f]+/g, " ").slice(0, 1000);
219
230
  }
220
231
 
221
232
  /** @param {string} value */