machine-bridge-mcp 3.0.0-beta.12 → 3.0.0-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0-beta.16 - 2026-07-25
4
+
5
+ ### Pending-call recovery and verified handover
6
+
7
+ - Separate the upstream MCP host/connector shard-mapper incident from Machine Bridge evidence. The exact temporary-keyspace error never appeared in Worker or daemon diagnostics and did not increment Worker server-error counters, so it is documented as an external boundary failure with unknown platform ownership rather than misclassified as a local daemon, OAuth, Git, or Cloudflare defect.
8
+ - Close the Machine Bridge failure-amplification path discovered after recovery. Pending calls now retain monotonic operation and reconnect deadlines, schedule the earliest deadline through the Durable Object alarm, and run a compensating overdue sweep on every HTTP/WebSocket event. In-memory timers remain the fast path; a transient alarm-storage error is observable without converting already-dispatched work into a false terminal failure.
9
+ - Make verified same-instance daemon handover atomic with respect to in-flight calls. Both attached and detached records move to the replacement before the incumbent closes, the complete `resume_calls` set is sent, remaining operation timeout is preserved, and failed replacement acknowledgement restores ownership to a still-open incumbent.
10
+ - Add deterministic disabled-timer deadline tests, direct runtime-alarm scheduling/failure tests, and a real Wrangler/workerd race regression that connects a same-instance replacement while the incumbent still owns an active call. The call remains active rather than detached and completes through the verified replacement.
11
+ - Use null-prototype dictionaries for managed-job `env` and `env_resources`, so valid variable names such as `__proto__`, `constructor`, `toString`, and `valueOf` remain ordinary own data instead of mutating JavaScript object prototypes. Add behavior coverage without weakening duplicate-variable rejection.
12
+ - Correct architecture and operations documentation that still described Durable Object `waitUntil` ownership or direct rejection during socket replacement. Document the three deadline enforcement paths, stale-pending diagnosis, host/connector internal-storage error triage, and the exact test evidence.
13
+
14
+ ## 3.0.0-beta.15 - 2026-07-25
15
+
16
+ ### Event-driven streamed-call settlement
17
+
18
+ - Block `3.0.0-beta.14` after exact owner-machine activation and repeated production verification. Version, launchd identity, private candidate runtime, status, doctor, sequence-zero delivery, session isolation, disconnect recovery, and terminal acknowledgement all converged, but a concurrent `server_info` still timed out while the original SSE remained open; session-scoped cancellation therefore could not enter. Beta.14 has no acceptance record and must not be pushed, published, or promoted.
19
+ - Remove the last cross-event terminal Promise from streamed `tools/call` initiation. `BridgeRoom` now commits the recovery record, registers an event-settled pending call, sends the daemon envelope, and returns the descriptor without retaining a Promise for the daemon result. The later daemon WebSocket `tool_result`, explicit cancellation request, timeout, send failure, or reconnect-grace expiry owns terminal settlement and persistence.
20
+ - Preserve ordinary JSON-only calls with the existing Promise-based request path while adding a separate event settlement mode to `PendingCallRegistry`. Same-instance daemon reconnect still detaches and rebinds both modes; terminal settlement removes request keys, closes observability, and writes exactly one resumable JSON-RPC result.
21
+ - Replace the resumption store's live Promise map with an active-stream set plus a bounded transient terminal map used only when persistence fails. A pending persisted record without matching active state still produces the existing restart-ambiguity error instead of inventing completion.
22
+ - Add deterministic event-lifecycle regressions that prove stream initiation returns before any terminal event, then exercise success, daemon rejection, cancellation, timeout, send failure, result transformation, persistence failure, and same-instance reconnect. Architecture checks forbid `dispatchJsonRpc` terminal Promises, `resumption.attach`, and Durable Object `waitUntil` from returning to the stream initiation path.
23
+
24
+ ## 3.0.0-beta.14 - 2026-07-25
25
+
26
+ ### Concurrent MCP control during streamed delivery
27
+
28
+ - Block `3.0.0-beta.13` after exact owner-machine activation. Live recovery itself succeeded—sequence zero, session isolation, disconnect recovery, and one-time terminal acknowledgement all worked—but a production Cloudflare Durable Object that directly owned the open SSE response did not accept concurrent `server_info` or `notifications/cancelled` requests until that stream ended. Beta.13 has no acceptance record and must not be published or promoted.
29
+ - Move public SSE ownership to the stateless outer Worker. `BridgeRoom` now authenticates and binds the request, commits the resumable record, dispatches local work, and returns only a bounded internal descriptor. The outer Worker emits sequence zero/keepalives/sequence one and polls the Durable Object with short immediate requests for pending or terminal state, so no Durable Object request remains open while a client stream is active.
30
+ - Strip all internal stream-control headers from public requests before forwarding, then add them only on the trusted service-binding path. OAuth/DPoP, signed MCP-session, token/session replay isolation, explicit cancellation, bounded persistence, and acknowledged-terminal suppression remain enforced by `BridgeRoom`.
31
+ - Extend real Wrangler integration to hold an SSE call open while a concurrent `server_info` succeeds and a session-scoped cancellation reaches the exact daemon call. Transport tests now parse complete SSE events rather than assuming one network chunk equals one event.
32
+
33
+ ## 3.0.0-beta.13 - 2026-07-25
34
+
35
+ ### Resumable MCP result delivery and outage closure
36
+
37
+ - Complete the Streamable HTTP recovery contract. Every streamed `tools/call` now emits a sequence-zero SSE event identifier before local execution can complete, persists a token- and MCP-session-bound delivery record, emits the terminal result as sequence one, and accepts authenticated `GET /mcp` recovery with `Last-Event-ID`. Reusing sequence one returns an empty completed stream instead of delivering the terminal response twice.
38
+ - Separate execution continuity from result-delivery continuity. An HTTP/SSE disconnect does not cancel the daemon call; only session-scoped `notifications/cancelled` does. A new POST always starts a new request, while GET only resumes a previously issued stream identifier, preventing retry semantics from being conflated with replay.
39
+ - Bound Durable Object recovery state to 64 streams, two minutes, and 1.5 MiB per persisted terminal message. A compact metadata index avoids scanning stored result bodies. Result records carry SHA-256 integrity metadata, are isolated by OAuth token and MCP session, evict expired or oldest completed entries first, and return explicit errors for oversized replay data, lost in-memory execution after Worker restart, or stored-result corruption.
40
+ - Preserve online delivery when persistence fails transiently, fail before side effects when a new recovery record cannot be admitted, and allow browser DPoP/resumption preflights by advertising both `DPoP` and `Last-Event-ID` in CORS.
41
+ - Promote the recovery summary for an already-warned relay outage to `warn`, while brief self-healing interruptions remain debug-only. Default background-service logs now contain both outage start and recovery closure without exposing raw close reasons.
42
+ - Add direct store fault/tamper/capacity tests, SSE framing tests, and live Wrangler integration that disconnects after sequence zero, completes the daemon call, rejects another session, recovers through GET, and proves the acknowledged terminal event is not duplicated.
43
+ - Refresh the locked development-only `brace-expansion` transitive dependency from 5.0.7 to 5.0.8 after the mandatory pre-candidate registry audit reported GHSA-mh99-v99m-4gvg. Both complete and production-only audits must be clean before beta.13 candidate preparation.
44
+ - Integrate Dependabot PR #56 into the complete beta.13 candidate rather than merging its incomplete two-file update. Wrangler advances to 4.114.0, Miniflare/workerd to the 2026-07-22 build, the exact `workerd@1.20260722.1` postinstall approval is reviewed and updated, and the existing patched `sharp@0.35.3` override remains authoritative.
45
+
3
46
  ## 3.0.0-beta.12 - 2026-07-23
