machine-bridge-mcp 3.0.0-beta.20 → 3.0.0-beta.22
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 +30 -0
- package/README.md +2 -0
- package/SECURITY.md +4 -4
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +14 -12
- package/docs/AUDIT.md +28 -0
- package/docs/ENGINEERING.md +1 -1
- package/docs/LOCAL_AUTHORIZATION.md +1 -1
- package/docs/LOGGING.md +4 -2
- package/docs/MULTI_ACCOUNT.md +1 -1
- package/docs/OPERATIONS.md +13 -5
- package/docs/OVERVIEW.md +1 -1
- package/docs/PRIVACY.md +1 -1
- package/docs/TESTING.md +9 -7
- package/docs/THREAT_MODEL.md +3 -2
- package/docs/UPGRADING.md +7 -1
- package/package.json +1 -1
- package/src/local/autostart-log-maintenance.mjs +36 -0
- package/src/local/cli.mjs +10 -0
- package/src/local/process-tree-ownership.mjs +17 -5
- package/src/local/process-tree.mjs +6 -2
- package/src/local/relay-call-recovery.mjs +40 -21
- package/src/local/relay-connection.mjs +24 -11
- package/src/local/runtime-activation.mjs +16 -0
- package/src/local/runtime.mjs +17 -2
- package/src/local/worker-deployment.mjs +16 -6
- package/src/shared/relay-contract.json +1 -0
- package/src/worker/daemon-socket-attachment.ts +52 -0
- package/src/worker/daemon-sockets.ts +17 -48
- package/src/worker/durable-stream-calls.ts +117 -0
- package/src/worker/durable-stream-result.ts +22 -0
- package/src/worker/index.ts +123 -93
- package/src/worker/mcp-pending-call-expiry.ts +20 -0
- package/src/worker/mcp-pending-call-inspection.ts +33 -0
- package/src/worker/mcp-pending-call-records.ts +58 -0
- package/src/worker/mcp-pending-call-storage.ts +18 -0
- package/src/worker/mcp-pending-call-store.ts +285 -0
- package/src/worker/mcp-resumption-index.ts +41 -0
- package/src/worker/mcp-resumption-records.ts +8 -2
- package/src/worker/mcp-resumption.ts +80 -87
- package/src/worker/mcp-stream-dispatch.ts +42 -54
- package/src/worker/pending-admission.ts +15 -0
- package/src/worker/pending-call-contract.ts +4 -7
- package/src/worker/pending-calls.ts +4 -10
- package/src/worker/runtime-alarm-storage.ts +35 -0
- package/src/worker/runtime-alarm.ts +29 -39
- package/src/worker/tool-timeout.ts +5 -1
- package/src/worker/websocket-protocol.ts +4 -0
- package/src/worker/worker-entry.ts +50 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.22 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
### ChatGPT call continuity and terminal delivery
|
|
6
|
+
|
|
7
|
+
- Add an explicit daemon-result acknowledgement. The local runtime retains every terminal result after WebSocket queueing, replays unacknowledged results after reconnect and on heartbeat, and removes them only after the Worker confirms that the generation-guarded terminal transaction committed. This closes the loss window between local `send()` acceptance and Durable Object persistence that could leave a completed local command as a ghost Worker call.
|
|
8
|
+
- Make durable settlement fail closed. A terminal-storage exception is observable and retryable instead of being reported as a completed call; stale connection generations remain unacknowledged, while duplicate results for an already terminal call are acknowledged idempotently so replay converges.
|
|
9
|
+
- Stop treating one tool deadline as proof that the complete daemon socket is dead. Tool timeout now cancels only that call; the independent 90-second daemon-liveness alarm remains the sole connection-invalidating authority.
|
|
10
|
+
- Bound remote foreground execution to 85 seconds plus five seconds of relay overhead, below the observed hosted-client request ceiling. The local process APIs retain their 600-second schema range, but work expected to exceed the interactive budget must use process sessions or managed jobs rather than one foreground ChatGPT call.
|
|
11
|
+
- Tail-trim background daemon logs every 15 minutes as well as before startup, reusing the existing owner-only, no-follow, single-link, schema-checked, UTF-8 line-safe maintenance path.
|
|
12
|
+
|
|
13
|
+
### Verification
|
|
14
|
+
|
|
15
|
+
- Add acknowledgement-loss/replay, persistent-terminal-write failure, stale generation, hosted-client deadline, runtime log-maintenance, and real Wrangler acknowledgement coverage. Type checking, lint, architecture, privacy, structured logging, security properties, SARIF, critical coverage, local self-test, Worker infrastructure, and Worker OAuth/MCP integration pass.
|
|
16
|
+
|
|
17
|
+
## 3.0.0-beta.21 - 2026-07-27
|
|
18
|
+
|
|
19
|
+
### Relay continuity and stable MCP catalog
|
|
20
|
+
|
|
21
|
+
- Keep `tools/list` stable for an authenticated account role instead of withdrawing almost every tool whenever the local relay is briefly unavailable. The Worker still fails every execution closed against the live daemon capability ceiling, and `server_info` now distinguishes the stable advertised catalog from the currently effective daemon/account intersection.
|
|
22
|
+
- Persist streamed daemon-call ownership, request correlation, operation deadlines, reconnect deadlines, and result transformation metadata in Durable Object storage. A hibernated or restarted Worker can recover the active call, a verified same-instance daemon can reclaim it, and a per-WebSocket connection generation prevents stale close events or delayed results from mutating the rebound call. Active-record expiry advances monotonically across repeated detach/rebind cycles instead of being capped by the original single-reconnect window.
|
|
23
|
+
- Make Durable Object alarms the sole deadline owner for persisted streamed calls while retaining the existing Promise/timer path for bounded JSON-only calls. A FIFO admission gate computes one combined 32-call ceiling across both paths. Cancellation, send failure, operation timeout, reconnect-grace expiry, daemon replacement, and successful completion all converge through one guarded terminal write.
|
|
24
|
+
- Classify Worker-requested transport and liveness invalidation as retryable relay recovery instead of a permanent protocol mismatch. The daemon now terminates only the affected socket, preserves ordinary disconnect cleanup, and reconnects automatically; Worker transient invalidation uses WebSocket 1012, while unknown protocol messages, authentication failure, and identity/version mismatch remain fatal. Close-only delivery is also classified from bounded reasons so loss of the preceding error frame cannot restart the daemon. A failed daemon `hello` send and a readiness-probe result lost to an ending relay generation are likewise transport races, not authentication or protocol violations.
|
|
25
|
+
- Add red-green persistence, stale-generation, exactly-once, stable-catalog, disconnected-execution, reconnect, cancellation, timeout, transient Worker-error/close-only recovery, and real Wrangler OAuth/MCP integration coverage.
|
|
26
|
+
- Repair POSIX process-tree escalation after workflow-level repeated full verification exposed a surviving anti-`SIGTERM` descendant. Ownership is refreshed immediately after graceful termination, and escalation falls back to targeted PID/start-time/PGID checks when a full process-table snapshot is unavailable under load; PID reuse still fails closed.
|
|
27
|
+
- Extend post-deployment Worker health convergence for edge propagation, and treat an already recorded current deployment fingerprint as verification-only unless `--force-worker` is explicitly supplied. Persistent candidate activation now compensates an early failure by restarting a service that was active before the transaction, after candidate and lock cleanup; restoration failures remain aggregated with the primary failure.
|
|
28
|
+
|
|
29
|
+
### Audit and documentation
|
|
30
|
+
|
|
31
|
+
- Re-audit the relay lifecycle, tool-advertisement contract, pending-call accounting, storage validation, state-machine boundaries, privacy-safe diagnostics, and obsolete event-settlement code. Synchronize architecture, operations, logging, testing, security, privacy, upgrading, and audit documentation with the implemented continuity model and its residual failure boundaries.
|
|
32
|
+
|
|
3
33
|
## 3.0.0-beta.20 - 2026-07-26
|
|
4
34
|
|
|
5
35
|
### Fixed
|
package/README.md
CHANGED
|
@@ -155,6 +155,8 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
|
|
|
155
155
|
|
|
156
156
|
For remote calls, `server_info.authorization.effective_policy` and `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
|
|
157
157
|
|
|
158
|
+
`tools/list` is a stable discovery catalog for the authenticated account role. A brief relay interruption does not withdraw tool definitions or require a tools-list-changed notification. Discovery is not authority: every `tools/call` is still intersected with the current end-to-end-ready daemon policy and tool ceiling, and fails retryably with `unavailable` when no daemon is ready. `server_info.tool_delivery` distinguishes the stable advertised catalog from the currently effective daemon/account intersection.
|
|
159
|
+
|
|
158
160
|
`full` is the daemon capability ceiling. An authenticated owner may exercise it without per-operation approval IDs. Delegated reviewer, editor, and operator accounts remain inside immutable role ceilings; out-of-role operations are denied rather than converted into a temporary elevation workflow. Process sessions, retained output, and managed jobs are additionally bound to account, client, and refresh-token family. See [local authorization](docs/LOCAL_AUTHORIZATION.md).
|
|
159
161
|
|
|
160
162
|
## Browser and application automation
|
package/SECURITY.md
CHANGED
|
@@ -67,7 +67,7 @@ The roles are:
|
|
|
67
67
|
|
|
68
68
|
No approval ID, refresh token, reconnect, client registration, or legacy lease can expand a role. Out-of-role operations fail with `authorization_denied`.
|
|
69
69
|
|
|
70
|
-
The Worker filters the
|
|
70
|
+
The Worker filters the stable discovery catalog by account role. Discovery is not authorization: each call is separately intersected with the current end-to-end-ready daemon capability ceiling, and the local runtime independently recomputes the role and policy boundary before dispatch. Account disablement, role change, password rotation, account removal, client revocation, token-version rotation, and refresh-family replay invalidate the appropriate credentials.
|
|
71
71
|
|
|
72
72
|
An OAuth client is bound to one account, account version, and role after successful authorization. It cannot silently switch accounts. Use `machine-mcp account clients` to inspect clients and `machine-mcp account revoke-client CLIENT_ID` to revoke one client and its credentials.
|
|
73
73
|
|
|
@@ -194,7 +194,7 @@ Sensitive and persistence targets are owner-only. Generic remote file tools cann
|
|
|
194
194
|
|
|
195
195
|
Direct processes use argv without shell parsing. Shell expansion is available only through the explicit shell tool.
|
|
196
196
|
|
|
197
|
-
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable daemon replacement use process-tree termination with bounded graceful and forced phases. A transient relay or HTTP/SSE disconnect is not cancellation.
|
|
197
|
+
Process counts, stdin, output, timeouts, retained sessions, and tool-call concurrency are bounded. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable daemon replacement use process-tree termination with bounded graceful and forced phases. A transient relay or HTTP/SSE disconnect is not cancellation. Streamed-call ownership and deadlines survive Durable Object hibernation, while a random per-WebSocket generation prevents an obsolete socket from settling or detaching a rebound call.
|
|
198
198
|
|
|
199
199
|
Interactive process sessions die when their owning runtime stops or is replaced; an ordinary same-process relay reconnect does not itself destroy them. Retained output sessions and process control are bound to account, account version, OAuth client, and refresh family.
|
|
200
200
|
|
|
@@ -226,7 +226,7 @@ Destructive state removal validates marker files, selected workspace, known layo
|
|
|
226
226
|
|
|
227
227
|
Only one verified daemon is active. Candidates have preflight, hello, readiness, and liveness deadlines. A candidate cannot displace the current daemon before authentication and end-to-end readiness.
|
|
228
228
|
|
|
229
|
-
Pending calls are bounded, socket-bound, request-bound, timed out, cancellable, and recoverable only for the same verified daemon instance during the documented reconnect grace period.
|
|
229
|
+
Pending calls are bounded, socket-generation-bound, request-bound, timed out, cancellable, and recoverable only for the same verified daemon instance during the documented reconnect grace period. Worker transport/liveness invalidation is retryable and cannot by itself stop the daemon process; unknown protocol messages, authentication rejection, and identity/version mismatch remain fatal.
|
|
230
230
|
|
|
231
231
|
Request bodies, messages, traversals, files, output, OAuth stores, nonce stores, sessions, and failure identities are bounded. These controls do not replace Cloudflare MFA, WAF/rate limits, billing alerts, or external cost controls.
|
|
232
232
|
|
|
@@ -263,4 +263,4 @@ See [docs/AUDIT.md](docs/AUDIT.md) for historical findings and residual limitati
|
|
|
263
263
|
|
|
264
264
|
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.
|
|
265
265
|
|
|
266
|
-
The Worker stores
|
|
266
|
+
The Worker stores bounded stream and call state to bridge transport loss. Active streamed-call records contain opaque ownership/generation identifiers, request correlation, deadlines, and no tool arguments; terminal records retain at most 1.5 MiB for two minutes and include SHA-256 integrity metadata. The index is limited to 64 streams. This protects continuity and detects accidental storage corruption, not compromise of the Worker account or Durable Object. A valid persisted call remains pending after Worker restart. Only a pending stream record with no durable call owner is reported as an ambiguous execution outcome; clients must reconcile before retrying a non-idempotent tool in that case.
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.22",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -111,12 +111,14 @@ Public `/healthz`, `/`, discovery metadata, CORS preflight, and unknown-path 404
|
|
|
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
|
|
114
|
+
- a bounded in-memory map for JSON-only daemon calls whose initiating request still owns the terminal Promise;
|
|
115
|
+
- one short FIFO admission gate that computes the combined 32-call ceiling across the in-memory and persistent paths;
|
|
116
|
+
- a bounded persistent index for streamed daemon-call ownership, opaque connection generation, request correlation, result-transform metadata, and monotonic operation/reconnect deadlines;
|
|
115
117
|
- bounded resumable MCP delivery metadata and terminal responses for recently disconnected SSE clients.
|
|
116
118
|
|
|
117
|
-
`BridgeRoom` owns stateful routing, MCP authorization/dispatch, daemon WebSocket lifecycle,
|
|
119
|
+
`BridgeRoom` owns stateful routing, MCP authorization/dispatch, daemon WebSocket lifecycle, cancellation, and composition of the extracted state machines. `worker-entry.ts` owns outer-Worker static routing, stateful admission, SSE proxy selection, and privacy-safe gateway failures; `worker-static-routes.ts` and `worker-metadata.ts` own stateless public responses; `worker-edge-guard.ts` owns the burst guard and quota classification. `mcp-stream-proxy.ts` owns public SSE adaptation, while `mcp-stream-subscription.ts` owns bounded terminal-subscription retries and payload validation. `mcp-stream-channel.ts` owns Durable Object subscriber registration, single-subscriber replacement, and hibernation-safe terminal push. `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, immediate pending/terminal polls, expiry, replay, and guarded terminal writes; `mcp-resumption-records.ts` and `mcp-resumption-index.ts` own record validation, compact indexing, terminal-message bounds, integrity metadata, pruning, and eviction. `mcp-pending-call-store.ts` and `mcp-pending-call-records.ts` own persistent streamed-call identity, capacity, request-key uniqueness, deadlines, detach/rebind, and connection-generation checks. `durable-stream-calls.ts` owns streamed-call cancellation, timeout, settlement, and combined observability; `runtime-alarm.ts` and `runtime-alarm-storage.ts` own earliest-deadline projection and coalesced alarm writes. `daemon-sockets.ts` owns socket role transitions, while `daemon-socket-attachment.ts` owns bounded attachment decoding. `mcp-stream.ts` owns SSE 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.
|
|
118
120
|
|
|
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
|
|
121
|
+
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` first commits the recovery record, then transactionally attaches the durable call ID, daemon-process identity, per-WebSocket connection generation, client request key, operation deadline, and optional bounded result transform before sending the daemon envelope and returning an internal descriptor. The later WebSocket result, explicit cancellation, operation timeout, send failure, or reconnect-grace expiry converges through one guarded terminal write. A Durable Object restart can therefore rediscover the call and keep it pending; only a pending stream record with no durable call owner becomes the restart-ambiguity result. JSON-only calls retain the ordinary in-event Promise path. The outer Worker sends `stream:0`, heartbeats, and `stream:1`; the Durable Object accepts one hibernatable subscriber WebSocket per stream and pushes the terminal JSON-RPC envelope once. 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/subscribe 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 stream and call ownership, not live Promises; a transient terminal map is used only when persistence fails. It has no local filesystem or process API.
|
|
120
122
|
|
|
121
123
|
|
|
122
124
|
### Daemon device authentication
|
|
@@ -171,12 +173,12 @@ Remote OAuth binds each code, access token, and refresh token to a named Machine
|
|
|
171
173
|
6. A valid verifier exchanges the one-time code for an expiring access token and refresh token; only their hashes are stored. A refresh request is bound to the original public client, account, scope, resource, account version/role, deployment token version, and optional DPoP key. Rotation derives one replacement pair from the consumed token and the private deployment token version, then permits at most two identity-equivalent responses with that exact pair during a 30-second concurrency window; over-budget retries are throttled, and replay after the window revokes the family.
|
|
172
174
|
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 and increments the bounded `session_bootstrap_failed` observability counter.
|
|
173
175
|
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.
|
|
174
|
-
9. `tools/list` is
|
|
175
|
-
10. `tools/call` receives a random relay call ID and is bound to the
|
|
176
|
-
11. The runtime validates policy and arguments, executes the tool, and
|
|
177
|
-
12. If the socket remains ready, the Durable Object accepts the result only from that
|
|
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.
|
|
179
|
-
14. If same-instance readiness does not return before the grace deadline, the Worker rejects the detached request and the local runtime cancels ordinary calls, terminates their process trees, and discards queued results. A newly started daemon has a different instance identifier and cannot inherit prior calls.
|
|
176
|
+
9. `tools/list` is a stable package-and-account-role discovery catalog and declares `listChanged: false`; a brief relay interruption does not mutate it. `server_info.authorization.effective_tools` is the live daemon/account intersection and is the authority diagnostic.
|
|
177
|
+
10. `tools/call` receives a random relay call ID and is bound to the daemon process's ephemeral instance identifier, a random per-WebSocket connection generation, and the authenticated client request key. When the client accepts `text/event-stream`, `BridgeRoom` commits recovery state plus durable call ownership and deadlines before sending the daemon envelope, then returns a bounded descriptor immediately; the outer Worker owns the SSE priming frame, keepalives, and one internal terminal subscription. No unresolved terminal Promise, JavaScript timer, or Durable Object `waitUntil` owns the streamed dispatch. JSON-only clients retain the single terminal response.
|
|
178
|
+
11. The runtime validates policy and arguments, executes the tool, and produces a bounded result. It retains the terminal envelope after WebSocket queueing and replays it until the Worker returns `tool_result_ack`; queue acceptance is not treated as durable delivery. 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.
|
|
179
|
+
12. If the socket remains ready, the Durable Object accepts the result only from that connection generation and acknowledges only after transient settlement, committed durable settlement, or an idempotent already-terminal duplicate. A persistence exception is not converted into success, and a stale generation is not acknowledged. If it drops, the Worker durably detaches the streamed 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 and atomically acquired a new connection generation. A delayed result or close event from the old socket cannot settle or detach the rebound call. 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.
|
|
180
|
+
13. The terminal store is exactly-once while daemon delivery is at-least-once. Lost acknowledgement causes heartbeat/reconnect replay; the generation guard and idempotent missing-record acknowledgement make replay converge without reviving a completed call. 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.
|
|
181
|
+
14. A tool deadline cancels only that operation and never infers daemon death from tool duration. The independent daemon-liveness alarm owns socket invalidation. If same-instance readiness does not return before the grace deadline, the Worker rejects the detached request and the local runtime cancels ordinary calls, terminates their process trees, and discards queued results. A newly started daemon has a different instance identifier and cannot inherit prior calls.
|
|
180
182
|
15. `start_job` is different: after durable acceptance, the detached runner is no longer bound to the relay call or socket. Later cancellation uses `cancel_job` or the local CLI.
|
|
181
183
|
|
|
182
184
|
Duplicate in-flight JSON-RPC IDs are rejected only within the same authenticated MCP session. The request key includes OAuth token identity, the HMAC-bound MCP session, JSON-RPC id type, and id value, so separate initialized clients may safely reuse the same numeric id.
|
|
@@ -233,7 +235,7 @@ Large object results use `structuredContent` as the authoritative representation
|
|
|
233
235
|
|
|
234
236
|
Startup-lock waits, daemon takeover, process-session reads, managed-job recovery handoff, browser/page waits, application-cache freshness, and in-memory duration metrics use monotonic elapsed time, so wall-clock correction cannot extend or prematurely terminate their configured duration. Persisted timestamps and retention/credential expiry continue to use wall time. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Head/tail previews trim incomplete UTF-8 boundary code points instead of introducing replacement characters. Session IDs are random. Running sessions are killed on runtime stop or non-recoverable daemon replacement; a transient same-process relay disconnect enters bounded recovery instead of being treated as cancellation.
|
|
235
237
|
|
|
236
|
-
Child processes run in a separate process group where supported. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. Windows uses tree-aware task termination.
|
|
238
|
+
Child processes run in a separate process group where supported. Timeout, explicit cancellation, reconnect-grace expiry, runtime shutdown, and non-recoverable replacement send termination to process trees, with a referenced forced-escalation timer that remains alive even when the direct child exits before a resistant descendant. POSIX ownership is captured before `SIGTERM`, refreshed immediately afterward to include descendants created near the timeout boundary, and revalidated before `SIGKILL` by PID, process start time, and process-group ID. If a full process-table snapshot is unavailable, each captured PID is queried directly; ambiguous identity or PID reuse still fails closed. Windows uses tree-aware task termination.
|
|
237
239
|
|
|
238
240
|
Managed jobs use the same argv/environment primitives but a different lifecycle. Each job is capped at 16 main and 16 finally steps, 50 retained jobs, 64 registered resources, 8 MiB of referenced resource bytes, 512 KiB of temporary-file content, and bounded per-step output. They are non-interactive. Resource paths/stdin/environment are injected only inside the runner. Exact resource output redaction is defense in depth; discard capture is the strong option when a command may echo credentials.
|
|
239
241
|
|
|
@@ -249,13 +251,13 @@ Worker-name mutation is a separate identity transition. Existing state rejects a
|
|
|
249
251
|
|
|
250
252
|
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to standard HTTP(S) environment-proxy resolution, honors `NO_PROXY`, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
|
|
251
253
|
|
|
252
|
-
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts heartbeats, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
|
|
254
|
+
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts heartbeats, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once ready, application heartbeats require inbound activity; a silent half-open socket is terminated and reconnected. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. The same classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback.
|
|
253
255
|
|
|
254
256
|
Reconnect uses bounded exponential backoff with jitter. Brief self-healing interruptions are debug-only. An unresolved outage is promoted to a rate-limited warning after a grace period, and recovery produces one summary. Raw close codes and reason strings remain debug-only.
|
|
255
257
|
|
|
256
258
|
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.
|
|
257
259
|
|
|
258
|
-
Each daemon process generates a random bounded `instance_id` at startup and includes it in every reconnect hello.
|
|
260
|
+
Each daemon process generates a random bounded `instance_id` at startup and includes it in every reconnect hello. Each accepted WebSocket additionally receives a random `connection_id` generation stored in its hibernation attachment. JSON-only pending calls retain an in-memory socket reference; streamed calls persist the opaque generation instead. On an unexpected socket loss, only calls owned by that generation are detached and the shared two-minute relay contract bounds recovery. During verified same-instance handover, the Worker transfers both already-detached and still-attached calls to the replacement generation before closing the incumbent. A delayed close or result from the incumbent fails the generation check and cannot mutate the rebound record. 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. The active stream record expiry is extended to cover each new reconnect deadline, remaining operation budget, and terminal replay window; repeated successful reconnect cycles cannot outlive and delete their own ownership record. JavaScript timers remain only the low-latency owner for JSON-only calls. Streamed operation and reconnect deadlines live in the durable call record, the earliest deadline is projected onto one 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-process restart or machine failure; managed jobs remain the separate durable mechanism.
|
|
259
261
|
|
|
260
262
|
## Persistence
|
|
261
263
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-07-28 version 3.0.0-beta.22 ChatGPT interruption and full repository audit
|
|
4
|
+
|
|
5
|
+
The visible symptom combined three different mechanisms that must not be collapsed into one diagnosis. First, historical 11–34 minute relay gaps aligned with macOS clamshell sleep and DarkWake intervals; those records are expected machine suspension, not evidence of an active-use Cloudflare or daemon failure. Second, a controlled foreground command requested 120 seconds but the hosted tool path abandoned it at approximately 100 seconds. Third, the live Worker retained durable `exec_command` records after the local runtime had already completed and removed the corresponding operation. The latter two mechanisms explain active-use ChatGPT interruption and ghost pending-call growth.
|
|
6
|
+
|
|
7
|
+
The terminal-delivery protocol had a concrete loss window. The local runtime deleted a completed result as soon as `WebSocket.send()` accepted the frame, although that call proves only local queueing. If the connection ended before the Worker committed its generation-guarded terminal storage transaction, the runtime had neither an active operation nor a retained result to replay, while the Worker still owned the durable call. Beta.22 adds `tool_result_ack`: the runtime retains and periodically replays terminal results until acknowledgement; the Worker acknowledges only a transient settlement, a committed durable settlement, or an idempotent already-terminal duplicate. A result from a stale WebSocket generation is never acknowledged. Durable persistence failure now propagates instead of logging and falsely incrementing completion.
|
|
8
|
+
|
|
9
|
+
The timeout path also crossed responsibility boundaries. A single call timeout used a 45-second socket-silence heuristic to invalidate the complete daemon connection even though daemon liveness already has an independent 90-second alarm and readiness state machine. A slow command could therefore interrupt unrelated reads and make ChatGPT lose every local surface. Beta.22 removes socket invalidation from both transient and durable call expiry; timeout sends best-effort per-call cancellation only. The liveness alarm remains the sole owner of connection invalidation. Because the hosted foreground tool path was observed to stop near 100 seconds, configurable remote operations are capped at 85 seconds plus five seconds for terminal delivery. Longer work belongs in `start_process`/`read_process` or managed jobs, subject to what the MCP host exposes.
|
|
10
|
+
|
|
11
|
+
The broader review found a separate operations defect not named in the report: active daemon logs were safely trimmed before startup but could grow without another bound during a very long service lifetime. Beta.22 reuses the existing secure in-place trim every 15 minutes. The maintenance path preserves regular-file, no-follow, single-link, owner-only mode, schema, UTF-8, and line-boundary checks; failures log only a coarse class. Static review found no new secret-bearing operational logs, raw command/result logging, unbounded Worker body, public control-plane path, or obsolete transient settlement branch. Intentional migration fixtures remain confined to tests and explicit state upgrade code.
|
|
12
|
+
|
|
13
|
+
Fault-directed tests cover connected and reconnect result retention, lost acknowledgement heartbeat replay, Worker acknowledgement identity, persistent terminal-write failure, stale-generation non-acknowledgement, host-safe timeout bounds, log-maintenance scheduling/failure containment, and the previous cancellation/reconnect matrix. The repository architecture line budget rejected an oversized first draft of the recovery module; the implementation was reduced below the existing boundary rather than weakening the gate.
|
|
14
|
+
|
|
15
|
+
## 2026-07-27 version 3.0.0-beta.21 relay-continuity and repository audit
|
|
16
|
+
|
|
17
|
+
The reported symptom was a temporary loss of every local command surface, including `pwd`, followed by automatic daemon reconnection. Process evidence rejects the premise that the local daemon crashed: launchd retained one daemon PID, one start time, and `runs=1` throughout the observed interval. The failing layer was the relay/Worker connection and result-delivery path. Historical service logs also contain abnormal WebSocket closure and heartbeat-outage intervals that later recovered without a daemon restart. The exact network origin of each `1006` remains unknowable from those logs alone; a system VPN/TUN, proxy route, intermediary, or edge connection can all produce the same transport-level symptom.
|
|
18
|
+
|
|
19
|
+
A separate failure occurred during this audit: at 2026-07-27 05:24:33 UTC the local service emitted `remote relay protocol error`, exited with code 1, and launchd advanced from one run to two before reconnecting a replacement process. The retained log does not include the raw Worker error code, so that exact triggering frame cannot be reconstructed. Static and red-green analysis nevertheless found a concrete fatal-amplification family: `daemon_transport_error` and `daemon_liveness_timeout`, emitted by the Worker for transient socket failure, fell through the local `handleServerError` default and invoked `failPermanently`; a failed `hello` send was relabelled as authentication failure; and a readiness-probe result lost to an ending socket/session was relabelled as protocol violation. Beta.21 routes all three transport races through ordinary socket teardown and bounded reconnect, classifies error-frame and close-only forms consistently, uses WebSocket 1012 for transient Worker invalidation, and preserves permanent failure only for unknown/incompatible protocol, genuine authentication failure, malformed readiness sequencing, or identity/version mismatch.
|
|
20
|
+
|
|
21
|
+
The audit found two Machine Bridge amplification defects. First, the MCP initialize response declared `tools.listChanged=false`, but `tools/list` was derived from the momentary ready-daemon set. A brief relay interruption therefore withdrew every tool except `server_info` without the protocol notification required for a mutable catalog. Beta.21 makes discovery stable by package catalog and account role. This does not broaden authority: every call still requires a live end-to-end-ready daemon, daemon policy/tool permission, and account-role permission. `server_info.tool_delivery` now reports stable advertised and live effective scopes separately, and disconnected calls fail retryably with `unavailable` instead of mutating the catalog.
|
|
22
|
+
|
|
23
|
+
Second, streamed calls returned from their initiating Durable Object event but retained ownership in an in-memory pending registry backed by JavaScript timers. That model either prevents hibernation or loses ownership if the object is evicted; an alarm alone cannot recover an in-memory record. Beta.21 persists the call ID, daemon-process identity, random per-WebSocket connection generation, client request key, operation/reconnect deadline, and bounded result-transform metadata in the stream record before dispatch. Durable Object alarms and event-entry sweeps are the cross-event deadline owners. A restarted object can rediscover the call, the same verified daemon process can atomically rebind it, and a delayed close or result from the old socket fails the generation check. Success, rejection, cancellation, send failure, operation timeout, reconnect-grace expiry, and duplicate terminal delivery converge through one guarded terminal transaction. A FIFO admission gate enforces one 32-call ceiling across persistent and JSON-only calls. Active-record expiry advances over every new reconnect and remaining-operation budget, so repeated successful handovers cannot delete a still-owned call. JSON-only calls retain the prior bounded Promise/timer path.
|
|
24
|
+
|
|
25
|
+
The broader repository review refused line-budget exceptions and extracted outer Worker routing, pending-call persistence, record validation, stream-index maintenance, durable settlement, result projection, alarm storage, and socket-attachment decoding into focused modules. It removed the obsolete transient `registerEvent` settlement branch, added architecture rules forbidding its return, preserved Node strip-only TypeScript compatibility, and added fault-directed tests for restart recovery, corrupt records, fixed storage-write budgets, alarm expiry, stale generations, exactly-once settlement, stable discovery, and disconnected execution. Logging and privacy rules prohibit arguments, results, request keys, account identifiers, raw call/connection IDs, private paths, and subscriber payloads from operational diagnostics.
|
|
26
|
+
|
|
27
|
+
The workflow-level global verification deliberately repeated the complete project-native gate and exposed a separate real cleanup defect after two earlier green full runs. Under load, the POSIX process-tree ownership snapshot could miss a newly spawned descendant or the later full-table `ps` scan could fail; if the direct parent exited after `SIGTERM`, the conservative identity check then skipped `SIGKILL`, leaving an anti-`SIGTERM` descendant reparented to init. The fix refreshes process-group ownership after graceful termination and, during escalation, performs a targeted `ps -p` identity check for each captured PID when the full group scan yields no match. Five consecutive complete self-tests leave no descendant behind, while PID/start-time/PGID mismatch continues to suppress escalation.
|
|
28
|
+
|
|
29
|
+
`npm run check:fast` passes all 63 repository tasks after the implementation. Full, dependency, package, Worker dry-run, privacy-history, and workflow-bundle verification remain separate evidence and must pass before merge readiness is claimed. This source audit does not deploy a Worker, replace the running daemon, activate a candidate, rotate credentials, publish npm, push Git history, create a tag, or record live acceptance.
|
|
30
|
+
|
|
3
31
|
## 2026-07-26 version 3.0.0-beta.18 account-continuity and quota audit
|
|
4
32
|
|
|
5
33
|
The repeated hosted-client message `We couldn't connect your account. Please try again.` occurred while the Worker, Durable Object, daemon, Wrangler login, and end-to-end doctor probes were healthy. The remaining credential path had a destructive concurrency assumption: refresh tokens rotated once, and any immediate second use revoked the complete family—including the replacement credentials just returned to a concurrent request. A network retry or two refreshes racing through the same hosted connector could therefore convert a recoverable response race into total account loss. Beta.18 records a bounded source snapshot for 30 seconds and uses a deployment-keyed, domain-separated HMAC to reproduce the original replacement pair for at most two identity-equivalent retry responses. Retries neither create another credential branch nor extend expiration; the Worker returns a retryable 429 after that budget, and retains whole-family revocation for replay after the grace window. Client, resource, scope, account/version/role, deployment token version, and DPoP binding are revalidated on every retry.
|
package/docs/ENGINEERING.md
CHANGED
|
@@ -21,7 +21,7 @@ This document records project-wide decisions that must survive individual fixes,
|
|
|
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
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
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
|
|
24
|
+
18. **Durable state owners do not retain cross-event terminal Promises or depend on JavaScript timers as durable ownership.** A Durable Object that must accept cancellation, status, recovery, or hibernation 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. Stream initiation transactionally persists the stream plus daemon-call ownership, connection generation, operation deadline, reconnect deadline, request correlation, and bounded result-transform metadata before sending work. Later WebSocket, cancellation, timeout, send-failure, or reconnect-expiry events converge through one guarded terminal write. Durable Object alarms and compensating event-entry sweeps own cross-event deadlines; JavaScript timers remain only for bounded JSON-response calls. Descriptor requests remain short; terminal delivery uses one authenticated hibernatable WebSocket subscription. Both are admitted only on the internal service-binding path and are unreachable through caller-supplied internal headers.
|
|
25
25
|
|
|
26
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.
|
|
27
27
|
|
|
@@ -25,7 +25,7 @@ Local stdio does not use remote OAuth accounts. It runs as the local owner under
|
|
|
25
25
|
| `operator` | Workspace-confined editing and direct process execution | No unrestricted paths, credentials, browser/desktop control, or persistent job creation |
|
|
26
26
|
| `owner` | Complete bridge authority within the daemon policy ceiling | Generic path-based tools cannot target Machine Bridge control-plane state; owner shell remains OS-user authority |
|
|
27
27
|
|
|
28
|
-
The
|
|
28
|
+
The package catalog defines the stable discovery surface. The Worker filters `tools/list` by account role without rewriting it during a brief daemon outage. The live daemon advertisement remains the execution ceiling, and the local runtime independently recomputes and validates the role and policy boundary before dispatch.
|
|
29
29
|
|
|
30
30
|
## Trusted OAuth clients
|
|
31
31
|
|
package/docs/LOGGING.md
CHANGED
|
@@ -64,7 +64,9 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
|
|
|
64
64
|
- failure to receive `hello_ack` within the handshake deadline, or `ready_ack` within the independent end-to-end readiness deadline, terminates the candidate socket and retries;
|
|
65
65
|
- lack of inbound heartbeat activity terminates a half-open socket and reconnects.
|
|
66
66
|
|
|
67
|
-
A WebSocket close code such as `1006` means the transport ended without a normal close handshake. It is useful for debug diagnosis but not useful as the default user message. Default logs therefore describe the
|
|
67
|
+
A WebSocket close code such as `1006` means the transport ended without a normal close handshake. It is useful for debug diagnosis but not useful as the default user message. It is not evidence that the daemon process restarted. Worker `daemon_transport_error` / `daemon_liveness_timeout` messages and their 1012 close frames are likewise retryable connection conditions, not upgrade instructions. Only an unknown/incompatible Worker error, authentication failure, or identity/version mismatch may produce the fatal protocol/configuration log and daemon exit. Default logs therefore describe the affected layer, duration, classification, and recovery behavior rather than printing raw close envelopes.
|
|
68
|
+
|
|
69
|
+
Persisted streamed-call diagnostics are deliberately coarse. Logs and `server_info` may report aggregate active/detached counts, oldest age, tool-name counts, alarm mutations, unmatched-result counts, and whether a call was transient or durable. They must not include tool arguments, terminal results, command text, request keys, account identifiers, raw call IDs, raw connection generations, private paths, or subscriber payloads. A stale-generation result is counted as unmatched rather than logged with its envelope.
|
|
68
70
|
|
|
69
71
|
Examples:
|
|
70
72
|
|
|
@@ -133,7 +135,7 @@ logs/daemon.out.log
|
|
|
133
135
|
logs/daemon.err.log
|
|
134
136
|
```
|
|
135
137
|
|
|
136
|
-
Existing files are opened without following symbolic links where supported and tail-trimmed on UTF-8/line boundaries before startup. Background services use `warn`, so ordinary tool traffic and brief relay interruptions do not cause sustained growth.
|
|
138
|
+
Existing files are opened without following symbolic links where supported and tail-trimmed on UTF-8/line boundaries before startup. The active background daemon repeats the same secure trim every 15 minutes, so a long-lived process remains bounded even under repeated warning-level failures. Maintenance errors expose only a coarse error class. Background services use `warn`, so ordinary tool traffic and brief relay interruptions do not cause sustained growth.
|
|
137
139
|
|
|
138
140
|
The log format has an explicit schema marker. If the marker differs from the current format, the daemon clears the active files before startup and writes the current marker. Runtime code recognizes only `daemon.out.log` and `daemon.err.log`; it does not parse or archive other log formats.
|
|
139
141
|
|
package/docs/MULTI_ACCOUNT.md
CHANGED
|
@@ -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`
|
|
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
|
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -42,7 +42,7 @@ The standard public endpoint remains the automatically provisioned `workers.dev`
|
|
|
42
42
|
|---|---|
|
|
43
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 |
|
|
44
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 |
|
|
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
|
|
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 |
|
|
46
46
|
| `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
|
|
47
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 |
|
|
48
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 |
|
|
@@ -59,9 +59,13 @@ A successful diagnostic result applies only to that probe. An MCP host can still
|
|
|
59
59
|
|
|
60
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.
|
|
61
61
|
|
|
62
|
-
`server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, and `
|
|
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
63
|
|
|
64
|
-
|
|
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.
|
|
65
|
+
|
|
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.
|
|
67
|
+
|
|
68
|
+
The terminal-result protocol is at-least-once from daemon to Worker and exactly-once at the generation-guarded terminal store. The daemon retains a completed result until `tool_result_ack`, replays it after reconnect and on heartbeat, and reconciles it against the authoritative `resume_calls` set. A missing acknowledgement therefore causes bounded duplicate delivery, not a ghost call.
|
|
65
69
|
|
|
66
70
|
`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.
|
|
67
71
|
|
|
@@ -79,6 +83,10 @@ A brief relay interruption is retried automatically and is visible only with `--
|
|
|
79
83
|
|
|
80
84
|
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.
|
|
81
85
|
|
|
86
|
+
Compare outage intervals with operating-system sleep/wake records before classifying them as active network failures. A suspended laptop cannot maintain the relay heartbeat, and a long outage that exactly spans clamshell sleep or DarkWake is expected suspension evidence. It does not justify credential rotation, Worker redeployment, or daemon replacement.
|
|
87
|
+
|
|
88
|
+
A foreground MCP tool is not a durable job. Hosted clients may impose a request ceiling below the local tool schema; ChatGPT was observed abandoning a foreground call near 100 seconds. Machine Bridge therefore terminates remote foreground execution by 90 seconds including delivery overhead. Use `start_process` plus bounded `read_process`, or a managed job, for work that can exceed 85 seconds. If the host exposes only `exec_command`, split work into independently terminal steps rather than requesting a larger timeout.
|
|
89
|
+
|
|
82
90
|
The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. `server_info.runtime.relay.network_route` reports `system-network-stack`, `application-http-proxy`, or `invalid-application-proxy-configuration`. This field describes only Machine Bridge application-level proxy selection: an operating-system VPN/TUN may still intercept `system-network-stack` traffic. `network_route_scope`, outage timestamps/durations, close category/code, transport error class, and next retry timing make that distinction explicit; proxy endpoints and credentials are never returned or logged. The browser-broker CLI health probe is a separate loopback-only path: it accepts only canonical `127.0.0.1`, uses direct Node HTTP with no proxy agent, and does not depend on `NO_PROXY`.
|
|
83
91
|
|
|
84
92
|
## Browser extension setup and diagnosis
|
|
@@ -180,7 +188,7 @@ Windows Task Scheduler limits the `/TR` action text, so the platform adapter wri
|
|
|
180
188
|
|
|
181
189
|
The Windows trigger is current-user `ONLOGON` with `LIMITED` run level. After a reboot, signing in to that user is sufficient; no terminal command is required. Pre-login operation is intentionally not provided by the default design because it would require a different service-account/credential boundary. Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the current Node/package directories, the operator's inherited absolute PATH entries, and platform defaults. When installation runs through npm, all nested run-script prefixes through the final npm private `node-gyp-bin` marker are discarded; paths belonging to inactive candidate runtimes are also removed. This prevents prerelease activation from persisting source-repository shims or the prior runtime that activation subsequently prunes, while an ordinary user-supplied `node_modules/.bin` remains valid. A private allowlisted `service-environment.json` preserves `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, matching lowercase forms, optional `ALL_PROXY`, Node proxy selection, and custom-CA path variables. Existing saved values survive an environment-free reinstall, while explicitly supplied values replace case-insensitive prior variants. Values may include proxy credentials, so the file stays in owner-only state and is never returned or logged; status exposes key names only. Re-run `machine-mcp service install` after changing Node installation families, PATH layout, proxy, or CA configuration. Custom Windows state paths used for autostart must also remain within the Task Scheduler path limit and must not contain a literal `%`.
|
|
182
190
|
|
|
183
|
-
A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup. If the log schema marker does not match the current format, the active files are cleared before startup and the current marker is written. Runtime code reads and maintains only the active filenames.
|
|
191
|
+
A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup and every 15 minutes while the background daemon remains active. Runtime maintenance reuses the same no-follow, regular-file, single-link, `0600`, schema, UTF-8, and line-boundary checks. If the log schema marker does not match the current format, the active files are cleared before startup and the current marker is written. Runtime code reads and maintains only the active filenames.
|
|
184
192
|
|
|
185
193
|
Logging is level-based:
|
|
186
194
|
|
|
@@ -278,7 +286,7 @@ Defense-in-depth limits include:
|
|
|
278
286
|
- process stdin write: 64 KiB per call;
|
|
279
287
|
- local simultaneous tool calls: 16;
|
|
280
288
|
- Worker pending daemon calls: 32;
|
|
281
|
-
- command timeout: 1–600 seconds;
|
|
289
|
+
- local command timeout schema: 1–600 seconds; remote foreground relay execution is capped at 85 seconds plus five seconds of terminal-delivery overhead;
|
|
282
290
|
- process-session read wait: at most 30 seconds, measured with monotonic elapsed time;
|
|
283
291
|
- direct directory result: 10,000 entries and 4 MiB of path metadata;
|
|
284
292
|
- recursive walk: 200,000 visited entries;
|
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
|
|
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.
|
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
|
|
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;
|
|
@@ -39,8 +39,8 @@ The suite includes:
|
|
|
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
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, bounded propagation
|
|
43
|
-
- Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a
|
|
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;
|
|
@@ -159,6 +159,8 @@ The stdio integration test also sends an oversized line, verifies bounded reject
|
|
|
159
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.
|
|
160
160
|
## Resumable MCP delivery coverage
|
|
161
161
|
|
|
162
|
-
`npm run mcp-resumption:test` directly exercises stream cursor parsing, OAuth-token/MCP-session isolation, immediate pending/terminal polls, active and completed replay,
|
|
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
163
|
|
|
164
|
-
`npm run worker-runtime-infrastructure:test` verifies outer-Worker stream ownership, stripping of caller-supplied internal headers, bounded descriptor/subscribe adaptation, fixed
|
|
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. It additionally proves terminal-result retention until Worker acknowledgement, heartbeat replay after acknowledgement loss, stale-generation non-acknowledgement, propagation of durable terminal-write failure without false completion, and the 85-second interactive execution plus five-second delivery budget. Local infrastructure coverage verifies periodic secure autostart-log trimming and contains maintenance failure without leaking exception text. `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`.
|
|
165
|
+
|
|
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.
|
package/docs/THREAT_MODEL.md
CHANGED
|
@@ -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;
|
|
@@ -123,7 +124,7 @@ The implementation aims to preserve these invariants:
|
|
|
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
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;
|
|
126
|
-
- cancellation, timeout, disconnect, replacement, and shutdown have explicit process ownership and cleanup semantics;
|
|
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
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;
|
|
@@ -189,7 +190,7 @@ Application-level limits bound many requests and outputs. An authorized owner pr
|
|
|
189
190
|
|
|
190
191
|
### System VPN/TUN and distributed activation
|
|
191
192
|
|
|
192
|
-
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.
|
|
193
194
|
|
|
194
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.
|
|
195
196
|
|
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.
|
|
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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machine-bridge-mcp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.22",
|
|
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",
|