4
47
 
5
48
  ### ChatGPT Streamable HTTP task continuity
package/SECURITY.md CHANGED
@@ -257,3 +257,8 @@ No logging policy prevents data from being returned to an authorized client that
257
257
  Machine Bridge cannot make arbitrary local executables safe, identify all sensitive data, guarantee cleanup across every power/storage/security failure, override MCP-host or endpoint-security policy, neutralize prompt injection, protect against root or a fully compromised same-user account, or manufacture production signing and governance controls.
258
258
 
259
259
  See [docs/AUDIT.md](docs/AUDIT.md) for historical findings and residual limitations.
260
+ ## Resumable Streamable HTTP delivery
261
+
262
+ SSE event identifiers are cursors, not bearer credentials. Recovery requires a valid OAuth Bearer/DPoP request and the original signed `MCP-Session-Id`; a cursor from another token or session is reported as not found. `GET /mcp` only replays an existing stream, while POST always represents new work.
263
+
264
+ The Worker stores a bounded terminal response for two minutes to bridge transport loss. Records are limited to 64 streams and 1.5 MiB each and include SHA-256 integrity metadata. This protects against accidental storage corruption, not compromise of the Worker account or Durable Object. A pending record found after Worker restart is reported as an ambiguous execution outcome because the local side effect may already have occurred; clients must reconcile before retrying non-idempotent tools.
@@ -30,6 +30,6 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "3.0.0-beta.12",
33
+ "version_name": "3.0.0-beta.16",
34
34
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
35
35
  }
@@ -42,7 +42,7 @@ A canonical workspace receives an independent profile, Worker name, secret set,
42
42
  - `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;
43
43
  - managed jobs, local resources, application automation, and browser automation remain separate managers.
44
44
 
45
- 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.
45
+ 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 the shared two-minute reconnect grace; 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.
46
46
 
47
47
  `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.
48
48
 
@@ -111,11 +111,12 @@ All requests for a deployed Worker route to one named Durable Object. It owns:
111
111
  - OAuth clients, authorization codes, hashed access-token records, an independently versioned hashed refresh-token store, and throttling metadata;
112
112
  - one active end-to-end-verified daemon WebSocket plus bounded candidate and probing sockets;
113
113
  - policy/tool metadata attached to the active socket;
114
- - a bounded in-memory map of pending daemon calls.
114
+ - a bounded in-memory map of pending daemon calls, with monotonic operation/reconnect deadlines projected onto Durable Object alarms and rechecked at every event boundary;
115
+ - bounded resumable MCP delivery metadata and terminal responses for recently disconnected SSE clients.
115
116
 
116
- `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.
117
+ `BridgeRoom` owns Durable Object routing, MCP authorization/dispatch, daemon WebSocket lifecycle, pending relay-call composition, cancellation, and resumable state. `mcp-stream-proxy.ts` owns the outer-Worker transport adapter: it strips public internal-control headers, obtains a bounded authenticated descriptor, creates the client-facing SSE stream, and polls terminal state through short immediate service-binding requests. `mcp-access.ts` owns shared Bearer/DPoP authorization for POST and recovery GET. `mcp-resumption-http.ts` owns recovery routing and signed session/protocol binding and returns descriptors rather than a long-lived response. `mcp-resumption.ts` owns stream admission, transaction ordering, immediate pending/terminal polls, expiry, replay, and lifecycle state; `mcp-resumption-records.ts` owns the compact metadata index, terminal-message bounds, serialization, and SHA-256 integrity metadata. `mcp-stream.ts` owns SSE sequence-zero/sequence-one framing and heartbeats. `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. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime and bundled by Wrangler.
117
118
 
118
- 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.
119
+ The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates explicit cancellation by access-token hash, signed MCP session, and JSON-RPC ID, and formats text/structured/image results. An HTTP/SSE disconnect is transport disposal, not MCP cancellation. For streamed daemon tools, `BridgeRoom` commits a recovery record, registers an event-settled pending call, sends the daemon envelope, and immediately returns an internal descriptor without retaining a terminal Promise. The later WebSocket result, explicit cancellation, timeout, send failure, or reconnect-grace expiry persists the terminal JSON-RPC envelope. JSON-only calls retain the ordinary Promise path. The outer Worker sends `stream:0`, heartbeats, and `stream:1`; bounded short polls return pending or terminal state immediately. Authenticated `GET /mcp` with `Last-Event-ID` resumes only the original OAuth-token/MCP-session stream; POST always creates new work. Public requests cannot select internal descriptor/poll modes because the outer boundary removes those headers before forwarding. At most 64 records and 1.5 MiB of terminal JSON per record are retained for two minutes. The recovery store tracks active stream identifiers, not live Promises; a transient terminal map is used only when persistence fails. If the Durable Object restarts with a pending record but no active owner, recovery reports that side effects may have occurred and requires reconciliation before retry. It has no local filesystem or process API.
119
120
 
120
121
 
121
122
  ### Daemon device authentication
@@ -171,7 +172,7 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
171
172
  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.
172
173
  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.
173
174
  9. `tools/list` is derived only from the active end-to-end-verified daemon; without one, only `server_info` is advertised.
174
- 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, that daemon process's ephemeral instance identifier, and the authenticated client request key. When the client accepts `text/event-stream`, the Worker immediately returns an SSE priming frame, emits bounded keepalive comments while the call runs, and retains the dispatch with Durable Object `waitUntil`; JSON-only clients retain the single terminal response.
175
+ 10. `tools/call` receives a random relay call ID and is bound to the current daemon socket, that daemon process's ephemeral instance identifier, and the authenticated client request key. When the client accepts `text/event-stream`, `BridgeRoom` commits recovery state, registers an event-settled pending call, sends the daemon envelope, and returns a bounded descriptor immediately; the outer Worker owns the SSE priming frame, keepalives, and terminal polling. No unresolved terminal Promise or Durable Object `waitUntil` owns the dispatch. JSON-only clients retain the single terminal response.
175
176
  11. The runtime validates policy and arguments, executes the tool, and returns a bounded result. Closing or losing the HTTP response stream only makes that stream unwritable; it is not an MCP cancellation and does not remove the pending request.
176
177
  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 two minutes 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 shared interval. The Worker pauses the record's remaining normal deadline while detached and resumes it after same-instance rebinding, so connected calls are not granted an unconditional recovery extension.
177
178
  13. Only a matching session-scoped `notifications/cancelled` request removes the pending indexes and sends best-effort cancellation to a connected daemon. Local completion that races with explicit 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 explicitly cancelled while disconnected therefore cannot be revived by a fast reconnect.
@@ -254,7 +255,7 @@ Reconnect uses bounded exponential backoff with jitter. Brief self-healing inter
254
255
 
255
256
  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.
256
257
 
257
- 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 the shared two-minute relay contract 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. The shared execution envelope remains independent of reconnect grace. Worker operation countdown is paused while detached and resumed on same-instance rebinding, so a healthy unresponsive daemon still fails on the normal deadline while a recoverable disconnected call is not expired by two competing timers. This does not make calls durable across daemon restart or machine failure; managed jobs remain the separate durable mechanism.
258
+ 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 the shared two-minute relay contract bounds recovery. During verified same-instance handover, the Worker transfers both already-detached calls and still-attached calls from the incumbent socket to the replacement before closing the incumbent; this prevents the asynchronous close event from creating a detached call after the only rebind pass. If replacement acknowledgement fails, ownership is restored to the still-open same-instance incumbent. Another process cannot inherit or resolve these calls. 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. The shared execution envelope remains independent of reconnect grace. Worker operation countdown is paused while detached or transferred and resumed with its remaining budget, so handover cannot reset the normal timeout. In-memory timers are only the fast path: the earliest monotonic pending deadline is also scheduled as a Durable Object alarm, and every HTTP or WebSocket event performs a compensating overdue scan. A transient alarm-storage failure is observable but does not turn an already-dispatched operation into a false terminal failure; the next event scan remains the bounded recovery path. This does not make calls durable across daemon restart or machine failure; managed jobs remain the separate durable mechanism.
258
259
 
259
260
  ## Persistence
260
261
 
package/docs/AUDIT.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Security and privacy audit notes
2
2
 
3
+ ## 2026-07-25 version 3.0.0-beta.16 pending-call recovery and boundary audit
4
+
5
+ The reported incident exposed two separate failure domains. The host/connector layer returned `No shard mapper found` for a temporary high-replication backfill keyspace and then rejected even minimal MCP calls. The exact text did not exist in Machine Bridge source, deployed Worker events, daemon logs, or local command output; Worker HTTP server-error counters remained unchanged. The original shard-routing failure therefore occurred before or outside the deployed Worker/daemon boundary. The evidence is insufficient to identify the owner of that upstream temporary store, so this audit does not misattribute it to Cloudflare, the local daemon, OAuth, or Git. The host path later recovered without credential rotation or state deletion.
6
+
7
+ Independent inspection after recovery found a real Machine Bridge amplification defect: two calls remained active and detached beyond the shared two-minute reconnect grace. Pending operation and reconnect expiry used only in-memory `setTimeout` callbacks. Event-driven streamed calls intentionally return from the initiating Durable Object event, so a timer alone was not a valid cross-event lifecycle guarantee. The Worker now records monotonic operation/reconnect deadlines, exposes the earliest remaining duration to one combined Durable Object alarm, and performs an overdue scan at every HTTP and WebSocket event boundary. Timer callbacks remain the low-latency path. Alarm-storage failure emits only a bounded coarse event and does not falsely fail work that was already dispatched; the next event scan is the compensating path.
8
+
9
+ A second race existed during verified daemon replacement. The previous implementation rebound only already-detached calls, then closed the incumbent socket. Calls still attached to that socket were detached later by its asynchronous close callback, after the only rebind pass, and could remain orphaned. Same-instance promotion now transfers both attached and detached calls to the candidate before closing the incumbent, sends the complete authoritative `resume_calls` set, preserves the remaining normal timeout, and restores ownership to a still-open incumbent if replacement acknowledgement fails. Different daemon instances remain unable to inherit calls.
10
+
11
+ The broader review found a separate prototype-sensitive input defect in managed-job environment maps. POSIX/Windows-compatible variable validation permits names such as `__proto__`, `constructor`, `toString`, and `valueOf`, but validation accumulated them into ordinary JavaScript objects. Assigning `__proto__` invoked the legacy prototype setter rather than creating an own environment entry. Both plain and resource-backed environment maps now use null-prototype dictionaries, with stage/inspection regression coverage for all prototype-shaped keys and the existing duplicate-variable denial.
12
+
13
+ Documentation had also drifted: the remote lifecycle still claimed streamed work used Durable Object `waitUntil`, despite beta.15 explicitly removing and architecture tests forbidding that design. Architecture, operations, testing, changelog, and this audit now describe event-settled dispatch, alarm-plus-sweep deadline enforcement, atomic same-instance handover, upstream-host diagnosis, and prototype-safe environment maps. Complete and production dependency audits report zero vulnerabilities; privacy history, critical-module coverage, Worker dry-run, deterministic lifecycle tests, managed-job integration, type checks, lint, and real Wrangler OAuth/MCP integration pass before candidate preparation.
14
+
15
+ These source changes do not claim to repair the upstream shard mapper or prove its platform owner. They prevent a transient external failure or socket replacement from leaving permanent pending-call occupancy inside Machine Bridge. No Worker deployment, daemon/service replacement, global installation, credential rotation, push, tag, npm publication, or GitHub Release is performed by the source audit itself. Exact beta.16 candidate activation and owner-machine verification remain mandatory.
16
+
17
+ ## 2026-07-25 version 3.0.0-beta.15 event-lifecycle audit
18
+
19
+ The exact beta.14 candidate was activated by the repository owner. The Worker, launchd daemon, private runtime path, activation record, candidate checksum, `status`, and `doctor` all converged on `3.0.0-beta.14`; the globally installed beta.12 package remained the explicit rollback baseline. A random temporary owner account and OAuth client then exercised the production protocol and were removed after the run. Sequence zero, token/session isolation, disconnect recovery, terminal sequence one, and empty acknowledgement replay all passed.
20
+
21
+ The acceptance-blocking beta.14 defect was narrower than beta.13 but still real. The outer Worker owned the public SSE and used short polls, yet the initiating Durable Object fetch created `dispatchJsonRpc(...).then(...)`, attached that unresolved terminal Promise to recovery state, and retained it through the event lifecycle. In production, later requests to the same object—including concurrent `server_info` and the authoritative `notifications/cancelled` request—remained queued until the daemon operation ended. Local Wrangler integration allowed an interleaving that production did not, so beta.14 has no acceptance and is not a valid publication candidate.
22
+
23
+ Beta.15 makes streamed daemon execution event-driven. The initiating fetch commits the pending recovery row, registers a pending call whose settlement is a callback rather than a Promise, sends `tool_call`, and immediately returns the internal stream descriptor. A later daemon WebSocket result event resolves or rejects that record and persists the terminal JSON-RPC envelope. Explicit cancellation, timeout, send failure, no-instance socket failure, and reconnect-grace expiry use the same settlement path. Same-instance reconnect only detaches and rebinds the record; it does not manufacture a terminal result. JSON-only clients continue through the ordinary Promise-based call path.
24
+
25
+ The recovery store no longer contains live terminal Promises. An active-stream set distinguishes work owned by the current Durable Object instance; a transient terminal map preserves an already produced message only when Durable Object storage fails. A pending stored record with no active owner is still converted to the bounded restart-ambiguity result. Static architecture checks reject reintroduction of a stream-initiation `dispatchJsonRpc` Promise, `resumption.attach`, Durable Object `waitUntil`, or a Promise-valued recovery map. Direct tests prove that registration returns without settlement and that later success, rejection, cancellation, timeout, send failure, transformation failure, persistence failure, and same-instance reconnect each close the stream exactly once.
26
+
27
+ This source correction does not accept beta.15, deploy it, replace the login daemon, push Git history, publish npm, create a tag, or create a GitHub Release. A new exact candidate and repeated owner-machine production test are mandatory.
28
+
29
+ ## 2026-07-25 version 3.0.0-beta.14 production SSE-concurrency audit
30
+
31
+ The exact beta.13 tarball was activated through the owner command and converged successfully: the same-name Worker, one launchd daemon, private candidate runtime, service definition, status, and doctor all reported `3.0.0-beta.13`. A temporary random owner account and OAuth client were then used only for bounded live protocol verification and were deleted afterward. Sequence-zero delivery, token/session isolation, disconnect followed by authenticated `GET /mcp`, terminal sequence one, and empty acknowledgement replay all succeeded.
32
+
33
+ The same live run exposed a release-blocking production scheduling defect that the local Wrangler integration had not represented. While `BridgeRoom` directly returned an open SSE response, later requests routed to the same Durable Object—including `server_info` and the authoritative session-scoped `notifications/cancelled` notification—did not enter until the stream ended. The daemon call eventually terminated through its own boundary, but Worker observability did not record a successful cancellation. Beta.13 therefore has no acceptance record, is not pushed or published, and is explicitly blocked.
34
+
35
+ Beta.14 separates client transport ownership from durable state ownership. The outer stateless Worker creates the public SSE stream. `BridgeRoom` performs OAuth/DPoP authorization, signed MCP-session validation, stream admission, daemon dispatch, explicit cancellation, and terminal persistence, then returns a small internal descriptor. The outer Worker uses short service-binding polls: pending state returns immediately with HTTP 202; terminal state returns buffered JSON; missing or expired state fails closed. No internal poll or descriptor request remains open in the Durable Object. Publicly supplied internal-control headers are stripped before every service-binding forward, so callers cannot select the unauthenticated internal poll path.
36
+
37
+ The real Wrangler regression keeps the original SSE response open, confirms a concurrent `server_info` sees the pending call, sends `notifications/cancelled`, observes the matching daemon `cancel_call`, and receives a cancelled terminal result on the original stream. Existing disconnect/recovery, wrong-session rejection, sequence-one acknowledgement, CORS, persistence faults, capacity, integrity, and oversized-message tests remain in force. This correction requires a new exact beta.14 candidate, owner activation, and repeated live verification; beta.13 activation evidence cannot be reused.
38
+
39
+ ## 2026-07-25 version 3.0.0-beta.13 resumable-delivery and self-correction audit
40
+
41
+ The exercise began from clean released `main` at `ff3e2de` (`v3.0.0-beta.12`). Fast gates passed before modification, so the review concentrated on transport state, Durable Object persistence, protocol delivery, and live outage evidence rather than ordinary syntax defects.
42
+
43
+ Beta.12 correctly stopped treating an HTTP/SSE disconnect as cancellation, but that repaired only execution continuity. Its initial SSE frame had no event cursor, `/mcp` rejected GET, and a terminal response could not be retrieved after the response transport disappeared. Beta.13 implements the Streamable HTTP recovery split: POST commits a pending delivery record before dispatch and emits sequence zero; authenticated GET plus `Last-Event-ID` resumes only the original OAuth-token/MCP-session stream; sequence one is terminal and is not replayed again after acknowledgement. Explicit `notifications/cancelled` remains the sole client cancellation signal.
44
+
45
+ The first implementation pushed `BridgeRoom` beyond its architecture line budget. Rather than increasing the limit, authorization, recovery HTTP adaptation, lifecycle persistence, record encoding/integrity, and SSE framing were extracted into focused modules. A later review found that a prefix list would load every stored result value while admitting a new stream, potentially amplifying 64 retained 1.5-MiB results into a large allocation. The design was replaced with a small transactional metadata index and separate value records. Terminal messages carry SHA-256 integrity metadata, result retention is two minutes, capacity is 64, and oversized online results persist only a bounded replay error.
46
+
47
+ The review itself encountered a real beta.12 relay outage while a compound remote command attempted both a large source overwrite and tests. The daemon later reconnected with the same launchd PID and run count; the write had not completed, while earlier files remained intact. This exposed a flaw in the development method: a returned test result is not evidence that the preceding remote write persisted when the enclosing transport failed. Engineering rules now require independent anchor/Git verification after remote mutations. The same incident showed that warn-level service logs recorded an outage start but recovery only at info; recovery for an already-warned outage is now warn-level, while brief self-healing interruptions remain debug-only.
48
+
49
+ The mandatory candidate preflight then found a newly disclosed high-severity denial-of-service advisory in the development-only chain `eslint 10.7.0 -> minimatch 10.2.5 -> brace-expansion 5.0.7`. The compatible `brace-expansion 5.0.8` release satisfies the existing `^5.0.5` range, so the correction changes only the transitive lock entry and installed development tree. The candidate remains blocked until both complete and production-only registry audits return zero high-severity findings.
50
+
51
+ Dependabot PR #56 proposed Wrangler 4.114.0 but intentionally failed the repository gates because it did not update the exact `workerd` lifecycle-script approval and changed package bytes without a new prerelease acceptance. Beta.13 absorbs the update as one reviewed unit: Wrangler 4.114.0, Miniflare 4.20260722.0, workerd 1.20260722.1, and the corresponding platform binaries advance together; `sharp` remains forced to audited 0.35.3. An architecture assertion now derives the resolved workerd version from the lockfile and requires exactly one matching allowlist entry, so a future dependency-only PR cannot silently repeat this partial-update failure. The superseded Dependabot PR is closed only after this integrated change passes the complete local gate.
52
+
53
+ Fault-directed tests cover token/session isolation, live and persisted replay, restart ambiguity, persistence failure, capacity/expiry, oversized results, integrity tampering, SSE sequence semantics, CORS, and a real Wrangler disconnect/resume path. Static architecture checks prevent full-value scans, enforce module budgets and dependency direction, and require the storage/isolation anchors. This source audit does not deploy a Worker, replace the running beta.12 daemon, activate a candidate, publish npm, create a tag, or create a GitHub Release. Exact candidate preparation and owner-authorized activation remain separate release stages.
54
+
3
55
  ## 2026-07-23 version 3.0.0-beta.12 ChatGPT task-continuity audit
4
56
 
5
57
  The reported symptom was not evidence that the model simply chose to stop. Live beta.11 inspection found a healthy single launchd daemon and matching Worker, but the Worker could retain an orphaned active call after the ChatGPT turn had already ended. The service log independently recorded repeated relay outages with the same daemon process still alive, including an incident where the existing thirty-second recovery deadline cancelled an active call. A routine source search also lost its result through the same path during this audit. These observations separate process health from request continuity: the daemon can keep running while either the ChatGPT-to-Worker HTTP request or the Worker-to-daemon WebSocket disappears.
@@ -19,6 +19,9 @@ This document records project-wide decisions that must survive individual fixes,
19
19
  13. **The public protocol contract is current-only.** Shared metadata advertises only the current MCP protocol version. Compatibility code for obsolete protocol dates, lock formats, or state schemas is not retained in the final runtime; upgrade safety comes from explicit version negotiation, fail-closed state validation, and bounded operator convergence.
20
20
  14. **Security analysis is a failing gate.** CodeQL or Scorecard execution alone is not success. Generated SARIF must contain no unaccepted result, and missing rule metadata fails closed rather than being interpreted as non-security. An intentional or externally constrained finding requires an exact rule/path record with a substantive rationale and an expiry date.
21
21
  15. **Ambiguous health is not permission to repeat a remote write.** A successful Wrangler deployment is recorded before secondary health verification. Timeout, proxy, TLS, network, and temporary service failures preserve the deployment fingerprint and fail for diagnosis; only bounded evidence of a stale identity/version permits automatic same-name redeployment. Changing the Worker name is an explicit remote-resource transition, not a retry strategy.
22
+ 16. **Execution continuity and delivery continuity are separate proof obligations.** Keeping work alive after a client transport closes is insufficient unless the same authenticated principal can recover a terminal result or a durable handle. Fresh requests and replay endpoints must remain separate so recovery cannot accidentally duplicate a non-idempotent operation.
23
+ 17. **Remote compound commands are not persistence evidence.** When a remote edit and a long test share one relay call, a transport interruption can obscure whether the edit completed. High-impact writes must be followed by an independent read of stable anchors or a Git diff before tests and conclusions rely on them.
24
+ 18. **Durable state owners do not retain cross-event terminal Promises.** A Durable Object that must accept cancellation, status, or recovery requests cannot retain the public SSE response, an internal request waiting for completion, or an unresolved Promise owned by the initiating fetch event. The outer Worker owns streaming; streamed daemon calls are registered and returned immediately, then settled by later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events. Descriptor and poll requests remain short, authenticated at admission, and unreachable through caller-supplied internal headers.
22
25
 
23
26
  A proposed change that conflicts with an invariant requires an explicit owner decision and corresponding documentation update. It must not be hidden inside an unrelated refactor.
24
27
 
@@ -64,6 +67,8 @@ Rules:
64
67
  - Externally controlled string keys must not use prototype-chain membership or truthiness on ordinary objects. Use `Map`, `Set`, `Object.hasOwn`, or null-prototype records for command dispatch, enums, ACLs, form fields, registries, and other key-addressed contracts.
65
68
  - Repository text must not contain invisible ASCII controls other than tab, CR, and LF; architecture tests enforce this even when JavaScript syntax remains valid.
66
69
  - Persistent mutations use owner-only files, bounded no-follow reads, flushed atomic replacement, and integrity checks appropriate to the data.
70
+ - A transport-surviving operation exposes either authenticated replay or a durable inspection handle. A response stream alone is not durable delivery.
71
+ - After a remote mutation, inspect the persisted file/Git anchors in a separate call before treating a subsequent test as evidence for that exact change.
67
72
  - Exclusive locks use the shared complete-before-visible hard-link claim. Reclamation requires process identity plus a matching file snapshot/token; do not unlink a path merely because an earlier read looked stale.
68
73
  - Service providers normalize success/failure to one result contract. Definition removal follows the shared platform-stop → verified-daemon-stop → remove order.
69
74
  - Retry is limited to classified transient failures. Authentication, authorization, validation, integrity, and policy errors fail immediately.
@@ -51,17 +51,23 @@ 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`, `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. 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.
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 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.
55
55
 
56
- For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, remote tool calls return an immediate SSE frame and a keepalive comment every ten seconds until the terminal JSON-RPC result. This keeps a long call from presenting as an idle HTTP response. If the client or an intermediary nevertheless closes that stream, Machine Bridge keeps the bounded operation alive; only `notifications/cancelled` carries cancellation semantics. A completed result cannot be delivered back onto a stream that no longer exists, so the host may still need a later model turn to inspect resulting workspace state, but the bridge no longer kills the local operation merely because the response transport disappeared.
56
+ 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. Short pending/terminal polls therefore coexist with concurrent `server_info`, recovery, and session-scoped `notifications/cancelled` requests while SSE remains open. 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.
57
57
 
58
58
  `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.
59
59
 
60
+ ### MCP host or connector internal-storage errors
61
+
62
+ An error naming an internal shard mapper, temporary keyspace, backfill store, or connector database is not automatically a Machine Bridge Worker or daemon error. During the beta.15 incident, the exact error text was absent from repository source, Worker events, daemon logs, and local process output; Worker HTTP `server_error` counters also did not increase, while the host temporarily failed even `server_info`. That evidence places the original failure before or outside the deployed Worker/daemon boundary, but it does not identify which upstream platform component owned the temporary store. Do not rotate OAuth/device credentials, delete local state, or restart a healthy daemon solely because of such a message.
63
+
64
+ After the host path recovers, run `server_info`, `machine-mcp doctor`, and `machine-mcp service status`. Compare Worker `requests.server_error`, pending-call age, ready socket count, daemon PID/start time, and local logs. If the upstream text never appears locally and Worker server errors remain unchanged, report the host/connector incident separately. If pending calls remain older than their operation or reconnect deadline, that is a Machine Bridge lifecycle issue and should be investigated independently rather than attributed to the upstream shard error.
65
+
60
66
  ### Relay interruption messages
61
67
 
62
68
  A reconnect warning is evidence of a transport outage, not proof that the daemon process exited. Compare daemon PID/process start, `connected_at`, `last_seen_at`, `runtime.relay.last_disconnected_at`, close category/code, and outage count. A system VPN/TUN may remain shown as connected while its internal route is unavailable; Machine Bridge reports that route only as `system-network-stack` with application-proxy scope. The reconnect schedule now tops out at fifteen seconds.
63
69
 
64
- A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one readable summary. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. Worker-side hello and end-to-end readiness timeouts remain retryable. Authentication is not reported as usable service readiness until a session-bound probe result returns.
70
+ A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one warn-level closure summary so the default background-service log contains both ends of the incident. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. Worker-side hello and end-to-end readiness timeouts remain retryable. Authentication is not reported as usable service readiness until a session-bound probe result returns.
65
71
 
66
72
  Use `--verbose` only when close codes, close reasons, heartbeat timeouts, and retry delays are needed for diagnosis. A close code of 1006 means the transport ended without a normal close handshake; it does not by itself identify the cause.
67
73
 
package/docs/PRIVACY.md CHANGED
@@ -57,3 +57,8 @@ The scanner is heuristic. It cannot identify every personal or organizational na
57
57
  ## Incident response
58
58
 
59
59
  For an accidental publication, remove the value from the current tree and release artifacts, determine whether it is merely identifying metadata or an active credential, and rotate/revoke any credential immediately. Public Git and npm history are immutable in ordinary workflows: replacing the current file does not erase old commits or a published package. A coordinated history rewrite, cache invalidation request, or replacement release may be appropriate, but those actions are disruptive and require an explicit repository-owner decision.
60
+ ## Transient resumable result storage
61
+
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
+
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.
package/docs/TESTING.md CHANGED
@@ -155,3 +155,8 @@ The stdio integration test also sends an oversized line, verifies bounded reject
155
155
  ## Architecture and documentation regression checks
156
156
 
157
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.
158
+ ## Resumable MCP delivery coverage
159
+
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.
161
+
162
+ `npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/poll adaptation, sequence-zero/sequence-one framing, poll-error closure, 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`.
package/docs/UPGRADING.md CHANGED
@@ -120,3 +120,14 @@ A rollback must restore together:
120
120
  - the prior browser extension.
121
121
 
122
122
  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.
123
+ ## Version 3.0.0-beta.15
124
+
125
+ 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.
126
+
127
+ ## Blocked version 3.0.0-beta.14
128
+
129
+ Do not activate, accept, publish, or promote beta.14. It moved public SSE to the outer Worker but retained an unresolved terminal Promise in the initiating Durable Object event. Exact owner-machine verification showed that concurrent status and cancellation requests still did not enter while SSE remained open. Beta.15 removes that Promise and requires a new activation and acceptance cycle.
130
+
131
+ ## Blocked version 3.0.0-beta.13
132
+
133
+ Do not activate, accept, publish, or promote beta.13. Exact owner-machine verification found that recovery worked after disconnect, but an SSE response directly owned by the production Durable Object prevented concurrent control requests—including explicit MCP cancellation—from entering until the stream ended. Beta.14 moves the public stream outside the Durable Object and requires a new activation and acceptance cycle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "3.0.0-beta.12",
3
+ "version": "3.0.0-beta.16",
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",
@@ -163,12 +163,13 @@
163
163
  "device-key-id-compatibility:test": "node tests/device-key-id-compatibility-test.mjs",
164
164
  "candidate-runtime-store:test": "node tests/candidate-runtime-store-test.mjs",
165
165
  "service-restart:test": "node tests/service-restart-handoff-test.mjs",
166
- "browser-identity:test": "node tests/browser-extension-identity-test.mjs"
166
+ "browser-identity:test": "node tests/browser-extension-identity-test.mjs",
167
+ "mcp-resumption:test": "node tests/mcp-resumption-test.mjs"
167
168
  },
168
169
  "dependencies": {
169
170
  "https-proxy-agent": "9.1.0",
170
171
  "proxy-from-env": "2.1.0",
171
- "wrangler": "4.113.0",
172
+ "wrangler": "4.114.0",
172
173
  "ws": "8.21.1"
173
174
  },
174
175
  "devDependencies": {
@@ -208,7 +209,7 @@
208
209
  "esbuild@0.28.1": true,
209
210
  "fsevents": false,
210
211
  "sharp@0.35.3": true,
211
- "workerd@1.20260721.1": true
212
+ "workerd@1.20260722.1": true
212
213
  },
213
214
  "packageManager": "npm@12.0.1",
214
215
  "devEngines": {
@@ -50,6 +50,7 @@ export const FAST_CHECK_TASKS = Object.freeze([
50
50
  "lifecycle:test",
51
51
  "logging-structure:test",
52
52
  "worker-runtime-infrastructure:test",
53
+ "mcp-resumption:test",
53
54
  "lint:test",
54
55
  "lint",
55
56
  "typecheck",
@@ -11,6 +11,7 @@ const tests = [
11
11
  "tests/runtime-infrastructure-test.mjs",
12
12
  "tests/runtime-boundaries-test.mjs",
13
13
  "tests/worker-runtime-infrastructure-test.mjs",
14
+ "tests/mcp-resumption-test.mjs",
14
15
  "tests/worker-oauth-controller-test.mjs",
15
16
  "tests/logging-structure-test.mjs",
16
17
  "tests/runtime-handler-matrix-test.mjs",
@@ -125,6 +126,12 @@ try {
125
126
  "src/worker/policy.ts": [100, 25],
126
127
  "src/worker/errors.ts": [100, 40],
127
128
  "src/worker/mcp-jsonrpc.ts": [95, 55],
129
+ "src/worker/mcp-resumption-config.ts": [100, 80],
130
+ "src/worker/mcp-resumption-records.ts": [90, 65],
131
+ "src/worker/mcp-stream-proxy.ts": [85, 55],
132
+ "src/worker/mcp-stream-dispatch.ts": [90, 60],
133
+ "src/worker/mcp-resumption.ts": [90, 70],
134
+ "src/worker/mcp-stream.ts": [90, 65],
128
135
  "src/worker/websocket-protocol.ts": [100, 50],
129
136
  };
130
137
  const failures = [];
@@ -125,7 +125,7 @@ function validateEnv(value, label) {
125
125
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
126
126
  const entries = Object.entries(value);
127
127
  if (entries.length > 64) throw new Error(`${label} has too many entries`);
128
- const out = {};
128
+ const out = Object.create(null);
129
129
  for (const [key, raw] of entries) {
130
130
  if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(key)) throw new Error(`${label} contains invalid variable name: ${key}`);
131
131
  out[key] = boundedString(raw, 16 * 1024, `${label}.${key}`);
@@ -138,7 +138,7 @@ function validateEnvResources(value, label) {
138
138
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
139
139
  const entries = Object.entries(value);
140
140
  if (entries.length > 32) throw new Error(`${label} has too many entries`);
141
- const out = {};
141
+ const out = Object.create(null);
142
142
  for (const [key, raw] of entries) {
143
143
  if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(key)) throw new Error(`${label} contains invalid variable name: ${key}`);
144
144
  out[key] = validateResourceName(raw);
@@ -230,7 +230,7 @@ export class RelayConnection {
230
230
  const outageMs = Math.max(0, this.now() - this.outageStartedAt);
231
231
  if (this.outageNoticeEmitted) {
232
232
  const recoveryFields = relayRecoveryFields(this, outageMs);
233
- this.logger.info?.(`remote relay connection restored after ${formatDuration(outageMs)} (${formatAttempts(this.outageAttempts)})`, recoveryFields);
233
+ this.logger.warn?.(`remote relay connection restored after ${formatDuration(outageMs)} (${formatAttempts(this.outageAttempts)})`, recoveryFields);
234
234
  this.logger.debug?.("remote relay outage recovery details", recoveryFields);
235
235
  } else {
236
236
  this.logger.debug?.("remote relay connection recovered after a brief interruption", {
@@ -3,5 +3,8 @@
3
3
  "streamHeartbeatMs": 10000,
4
4
  "maximumExecutionTimeoutMs": 600000,
5
5
  "toolCallOverheadMs": 5000,
6
- "maximumRelayToolTimeoutMs": 610000
6
+ "maximumRelayToolTimeoutMs": 610000,
7
+ "streamResumeRetentionMs": 120000,
8
+ "maximumResumableStreams": 64,
9
+ "maximumResumableMessageBytes": 1500000
7
10
  }
@@ -223,7 +223,7 @@ export function corsPreflight(request: Request, base: string, configured: string
223
223
  headers: {
224
224
  "access-control-allow-origin": origin,
225
225
  "access-control-allow-methods": "GET, POST, OPTIONS",
226
- "access-control-allow-headers": "authorization, content-type, mcp-protocol-version, mcp-session-id",
226
+ "access-control-allow-headers": "authorization, content-type, dpop, last-event-id, mcp-protocol-version, mcp-session-id",
227
227
  "access-control-max-age": "600",
228
228
  "cache-control": "no-store",
229
229
  "vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",