machine-bridge-mcp 3.0.0-beta.192 → 3.0.0-beta.196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## 3.0.0-beta.192 - 2026-09-16
4
-
5
- - Replace the shipped current-audit document's stale release chronology with version-agnostic security, continuity, privacy, and residual-limit invariants; repository hygiene now rejects numbered prerelease chronology and mutable acceptance/candidate authority claims.
6
- - Keep WebSocket daemon handshake diagnostics aligned with the resilient relay wrapper so Worker `server_info` preserves the same bounded per-outage HTTPS fallback takeover attribution as HTTPS descriptors and local diagnostics; private relay values remain excluded, and execution/replay semantics are unchanged.
7
- - Keep durable managed-job execution available across in-place Node package-manager upgrades by falling back from a removed daemon `process.execPath` only to the still-executable absolute Node launcher that originally started the daemon; diagnostics expose provenance/availability without local paths.
8
- - Make macOS sleep diagnosis retain actual timestamped `Sleep` records with a bounded 15-second power-log probe, avoiding the previous five-second timeout and broad filter that could hide evidence explaining relay suspension.
9
- - Add regressions for stale runtime launcher recovery and sleep-probe boundaries; relay authentication, replay, duplicate-side-effect prevention, and reconnect policy are unchanged.
3
+ ## 3.0.0-beta.196 - 2026-09-21
4
+
5
+ - Stop verified-WebSocket operation from maintaining a continuously polled HTTPS standby relay; HTTPS fallback now starts only during startup grace, liveness degradation, or actual WebSocket loss, and verified WebSocket recovery stops it.
6
+ - Raise ready HTTPS fallback idle polling from one second to five seconds and standby retry from five seconds to thirty seconds while preserving the 750 ms minimum path for handshake and queued outbound work, reducing idle request and replay-nonce traffic. Scenario tests separately count recurring Durable Object alarm mutations; these are not a whole-account billing guarantee.
7
+ - Add regression coverage for healthy-day zero fallback polling, direct HTTP and WebSocket-preflight replay-nonce write accounting, daily request/write budgeting, exact-generation failover, late-ready recovery races, and repeated handover behavior.
8
+ - Enforce one monotonic HTTP retry deadline across queued work and lifecycle transitions, honor bounded 429/503 `Retry-After`, and reject exact-expiry authentication and delayed preflight consumption without nonce writes.
9
+ - Correct stale initialization-compatibility and subscription documentation to match the current-only protocol implementation.
10
+ - Independent review removes obsolete `standbyDelayMs` test fixtures and synchronizes README, architecture, operations, logging, testing, privacy, and audit documentation with the quota-bounded fallback behavior and the absence of a checked-in Workers KV binding.
10
11
 
11
12
  ## Historical releases
12
13
 
package/README.md CHANGED
@@ -174,7 +174,7 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
174
174
 
175
175
  For routine remote health checks, prefer `server_info` with `detail: "summary"`; the empty/default call remains full diagnostics and, for owner/full callers, includes a privacy-bounded durable continuity summary that survives Worker isolate replacement without retaining identities, call IDs, tool arguments, results, endpoints, or close reasons. For routine workspace inventory, `project_overview` also accepts `detail: "summary"`; it preserves policy/tool counts and top-level names/types without repeating exact tool arrays, account identity, routing fingerprints, or per-entry paths/sizes. Its empty/default call likewise remains full for compatibility. For remote calls, `server_info.authorization.effective_policy` and, when exact membership is needed, the full projection's `effective_tools` are authoritative. Daemon policy and tools describe only the local capability ceiling before account-role and host-side filtering.
176
176
 
177
- `tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. 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` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: on first-stage WSS suspicion the same root-certified ephemeral device identity prewarms HTTPS in standby, and a real WSS loss promotes that path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second request deadline, twelve-second liveness window, one-second ordinary poll cadence, and a 750 ms minimum request-start interval. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling. That delivery-only extension cannot authorize redelivery or new execution, cannot accumulate across repeated handovers, and does not create client-visible replay or durable result state. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
177
+ `tools/list` is the authenticated account's current discovery catalog. Discovery instructions and tool descriptions carry execution/orchestration semantics, so both `server/discover` and `tools/list` advertise `ttlMs=0`. Current MCP 2026-07-28 remote discovery also advertises `tools.listChanged=true`: a client that opts into `toolsListChanged` through `subscriptions/listen` receives a correlated acknowledgement and level-trigger `notifications/tools/list_changed` event, then re-fetches `tools/list`. The request-scoped subscription remains open until explicit cancellation or the advertised bounded server lease expires; the lease is a fail-safe for HTTP disconnects that the Worker runtime cannot reliably observe and does not replace the initial level-trigger/refetch contract. Every host-visible tool description carries `Tool schema generation N`; `server_info.tool_delivery` exposes the current `tool_schema_generation`, `tool_schema_server_version`, and `tool_list_ttl_ms`, while explicitly reporting that Machine Bridge cannot observe which schema generation an external host has actually cached. A generation change therefore requires the subscription/refetch path or another host-side schema refresh plus post-activation verification. 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` also distinguishes the advertised catalog from the currently effective daemon/account intersection. WebSocket is the preferred daemon transport: verified ready traffic resumes immediately, but reconnect attempt history resets only after five seconds of generation-stable ready uptime so a shorter ready/close flap keeps its prior exponential backoff position. It requests a protocol-level Ping after five seconds and gives an actually dispatched Ping its full ten-second Pong deadline, then uses one independent fifteen-second application-confirmation window before a ready WSS may be terminated as a transport black hole. A protocol Pong or explicit application `pong` during that second stage preserves WSS; ordinary tool/control inbound remains receive-side evidence and cannot clear transport suspicion, while local event-loop stalls cancel remote suspicion and use the separate recovery-grace path. The periodic application heartbeat remains twenty-five/seventy-five seconds after end-to-end readiness, and the Worker keeps a wider ninety-second fallback. WSS connect attempts have a thirty-second outer budget. Signed HTTPS is independent of that budget: a verified healthy WSS keeps HTTPS fallback stopped; on first-stage WSS suspicion the same root-certified ephemeral device identity may prewarm HTTPS in standby, verified WSS recovery stops that prewarm, and a real WSS loss promotes the path to exact-generation takeover while aborting any obsolete standby request. Fallback requests bind the fixed route/origin/server/version, a short-lived nonce, timestamp, and exact body hash; they use a seven-second ordinary request deadline, a twelve-second liveness window, a five-second verified-ready idle poll cadence, a thirty-second standby retry cadence, and a 750 ms minimum request-start interval for handshake or queued outbound work. The first authenticated exchange enters probing immediately, so verified readiness requires at most two bounded exchanges rather than a separate challenge round trip. Candidate → probing → verified-ready handover prevents the Worker from dispatching until the daemon has processed `ready_ack` and returned sequenced `https_ready`; a same-instance takeover may retire a Worker-side zombie WSS only after the signed candidate preconditions pass. Both directions use bounded contiguous transport sequences, so a lost HTTP response retransmits the same transport envelope and duplicates are discarded before business handling; this does not restore MCP sessions, recovery GET, `Last-Event-ID`, or public result persistence. Same-instance `resume_calls` / `resume_calls_ack` remains authoritative for in-flight ownership. A planned daemon shutdown is a different boundary: before a ready runtime closes its relay it sends `daemon_draining`; the Worker settles calls still owned by that daemon with structured `daemon_planned_drain` recovery and acknowledges the drain. In-flight `read_job` is explicitly read-only recoverable with `recovery.mode=read_same_job` plus the original `job_id`; resume by reading that same job after reconnect. This does not transfer an executing call to a new daemon process, whose random instance identity remains intentionally distinct, and it cannot revive an external assistant turn that has already ended. The daemon sends `resume_calls_ack.missing_ids` only after replacement readiness, only for IDs absent from both its active-call set and unacknowledged-result ledger, and only while it still has fail-closed proof that missing ownership means the call did not execute locally. If a completed-but-unacknowledged result expires, `diagnose_runtime.runtime.relay_result_recovery.automatic_redelivery_safe` becomes false and missing-ID automatic redelivery is disabled rather than risking duplicate side effects. A safe proven-undelivered call may be retransmitted with the same call ID, arguments, authority, and a reduced timeout inside the original execution deadline; a call that may have executed is never automatically replayed. Separately, if that same daemon already executed a call and its terminal result is delayed by the transport break, the Worker may retain the original in-memory settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling. That delivery-only extension cannot authorize redelivery or new execution, cannot accumulate across repeated handovers, and does not create client-visible replay or durable result state. A new call may wait up to fifteen seconds for a verified daemon channel, but measured recovery time is deducted from that call's original execution budget instead of extending the hosted foreground envelope. Hosted synchronous calls otherwise retain their ordinary 20-second execution plus separate five-second Worker settlement margin; configurable browser/application tools retain 20-second ordinary defaults, compound `computer_observe` / `computer_act` retain 30-second defaults, and the explicit remote maximum remains 45 seconds. Remote `exec_command`, `run_process`, and `run_local_command` require a caller-held `idempotency_key`, commit a principal-bound one-step managed job, and remain recoverable through bounded same-response `read_job` follow-up when the current task needs terminal state. Hosted active `read_job` uses a server-side 40-second long-poll by default and returns earlier on meaningful job progress or terminal state; `wait_ms=0` requests an immediate checkpoint, while every public hosted call is capped at 60 seconds. The default remains 40 seconds: live hosted evidence carried both the default and an explicit 60-second read, while beta.151 later reproduced `mcp_network_error` on a second explicit 180-second read even though the Worker-to-daemon WebSocket remained continuously ready. Longer tasks therefore keep the same `job_id` and use another server-paced read rather than one overlong host request. This keeps long-task waiting inside Machine Bridge within the demonstrated per-call host lifetime; the 40-second interval also bounds interaction density to at most 150 reads for a synthetic unchanged 100-minute job, but that arithmetic does not prove that one assistant response can survive the aggregate duration or call count. If a real host/tool boundary ends a response, preserve the durable identifier and resume the same operation later rather than resubmitting its side effect. `start_process` remains daemon-lifetime interactive state; hosted `read_process` permits paced same-response follow-up, defaults an omitted relay `wait_ms` to the one-second blocking cap, and paces another would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary. Active job/process reads do not force a user-turn handoff. Callers must not infer or preempt a host/tool deadline from elapsed wall-clock time: while calls continue to be accepted and the task still needs the result, bounded same-response follow-up may continue. Handoff is reserved for an actual observed host/tool boundary, required external input or authorization, or an explicit user checkpoint, while busy loops and status-surface substitution remain prohibited. The durable process façade preserves account/tool authority and delegated workspace sandbox rather than expanding privileges.
178
178
 
179
179
  `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).
180
180
 
package/SECURITY.md CHANGED
@@ -247,7 +247,7 @@ Destructive state removal validates marker files, selected workspace, known layo
247
247
 
248
248
  ## Relay and denial of service
249
249
 
250
- 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.
250
+ Only one verified daemon is active. Candidates have preflight, hello, readiness, and liveness deadlines. Ordinary replacement requires authentication and end-to-end readiness. A signed same-instance HTTPS takeover naming the exact incumbent connection generation may retire a zombie WebSocket after candidate preconditions pass; the replacement still cannot dispatch ordinary calls until end-to-end readiness is verified.
251
251
 
252
252
  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.
253
253
 
@@ -281,9 +281,9 @@ No logging policy prevents data from being returned to an authorized client that
281
281
 
282
282
  Machine Bridge cannot make arbitrary local executables safe, identify all sensitive data, guarantee cleanup across every power/storage/security failure, override MCP-host or endpoint-security policy, neutralize prompt injection, protect against root or a fully compromised same-user account, or manufacture production signing and governance controls.
283
283
 
284
- See [docs/AUDIT.md](docs/AUDIT.md) for historical findings and residual limitations.
284
+ See [docs/AUDIT.md](docs/AUDIT.md) for current audit conclusions and residual limitations.
285
285
  ## Request-scoped Streamable HTTP delivery
286
286
 
287
287
  MCP `2026-07-28` response streams are request-scoped and non-resumable. SSE frames carry no event IDs, `GET /mcp` is not a recovery channel, and `Mcp-Session-Id` / `Last-Event-ID` do not create protocol-session or replay state. Closing the public response stream cancels that request through a random internal capability that carries neither Authorization nor DPoP credentials.
288
288
 
289
- A brief relay interruption may rebind an already-dispatched pending call only to the same verified daemon instance within the bounded reconnect grace period. That continuity is in memory and remains owned by the initiating HTTP response; it does not persist a terminal result, create client-visible replay state, or authorize retry after the response has ended. Remote compatibility for the declared older initialization dates is likewise stateless and does not restore the removed session model.
289
+ A brief relay interruption may rebind an already-dispatched pending call only to the same verified daemon instance within the bounded reconnect grace period. That continuity is in memory and remains owned by the initiating HTTP response; it does not persist a terminal result, create client-visible replay state, or authorize retry after the response has ended. Both remote HTTP and stdio reject removed initialization and older protocol dates before tool dispatch; neither transport has an initialization compatibility adapter. Transport recovery does not restore the removed session model.
@@ -30,6 +30,6 @@
30
30
  "action": {
31
31
  "default_title": "Machine Bridge Browser"
32
32
  },
33
- "version_name": "3.0.0-beta.192",
33
+ "version_name": "3.0.0-beta.196",
34
34
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
35
35
  }
package/docs/AUDIT.md CHANGED
@@ -9,7 +9,7 @@ This file is a compact statement of current cross-cutting audit conclusions and
9
9
  - **State publication is atomic and identity-sensitive.** Security-sensitive state readers reject symlinks, unexpected hard links, malformed content, permission failures, and persistent identity changes. Bounded retries are reserved for explicitly classified atomic-publication races; durable state is complete before it becomes visible.
10
10
  - **Durable execution owns continuity explicitly.** Managed jobs persist recovery state independently of the hosted MCP response. Idempotency keys bind uncertain acceptance retries only while the retained record exists; cancellation, dependency state, cleanup, output budgets, and terminal persistence have explicit fail-closed boundaries. Callers recover the same accepted job instead of replaying its side effect.
11
11
  - **Daemon-owned Node children use a verified runtime launcher.** A live concrete runtime executable is preferred; if it disappears after a package-manager upgrade, only the original absolute Node launcher is eligible as fallback. Diagnostics expose provenance booleans rather than executable paths, and an unavailable launcher requires service restart instead of PATH guessing.
12
- - **Relay recovery separates transport from execution semantics.** WebSocket and signed HTTPS are transport paths, not permission or replay authorities. Ambiguous side effects are not transparently replayed. Same-daemon terminal-result retention is bounded, cancellation removes ownership, and repeated reconnects cannot accumulate execution time or delivery grace. Relay diagnostics project the same privacy-bounded fallback takeover history across WebSocket and HTTPS control paths.
12
+ - **Relay recovery separates transport from execution semantics and from quota pacing.** WebSocket and signed HTTPS are transport paths, not permission or replay authorities. Their authenticated handshake/resume state is generation-scoped per transport, ended-generation control messages are discarded before reconciliation or interruption, and a protocol violation is routed only to its originating current transport. Ambiguous side effects are not transparently replayed. A verified healthy WSS schedules no recurring HTTPS fallback traffic; fallback starts only for startup grace, transport suspicion, or actual WSS loss, then uses five-second ready-idle polls and thirty-second successful-standby retries while handshake/queued work retains the 750 ms minimum request-start interval. Each accepted signed HTTP request or WebSocket preflight consumes one bounded Durable Object replay-nonce write, while a replay is rejected without another write. The checked-in Wrangler configuration has no Workers KV namespace binding, so repository evidence supports Worker/Durable Object amplification but does not by itself establish a Workers KV quota incident. Same-daemon terminal-result retention is bounded, cancellation removes ownership, and repeated reconnects cannot accumulate execution time or delivery grace. Fatal protocol diagnostics retain only sanitized error/source/generation-class/stage/disposition fields, while fallback takeover history remains privacy-bounded across both control paths.
13
13
  - **Operating-system sleep is an observable suspension boundary, not a network guarantee.** On supported macOS hosts, diagnostics correlate bounded sleep history with runtime stalls and recovered relay outages. Machine Bridge cannot keep JavaScript or a socket executing while the machine is suspended, and sleep correlation does not identify unrelated upstream network causes.
14
14
  - **Browser automation remains existing-profile and snapshot-bound.** The extension controls an already installed Chromium profile; hosted calls bind explicit tab identity, and later Computer Use actions bind the exact observation snapshot. Mutation response loss, timeout, extension replacement, or ambiguous trusted-input settlement yields an unknown/non-retryable boundary that must be inspected before any retry.
15
15
  - **Desktop automation is permission- and evidence-bound.** Application actions use structured Accessibility operations or an explicitly enabled snapshot-bound visual backend. The runtime does not silently substitute arbitrary scripting or unverified pixel guessing when required authority/evidence is unavailable.
@@ -17,6 +17,14 @@ This file is a compact statement of current cross-cutting audit conclusions and
17
17
  - **Resource admission is cooperative scheduling, not a hostile-code sandbox.** CPU, memory, I/O, disk-reserve, queue, and result budgets protect service continuity, but ordinary execution still has the daemon OS user's ambient authority unless a dedicated low-privilege account or VM/container supplies the stronger boundary.
18
18
  - **Release and package evidence remain separate from audit prose.** Shipped-source or packed-document changes require synchronized package identity and fresh verification. Candidate acceptance, publication, activation, and stable promotion must be proven by their dedicated records and external systems; no sentence in this audit summary inherits or confers release authority.
19
19
 
20
+ ## Relay quota incident evidence boundary
21
+
22
+ The quota repair is intentionally split into repository evidence and external-platform evidence. Repository evidence establishes that the prior relay design could generate recurring signed HTTPS traffic even when WebSocket was healthy, that a ready HTTPS fallback polled every second, and that each accepted signed HTTP relay request plus each accepted WebSocket preflight consumed a bounded Durable Object replay-nonce write. The current design keeps HTTPS fallback stopped while WSS is verified healthy, uses a five-second ready-idle HTTPS cadence and thirty-second successful-standby cadence, and keeps handshake/queued traffic on the existing 750 ms minimum start interval. The request/nonce-only regression models idle HTTPS polling alongside continuous failed WSS reconnects; it is not an upper bound across readiness resets or traffic patterns. A separate executed idle-day regression includes the production nonce store and liveness-alarm scheduler, as documented in TESTING.md.
23
+
24
+ The checked-in Wrangler configuration contains Durable Object bindings and no Workers KV namespace binding. That fact narrows the repository-level causal claim: source and local runtime evidence support Worker request amplification plus Durable Object persistence amplification, but they cannot prove that a Cloudflare dashboard item labeled KV came from this repository. Account-side usage/resource records remain the authority for that label. Likewise, local full verification proves only the frozen source candidate; candidate activation, public Worker identity, relay readiness, representative durable execution, and post-activation quota-sensitive behavior require separate live evidence.
25
+
26
+ A reported account-wide Workers/KV quota event must be attributed using provider counters by UTC day, script, namespace, and deployed bindings before assigning a project root cause. The checked-in and inspected deployed bridge use SQLite-backed Durable Object storage, not a Workers KV namespace; another workload can exhaust the shared account allowance. HTTP polling and nonce/alarm mutations are separate resource-amplification risks that require their own regression budgets. Provider identifiers, account data, raw credentials, and request contents belong outside public repository evidence. Local scenario counts do not establish provider billing or a completed production soak.
27
+
20
28
  ## Residual limits
21
29
 
22
30
  - The default full profile is intentionally powerful. It is appropriate only where the daemon OS user is itself an acceptable trust boundary; stronger hostile-code isolation requires a separate OS account or VM/container.
package/docs/LOGGING.md CHANGED
@@ -68,11 +68,11 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
68
68
  - `ready_ack` restores ordinary traffic immediately, but reconnect-attempt history is reset only after five seconds of generation-stable ready uptime. A shorter ready/close flap therefore continues the existing exponential backoff rather than repeatedly returning to the shortest reconnect delay; this stability window is retry pacing only and is not an additional readiness or liveness proof;
69
69
  - a verified replacement is a distinct warning and permanently stops the older daemon;
70
70
  - 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;
71
- - authenticated transports request protocol-level WebSocket Ping every five seconds. A sender callback starts the full ten-second Pong deadline only after actual local dispatch. If that deadline expires on a fully ready WSS, the relay runtime records `relay.transport.suspect`, opens one fifteen-second application-confirmation window, sends a JSON heartbeat, and prewarms HTTPS in standby instead of killing the socket immediately. A later protocol Pong or explicit JSON application `pong` records `relay.transport.recovered` and preserves WSS; ordinary inbound tool/control traffic remains receive-side evidence and cannot clear transport suspicion. Only confirmation expiry records `relay.transport.confirmation_failed` and closes as `relay_transport_timeout`. A thirty-second local Ping-dispatch failure remains the distinct `relay.transport.send_timeout` / `relay_transport_send_timeout` path even if unrelated inbound traffic continues. Send-completion callbacks are exact-WebSocket-generation fenced, so a callback from a superseded socket cannot mutate current relay diagnostics or confirmation state. These anomaly events contain only bounded timing/state fields;
71
+ - authenticated transports request protocol-level WebSocket Ping every five seconds. A sender callback starts the full ten-second Pong deadline only after actual local dispatch. If that deadline expires on a fully ready WSS, the relay runtime records `relay.transport.suspect`, opens one fifteen-second application-confirmation window, sends a JSON heartbeat, and prewarms HTTPS in standby instead of killing the socket immediately. A later protocol Pong or explicit JSON application `pong` records `relay.transport.recovered`, preserves WSS, and stops any HTTPS prewarm started by that suspicion; ordinary inbound tool/control traffic remains receive-side evidence and cannot clear transport suspicion. Only confirmation expiry records `relay.transport.confirmation_failed` and closes as `relay_transport_timeout`. A thirty-second local Ping-dispatch failure remains the distinct `relay.transport.send_timeout` / `relay_transport_send_timeout` path even if unrelated inbound traffic continues. Send-completion callbacks are exact-WebSocket-generation fenced, so a callback from a superseded socket cannot mutate current relay diagnostics or confirmation state. These anomaly events contain only bounded timing/state fields;
72
72
  - a separate periodic twenty-five-second application heartbeat refreshes Worker daemon activity and retains a seventy-five-second application-silence timeout; it begins only after end-to-end readiness, so authenticated probing cannot send a message type that the Worker probing state does not accept. Protocol-level Pong therefore cannot mask a Worker application path that has stopped replying. The Worker queues the heartbeat's JSON `pong` before Durable Object alarm inspection or mutation, then performs one explicit coalesced schedule, so storage latency is not allowed to sit ahead of application-liveness acknowledgement;
73
73
  - a late local transport-watchdog tick is classified as `runtime.event_loop.stall`, sends a fresh transport probe, and defers disconnect for a bounded recovery interval instead of being mislabeled as immediate remote failure; a macOS sleep/wake interval may legitimately produce this warning without a daemon fault. The heartbeat snapshot retains the lag of the last actual stall separately from the rolling maximum. Owner `diagnose_runtime` can compare that end time and duration with a bounded fixed `pmset` sleep-history projection and reports `matched_system_sleep` only when both dimensions agree within the fixed tolerance; an unmatched stall remains unclassified rather than being labeled synchronous JavaScript blockage by elimination. The same diagnostic also intersects the most recent completed relay disconnect interval with that bounded sleep history and reports only outage/overlap timing plus a fixed classification; `majority_system_sleep_overlap` means host suspension dominated the observed outage, so a retained `connection_reset` is aftermath evidence rather than sufficient independent-network evidence. An authorized, schema-valid remote tool call enters macOS activity protection only after policy/account/operation authorization and argument validation succeed; remote process sessions extend that ownership through child settlement. The default `activity` mode keeps the existing shared `/usr/bin/caffeinate -i -s -w <owner-pid>` assertion and fixed thirty-minute rolling inactivity grace. `ac-continuous` adds a daemon-lifetime `-s` assertion, while `continuous` holds `-i -s` for the daemon lifetime and does not arm inactivity grace. Unexpected assertion-child failure retains desired-state ownership and schedules bounded 1/5/30-second recovery; explicit release/shutdown cancels pending recovery. `runtime.idle_sleep_guard.unavailable` still records only a coarse `error_class`, never argv, paths, PID, tool name/content, session identity, or job identity; mode, generation, restart count, recovery-pending state, and bounded unprotected-duration telemetry are exposed through diagnostics rather than logs. Remote account managed-job runners own the same self-healing assertion primitive only after runner-claim confirmation and persisted account ownership validation; local managed jobs do not acquire this remote-continuity assertion. The remote runner fallback stderr diagnostic remains the fixed text `managed job idle-sleep assertion unavailable` plus a sanitized coarse `error_class`, without job name/id, workspace path, argv, environment, or captured output.
74
74
 
75
- A WebSocket close code such as `1006` means the transport ended without a normal close handshake, but it does not identify who initiated termination: Machine Bridge's own liveness recovery calls `terminate()` when a transport/send timeout is confirmed, and that local hard close can surface as 1006. Diagnose the cause from `last_close_category`, transport-confirmation/send-timeout evidence, and retained network milestones rather than treating 1006 itself as proof of a remote/network-initiated close. If it recovers inside ten seconds, the warning-level service log is intentionally silent and the authenticated `daemon.relay_transport` snapshot is the post-event evidence surface. 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.
75
+ A WebSocket close code such as `1006` means the transport ended without a normal close handshake, but it does not identify who initiated termination: Machine Bridge's own liveness recovery calls `terminate()` when a transport/send timeout is confirmed, and that local hard close can surface as 1006. Diagnose the cause from `last_close_category`, transport-confirmation/send-timeout evidence, and retained network milestones rather than treating 1006 itself as proof of a remote/network-initiated close. If it recovers inside ten seconds, the warning-level service log is intentionally silent and the authenticated `daemon.relay_transport` snapshot is the post-event evidence surface. 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. A fatal relay protocol log retains only a sanitized protocol error code, source transport (WebSocket or HTTPS), coarse connection-generation class, handshake stage, and fatal/retry disposition; it never records the numeric session generation, endpoint, credentials, account identity, call IDs, or message payload. Default logs therefore describe the affected layer, duration, classification, and recovery behavior rather than printing raw close envelopes.
76
76
 
77
77
  Streamed-call diagnostics are deliberately coarse. MCP `2026-07-28` request/response-stream ownership and the Worker pending-call registry are memory-only; there is no protocol-session, terminal-result, subscriber, or replay store. Worker event counters are scoped to the current isolate and say so in `metric_scope`, while `server_info` exposes only bounded current pending-call/capacity state. These diagnostics do not prove public SSE consumption or MCP-host receipt. Logs and `server_info` must not include tool arguments, terminal results, command text, request keys, account identifiers, raw call IDs, raw connection generations, mirrored parameter values, private paths, or subscriber payloads. A stale-generation result is counted as unmatched rather than logged with its envelope.
78
78
 
@@ -93,9 +93,9 @@ After the host path recovers, compare authenticated `server_info`, `machine-mcp
93
93
 
94
94
  ### Relay interruption messages
95
95
 
96
- A reconnect warning proves a transport interruption, not a daemon crash. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, `daemon.relay_transport.last_close_category`, `last_close_code`, `outage_count`, `recent_outages`, `outage_attempts`, `previous_ready_inbound_silence_ms`, `last_connect_milestones_ms`, and the coarse network-route class. In each completed `recent_outages` entry, `disconnected_at` is the first transition into that outage, `last_disconnect_at` is the final failed reconnect/close transition, `ready_at` is verified recovery, and `duration_ms` measures first-disconnect-to-ready time. The bounded `probe_dispatch_pending_at_start`, `probe_dispatch_age_ms_at_start`, `probe_outstanding_at_start`, `probe_age_ms_at_start`, `transport_confirmation_pending_at_start`, and `application_inbound_silence_ms_at_start` fields freeze the liveness phase at that first transition. Use them to test hypotheses such as a failure coinciding with the first five-second protocol Ping, but do not treat that timing correlation as proof that Ping caused the reset. Also distinguish a planned restart from an accidental outage: a current-generation daemon sends `daemon_draining` before relay close, affected calls receive `reason=daemon_planned_drain`, and owner/full `server_info.worker.continuity_evidence` schema 2 durably retains the planned-drain count/time plus bounded socket-disconnect and client-cancellation observations across Worker isolate replacement. Treat `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` as the qualified durable socket-outage evidence; the broader `socket_disconnects` counters include candidate/probing transport churn. `last_socket_disconnect.role`, `was_ready`, and `connected_at` identify whether the newest first-observed close/error had ever become the authoritative ready channel. Planned-drain intent is serialized into the WebSocket attachment before close so a Durable Object isolate replacement cannot turn a planned shutdown into an unplanned disconnect. Schema-1 disconnect counts are intentionally reset during the schema-2 transition because old isolates could count stale/expired close callbacks more than once. `worker.observability.continuity` remains isolate-local and may reset; use the qualified durable summary for post-incident correlation rather than relying on a later close-1006 inference. For hosted `read_job`, recover with the same returned `job_id` plus its `recovery_key`; do not resubmit the job's underlying mutation. If the job predates capability issuance or its capability was never delivered, use local CLI/stdio administration instead of attempting hosted global discovery. `last_connect_milestones_ms` contains only bounded relative timings for the most recent connection attempt phases such as DNS resolution, TCP connect, TLS establishment, HTTP rejection, and WebSocket open; `last_failed_connect_stage`, `last_failed_connect_duration_ms`, `last_failed_connect_milestones_ms`, and `last_failed_connect_http_status` retain the most recent failed attempt even after a later retry succeeds. `last_transport_error_ready` and `last_transport_error_authenticated` distinguish failure of an already-established channel from a pre-readiness connection failure. `last_transport_error_reason` is a strict privacy-safe allowlist (`connection_reset`, `connection_timeout`, `network_unreachable`, bounded DNS/TLS classes, or `unknown`) rather than the raw operating-system message. The signed HTTPS fallback retains its last error class/reason after a later successful poll while resetting the current `http_poll_failures` count, so post-recovery diagnosis can determine whether WSS and HTTPS failed through the same system-network episode. None of these fields contains a hostname, address, DNS answer, certificate, close reason, or proxy endpoint. While no daemon channel is ready, `server_info.daemon.previous_connection` retains only the last verified channel's transport, connected/last-seen/disconnected timestamps, and sanitized relay diagnostics; it excludes policy, tools, account identity, daemon instance/connection identity, call IDs, arguments, and results, and it never participates in routing or authorization. `outage_duration_ms` measures the close-to-ready recovery episode; `previous_ready_inbound_silence_ms` measures how long the preceding ready socket had stopped producing inbound transport proof before it actually closed. The second value is therefore the field that exposes a black-holed OPEN WebSocket whose visible reconnect later completes quickly. A VPN/TUN UI may remain “connected” while its upstream route is unusable. Local OS logs can be compared with the exact `last_disconnected_at` timestamp, but an interface-quality change or tunnel-process correlation is not by itself proof of which product, node, edge, or upstream failed. Correlated failure of an independent HTTPS client at the same timestamp—for example an external API `unexpected EOF` while the relay records WebSocket 1006/`connection_reset`—is stronger evidence of a shared system-network/VPN/TUN episode than of a Machine Bridge event-loop or resource-admission failure; it still does not identify the failing tunnel node or upstream provider. Machine Bridge reports only coarse route/proxy classes and never sends or logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
96
+ A reconnect warning proves a transport interruption, not a daemon crash. When the daemon instead exits on a relay protocol error, inspect the same default service-log entry for the sanitized protocol error code, source transport, coarse generation class, handshake stage, and disposition; those fields distinguish a current-generation fatal WebSocket violation from a retryable HTTPS fallback violation without exposing raw connection identifiers. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, `daemon.relay_transport.last_close_category`, `last_close_code`, `outage_count`, `recent_outages`, `outage_attempts`, `previous_ready_inbound_silence_ms`, `last_connect_milestones_ms`, and the coarse network-route class. In each completed `recent_outages` entry, `disconnected_at` is the first transition into that outage, `last_disconnect_at` is the final failed reconnect/close transition, `ready_at` is verified recovery, and `duration_ms` measures first-disconnect-to-ready time. The bounded `probe_dispatch_pending_at_start`, `probe_dispatch_age_ms_at_start`, `probe_outstanding_at_start`, `probe_age_ms_at_start`, `transport_confirmation_pending_at_start`, and `application_inbound_silence_ms_at_start` fields freeze the liveness phase at that first transition. Use them to test hypotheses such as a failure coinciding with the first five-second protocol Ping, but do not treat that timing correlation as proof that Ping caused the reset. Also distinguish a planned restart from an accidental outage: a current-generation daemon sends `daemon_draining` before relay close, affected calls receive `reason=daemon_planned_drain`, and owner/full `server_info.worker.continuity_evidence` schema 2 durably retains the planned-drain count/time plus bounded socket-disconnect and client-cancellation observations across Worker isolate replacement. Treat `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` as the qualified durable socket-outage evidence; the broader `socket_disconnects` counters include candidate/probing transport churn. `last_socket_disconnect.role`, `was_ready`, and `connected_at` identify whether the newest first-observed close/error had ever become the authoritative ready channel. Planned-drain intent is serialized into the WebSocket attachment before close so a Durable Object isolate replacement cannot turn a planned shutdown into an unplanned disconnect. Schema-1 disconnect counts are intentionally reset during the schema-2 transition because old isolates could count stale/expired close callbacks more than once. `worker.observability.continuity` remains isolate-local and may reset; use the qualified durable summary for post-incident correlation rather than relying on a later close-1006 inference. For hosted `read_job`, recover with the same returned `job_id` plus its `recovery_key`; do not resubmit the job's underlying mutation. If the job predates capability issuance or its capability was never delivered, use local CLI/stdio administration instead of attempting hosted global discovery. `last_connect_milestones_ms` contains only bounded relative timings for the most recent connection attempt phases such as DNS resolution, TCP connect, TLS establishment, HTTP rejection, and WebSocket open; `last_failed_connect_stage`, `last_failed_connect_duration_ms`, `last_failed_connect_milestones_ms`, and `last_failed_connect_http_status` retain the most recent failed attempt even after a later retry succeeds. `last_transport_error_ready` and `last_transport_error_authenticated` distinguish failure of an already-established channel from a pre-readiness connection failure. `last_transport_error_reason` is a strict privacy-safe allowlist (`connection_reset`, `connection_timeout`, `network_unreachable`, bounded DNS/TLS classes, or `unknown`) rather than the raw operating-system message. The signed HTTPS fallback retains its last error class/reason after a later successful poll while resetting the current `http_poll_failures` count, so post-recovery diagnosis can determine whether WSS and HTTPS failed through the same system-network episode. None of these fields contains a hostname, address, DNS answer, certificate, close reason, or proxy endpoint. While no daemon channel is ready, `server_info.daemon.previous_connection` retains only the last verified channel's transport, connected/last-seen/disconnected timestamps, and sanitized relay diagnostics; it excludes policy, tools, account identity, daemon instance/connection identity, call IDs, arguments, and results, and it never participates in routing or authorization. `outage_duration_ms` measures the close-to-ready recovery episode; `previous_ready_inbound_silence_ms` measures how long the preceding ready socket had stopped producing inbound transport proof before it actually closed. The second value is therefore the field that exposes a black-holed OPEN WebSocket whose visible reconnect later completes quickly. A VPN/TUN UI may remain “connected” while its upstream route is unusable. Local OS logs can be compared with the exact `last_disconnected_at` timestamp, but an interface-quality change or tunnel-process correlation is not by itself proof of which product, node, edge, or upstream failed. Correlated failure of an independent HTTPS client at the same timestamp—for example an external API `unexpected EOF` while the relay records WebSocket 1006/`connection_reset`—is stronger evidence of a shared system-network/VPN/TUN episode than of a Machine Bridge event-loop or resource-admission failure; it still does not identify the failing tunnel node or upstream provider. Machine Bridge reports only coarse route/proxy classes and never sends or logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
97
97
 
98
- Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. For the default portable JWK root, the runtime derives a fresh root-certified ephemeral session ten minutes before expiry, swaps the shared WSS/HTTPS authentication provider, and reconnects the same daemon instance so existing reconnect reconciliation can resume in-flight relay calls without `runtime.stop()`. If the machine was suspended across the renewal timer, the next authentication boundary synchronously renews before signing instead of first attempting an expired certificate. `server_info.runtime.trust.daemon_session` exposes only coarse renewal state such as `automatic_renewal`, `session_generation`, `expires_at`, `renewal_due_at`, and failure class/count; it never exposes session keys or certificate material. A Secure Enclave root does not receive unattended renewal because signing may require user presence. If unattended renewal is unavailable or repeatedly fails until the session is truly expired, the existing fail-closed path still terminates with `relay_device_session_expired` rather than retrying forever with unusable credentials; installed launchd/systemd/Windows supervision may then restart the daemon and obtain a new root-signed session, while a manually run daemon requires operator restart. Ordinary standby/ready fallback requests retain the seven-second deadline; exact-generation takeover requests use a three-second deadline until verified readiness. The ordinary one-second ready poll cadence, five-second standby-prewarm cadence, bounded one/two/four/five-second retry backoff, 750 ms hard minimum request-start interval, and twelve-second liveness window are unchanged. A new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget; the takeover budget is deliberately small enough for one fully timed-out stale request, the first retry backoff, and two further takeover exchanges to fit inside that ceiling. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, authorized relay activity is protected only after policy/account/operation authorization and argument validation succeed, and relay heartbeats do not count as user activity. The persisted idle-sleep mode is `activity` by default, `ac-continuous`, or `continuous`: `activity` retains the existing shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion plus fixed thirty-minute inactivity grace; `ac-continuous` adds a daemon-lifetime `-s -w <daemon-pid>` assertion while keeping that activity lease; `continuous` holds `-i -s -w <daemon-pid>` for the daemon lifetime and does not arm inactivity grace. A remote `start_process` extends activity ownership only after resource admission and through child settlement. Remote account managed-job runners independently hold the same `-i -s -w <runner-pid>` primitive after runner-claim/account-ownership validation and through admission, steps, cleanup, and terminal persistence. Every assertion retains desired-state recovery after unexpected child failure with fixed 1/5/30-second backoff; explicit release/shutdown disables recovery before terminating the child. `diagnose_runtime.runtime.idle_sleep_guard` reports only coarse mode/activity/recovery ownership and bounded unprotected-duration telemetry; it does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing daemon assertions. None of these modes claims to prevent explicit sleep, lid-close sleep, power loss, or operating-system behavior outside `caffeinate` contracts. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
98
+ Brief retryable outages recover automatically. On a verified current daemon channel, `server_info.daemon.relay_transport.outage_active=false`; retained fields describe the immediately preceding transport episode rather than claiming a current outage. WebSocket remains preferred and requests a protocol-level probe after five seconds. Calling `ws.ping()` only queues the control frame; it is not treated as remote-probe dispatch until the WebSocket sender's write callback confirms that the Ping actually left the local send queue. The local sender has a separate thirty-second bounded dispatch window, while a confirmed Ping retains its full ten-second Pong deadline. This deliberately prevents compression/backpressure or a slow local socket queue from spending the remote-response budget before any probe was transmitted. A protocol Pong that arrives while a Ping callback is still pending records bidirectional proof for that dispatch round; if the local write callback then completes inside the thirty-second dispatch budget, it does not arm a stale future Pong deadline. Unrelated application inbound is receive-side evidence only and cannot prove the daemon-to-Worker direction. A local queue whose Ping write callback still has not completed after thirty seconds is classified as `relay_transport_send_timeout` even if unrelated inbound traffic continues. One dispatched Ping that reaches the ten-second response deadline does not hard-kill an otherwise ready WSS: the relay runtime enters a fifteen-second `transport_confirmation_pending` window, sends the existing JSON application heartbeat as an independent path check, and prewarms signed HTTPS in standby without taking ownership away from the still-ready WSS. A protocol Pong or the explicit JSON application `pong` clears suspicion and stops standby prewarm; ordinary tool/control traffic does not, because it proves only the Worker-to-daemon receive direction. Only a second-stage confirmation window that receives no application `pong` becomes `relay_transport_timeout` and terminates the WSS. This keeps a true black hole bounded while no longer amplifying a roughly ten-to-fifteen-second persistent-flow stall into an immediate reconnect storm. `heartbeat.probe_dispatch_*`, `heartbeat.transport_confirmation_*`, and bounded sender-backlog fields distinguish local send delay, first-stage response loss, successful second-stage recovery, and confirmed two-stage failure. The separate periodic JSON application heartbeat remains twenty-five seconds with a seventy-five-second application-silence timeout, begins only after verified relay readiness, and the Worker keeps a wider ninety-second WebSocket liveness fallback. This is a detection/recovery bound, not a guarantee that a degraded network can complete another WebSocket handshake inside the same interval. WebSocket connect attempts have a thirty-second outer budget so a degraded but still valid DNS/TCP/TLS/WebSocket upgrade is not misclassified by an unrealistically narrow connection cutoff. The daemon also explicitly disables client `permessage-deflate`: the relay carries bounded control/JSON traffic, while `ws` enables compression by default on clients and compression adds sender-state/CPU overhead that can queue later frames; the stability path does not need that optional negotiation. The fallback still begins independently rather than waiting thirty seconds for WSS. On first-stage WSS liveness suspicion, the same root-certified ephemeral daemon identity prewarms signed HTTPS in standby; if WSS proves live during the second-stage confirmation, that standby poller stops. If the WSS actually disconnects, fallback switches to exact-generation takeover immediately; an in-flight standby request is aborted and replaced rather than being allowed to consume up to its own request deadline before takeover can start. That in-memory session certificate intentionally has a 24-hour maximum lifetime. For the default portable JWK root, the runtime derives a fresh root-certified ephemeral session ten minutes before expiry, swaps the shared WSS/HTTPS authentication provider, and reconnects the same daemon instance so existing reconnect reconciliation can resume in-flight relay calls without `runtime.stop()`. If the machine was suspended across the renewal timer, the next authentication boundary synchronously renews before signing instead of first attempting an expired certificate. `server_info.runtime.trust.daemon_session` exposes only coarse renewal state such as `automatic_renewal`, `session_generation`, `expires_at`, `renewal_due_at`, and failure class/count; it never exposes session keys or certificate material. A Secure Enclave root does not receive unattended renewal because signing may require user presence. If unattended renewal is unavailable or repeatedly fails until the session is truly expired, the existing fail-closed path still terminates with `relay_device_session_expired` rather than retrying forever with unusable credentials; installed launchd/systemd/Windows supervision may then restart the daemon and obtain a new root-signed session, while a manually run daemon requires operator restart. Ordinary standby/ready fallback requests retain the seven-second deadline; exact-generation takeover requests use a three-second deadline until verified readiness. Verified healthy WSS operation keeps HTTPS fallback stopped. When fallback is needed, the ready-idle poll cadence is five seconds, successful standby probes retry after thirty seconds, network failures retain the bounded one/two/four/five-second retry backoff, handshake or queued outbound work retains the 750 ms hard minimum request-start interval, and the twelve-second liveness window is unchanged. A new daemon-backed call waits at most fifteen seconds for some verified daemon channel, and the measured wait is deducted from that call's original execution budget; the takeover budget is deliberately small enough for one fully timed-out stale request, the first retry backoff, and two further takeover exchanges to fit inside that ceiling. After an established WSS disappears, the daemon explicitly marks its signed HTTP request as a takeover of the Worker-issued `connection_id` for that exact disconnected WebSocket generation. Once candidate preconditions pass, HTTPS may retire only that targeted same-instance zombie WSS that the Worker has not yet observed closing. If a newer same-instance WSS is already ready before the HTTP request arrives, the old generation no longer matches and the stale takeover remains standby instead of retiring the recovered socket. A takeover request without the exact Worker-issued WebSocket connection ID is invalid rather than being treated as an instance-only legacy takeover. Malformed, stale, wrongly targeted, or different-instance requests cannot preempt a healthy incumbent. During replacement, the daemon reconciles `resume_calls`, processes `ready_ack`, proves local readiness, and only then returns `resume_calls_ack.missing_ids`. A missing ID therefore proves both that the same daemon has no active/unacknowledged-result ownership for that call and that the replacement channel is ready. If the initiating MCP response is still open and at least one second remains in the original execution budget, the Worker may transparently retransmit exactly that same call ID, arguments, authority, and a reduced timeout. `read_job` is stricter: redelivery requires the full ten-second reconciliation headroom to remain, otherwise the Worker declines redelivery and returns retryable recovery failure rather than rewriting the call into an under-budget immediate read. If safe redelivery cannot be accepted, the call falls back to retryable `unavailable` with `side_effects_started=false`. Calls that may have executed, retained terminal results, different-daemon calls, and ambiguous mutations are never automatically replayed. Completed relay results that are still waiting for Worker acknowledgement remain bounded in daemon memory and consume the same recovery-ownership capacity as active calls: 16 total with two control-plane slots reserved for `diagnose_runtime`/`list_roots`. When ordinary recovery ownership reaches 14, another ordinary relay call is rejected before execution with retryable `limit_exceeded` and `side_effects_started=false`; the two reserved diagnostic/recovery calls remain available until total capacity reaches 16. The retained-result implementation also keeps one non-admission emergency ownership slot solely for a violated internal capacity invariant: if an already-executed result reaches retention after the normal 16-entry ceiling is unexpectedly full, that one result remains retained for acknowledgement/reconnect ownership instead of being sent unowned and later misclassified as safe to redeliver. Use of that slot emits an error-level capacity event and may make diagnostics temporarily report ownership above the normal maximum; a second such overflow is not sent. This slot is not usable admission capacity and must never be counted to raise the 16-call execution ceiling. An acknowledgement that is permanently lost cannot pin a result forever: first retention is monotonic and the result expires after the 315-second maximum Worker settlement lifetime on the next live relay heartbeat; the disconnected path still uses the shorter reconnect-grace cleanup. `diagnose_runtime.runtime.relay_result_recovery` exposes only aggregate `active_calls`, `retained_results`, active ownership, and capacity counts—never call IDs, tool arguments, or results. Compare outage intervals with sleep/wake records and `diagnose_runtime.runtime.relay.heartbeat` before classifying them as active network faults; local stdio `server_info.runtime.relay.heartbeat` exposes the same state. On macOS, authorized relay activity is protected only after policy/account/operation authorization and argument validation succeed, and relay heartbeats do not count as user activity. The persisted idle-sleep mode is `activity` by default, `ac-continuous`, or `continuous`: `activity` retains the existing shared `/usr/bin/caffeinate -i -s -w <daemon-pid>` assertion plus fixed thirty-minute inactivity grace; `ac-continuous` adds a daemon-lifetime `-s -w <daemon-pid>` assertion while keeping that activity lease; `continuous` holds `-i -s -w <daemon-pid>` for the daemon lifetime and does not arm inactivity grace. A remote `start_process` extends activity ownership only after resource admission and through child settlement. Remote account managed-job runners independently hold the same `-i -s -w <runner-pid>` primitive after runner-claim/account-ownership validation and through admission, steps, cleanup, and terminal persistence. Every assertion retains desired-state recovery after unexpected child failure with fixed 1/5/30-second backoff; explicit release/shutdown disables recovery before terminating the child. `diagnose_runtime.runtime.idle_sleep_guard` reports only coarse mode/activity/recovery ownership and bounded unprotected-duration telemetry; it does not enumerate process-session or job identities. Runtime shutdown terminates process sessions before releasing daemon assertions. None of these modes claims to prevent explicit sleep, lid-close sleep, power loss, or operating-system behavior outside `caffeinate` contracts. A nonzero `event_loop_stall_count` with a large `max_event_loop_lag_ms` means the local daemon was not scheduled promptly; a short stall enters recovery grace, sends a fresh transport probe, and deliberately postpones disconnect. A large stall that aligns with `pmset` Sleep/Wake is suspension evidence. Independently, `relay_outage_analysis` can show either that a close-to-ready interval was itself dominated by system sleep or that the socket failure was first observable at the wake boundary while the same sleep matches the runtime pause; a genuinely awake outage without either evidence remains real transport evidence without identifying which network/host layer caused it. A large `previous_ready_inbound_silence_ms` without a matching local stall remains useful pre-close half-open evidence. Use `--verbose` only when close codes, liveness deadlines, and retry delays are required.
99
99
 
100
100
  A foreground MCP response is not durable delivery. For an active hosted `start_job`, `ui_monitor_candidate=true` plus `ui_monitor_render_tool=render_job_monitor` and a fresh `ui_monitor_id` means the host advertised MCP Apps support, but `start_job` itself remains data-only and does not mount UI. **Job polling ownership is not task ownership.** If the current assistant still has task-local work that depends on this job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls in the same response while calls are accepted. Render only when the current task can deliberately transfer this job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`; it verifies existing read authority, activates that pre-issued correlation ID, and mounts the Job Monitor resource. Do not depend on the render tool result being visible to the initiating planner. Immediately call `read_job` with the same `job_id`, `recovery_key`, and start-result `ui_monitor_id`. A matching current View claim on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; that proves the View can own subsequent status polling for this job only. The sole exception is a persisted `continuation_mode=task_supervisor` whose reviewed durable plan already encapsulates all remaining noninteractive task work: a later active claimed read may report `host_turn_handoff_recommended=true`, allowing the host turn to end because the supervisor job owns the remaining execution lifecycle. Machine Bridge can prove the persisted declaration, active state, and current View claim, but not conversation-level completeness; that remains an owner/caller semantic assertion. The monitor itself remains status-only. The mounted View then uses app-only `read_job_monitor`, not the model-visible `read_job`, for its fixed 40-second status long-polls. That app-only call verifies the same principal-bound `recovery_key`, strips the recovery/monitor values before daemon dispatch, and uses a 60-second client response watchdog and may be retried by the View for at most six attempts with bounded 1/2/4/8/15/30-second backoff after a missing/transport-failed response or an explicitly retryable tool error; it never re-submits the managed job. Exhausted retries and non-retryable status errors pause the View for manual inspection. An older View claim with another monitor ID is not handoff evidence. Hosted synchronous calls reserve room for Worker and host settlement instead of occupying the complete interaction window: ordinary daemon-backed tools default to 20 seconds of remote execution plus a separate five-second Worker settlement margin; ordinary configurable browser/application foreground tools also default to 20 seconds, while compound `computer_observe` and `computer_act` default to 30 seconds; all configurable browser/application foreground tools retain their explicit 45-second maximum. Remote `exec_command`, `run_process`, and `run_local_command` no longer keep the child process inside that response lifetime. Each remote process request must carry a unique caller-held `idempotency_key` before dispatch; reuse that same key only when recovering an ambiguous acceptance response. The daemon commits the authorized operation as a principal-bound one-step managed job, launches it with interactive resource-admission priority, and returns a `job_id` inside a 10-second acceptance budget; the Worker keeps a separate five-second settlement margin and adds principal-bound `recovery_key`/`control_key` capabilities to the hosted result. Preserve all three together: hosted `read_job` requires the read capability, hosted `cancel_job` requires the control capability, and a bare `job_id` is not remote recovery authority. If that acceptance response is lost to settlement timeout, HTTP response cancellation, or relay reconnect expiry after dispatch, the public error remains non-retryable for generic callers but carries the original key and the explicit recovery action `retry_same_tool_arguments_with_same_idempotency_key`; this reconciles against the retained job instead of authorizing a blind duplicate. The detached child may execute for up to 600 seconds after admission, but the managed runner can separately wait up to thirty minutes for cooperative machine-user resource admission before the child is spawned; the child execution deadline begins only after that admission succeeds. The shared ceiling is exposed machine-readably as `server_info.tool_delivery.managed_job_resource_admission_wait_max_ms`, because the same pre-spawn boundary applies to ordinary durable process jobs and owner `start_job` steps rather than to process tools alone. While the runner is in this pre-spawn state, `read_job.current_phase` is `resource_admission`; no command has started yet. An owner can correlate a long-running status at that phase with `diagnose_runtime.runtime.resource_admission` rather than interpreting it as a slow child process; a delegated non-owner should treat the phase itself as evidence that the child has not spawned, retain the same `job_id`, and avoid blind replay rather than attempting the owner-only machine-wide diagnostic. After admission, the phase returns to `steps`, `finally_steps`, or `recovery-cleanup` as appropriate. Completed step records preserve `duration_ms` as the total orchestration duration. Local/owner reads additionally expose `resource_admission_ms` as the pre-spawn portion so a delayed successful child can be distinguished from slow execution after the fact; delegated non-owner reads omit that machine-user scheduling timing rather than turning shared-host contention into a more precise cross-workload signal. The detached job survives MCP disconnect, relay reconnect, daemon restart, or service replacement. Non-owner process authority is unchanged: automatic durable execution still uses the delegated workspace sandbox and does not grant owner-only `start_job`. If a cached host schema omits a current required field, the Worker rejects before daemon dispatch with a normal no-side-effect tool error and requests a `tools/list` refresh rather than surfacing a protocol-only validation failure. Discovery instructions and tool descriptions both carry orchestration semantics, so `server/discover` and `tools/list` each advertise `ttlMs=0` and every host-visible tool description carries `Tool schema generation N`. `server_info.tool_delivery.tool_schema_generation`, `tool_schema_server_version`, `discovery_ttl_ms`, and `tool_list_ttl_ms` identify the live contract; `host_visible_schema_known_to_server=false` is equally important because a healthy new daemon/Worker cannot prove that an external host discarded an older cached action/tool snapshot. `host_turn_deadline_observable=false` means Machine Bridge cannot pre-compute the external assistant-turn deadline, while `managed_jobs_detached_from_mcp_response=true` records that an accepted durable job is not owned by that response lifetime. After an activation that changes hosted semantics, compare the live `server_info` generation and changed invocation behavior with the governed Workspace Action control snapshot when that product layer is applicable; automation may perform the supported refresh/review path without another conversational approval. Host-internal cache inspection is intentionally excluded from operational release verification. `start_process` remains the explicit daemon-lifetime path when interactive stdin or session-style incremental output is required, but hosted calls use a 10-second execution / 15-second settlement envelope and do not queue behind resource pressure: the first failed admission returns retryable `unavailable`; owner-local callers retain the cooperative wait. Hosted `read_process` supports paced same-response follow-up: each actual output/exit blocking wait lasts at most one second. If another would-block remote read arrives inside the fifteen-second blocking cooldown, the daemon keeps that same MCP call open until output/exit or the cooldown boundary rather than returning an immediate running checkpoint; the Worker reserves enough execution/settlement headroom for that server-side pacing. Results use `status_polling_mode=paced_followup` while the process remains live, plus `blocking_poll_throttled` and `next_blocking_poll_after_ms`; callers must not busy-loop and should respect that cooldown. A new hosted call waits at most fifteen seconds for daemon readiness, but that wait is charged against the call's existing execution budget. For an already-dispatched call, a disconnect never pauses or extends the original execution/redelivery deadline. The Worker may separately retain the same in-memory result-settlement owner for one fixed fifteen-second window beyond its original settlement deadline, including when that original settlement deadline was already at the ordinary per-tool ceiling, solely so an already-executed terminal result from the verified same daemon can still reach the original request. Repeated disconnect/rebind cycles cannot accumulate that delivery grace, cancellation still removes the owner, and the longer settlement deadline is never used to authorize another execution. Reconnect retention is bounded by reconnect grace and that fixed delivery deadline. Diagnostics distinguish `original call deadline expired during reconnect`, `terminal result delivery grace expired during reconnect`, and a true full `reconnect grace expired` rather than collapsing those separate boundaries. Owner-local stdio/CLI calls retain their synchronous local contract because they do not depend on a hosted response stream. Keep unrelated mutations and verification independently terminal, and never infer task success merely because a durable launch was accepted. For one coherent non-interactive sequence, prefer a repository umbrella command or multi-step `start_job` rather than creating many one-step durable process carriers. If the current task needs the result, hosted `read_job` may follow the known durable `job_id` with its preserved `recovery_key` repeatedly in the same assistant response until terminal state while calls continue to be accepted; active relay reads report `status_polling_mode=bounded_followup` and no longer recommend forced handoff. The normal hosted read is a 40-second server-side long-poll. Terminal settlement returns on the next bounded five-second internal poll; nonterminal status/phase/dependency progress is coalesced for at least 30 seconds by default, and `current_step`-only churn does not wake the hosted call. `wait_ms=0` is the explicit immediate-checkpoint mode, while public hosted `wait_ms` is capped at 60 seconds. The default stays at 40 seconds because live host evidence showed that overlong single requests can outlive the host invocation even though the durable job itself remains healthy; beta.151 reproduced that class with a second explicit 180-second `read_job` returning `mcp_network_error` while generation-18 continuity evidence recorded zero unplanned ready-socket disconnects. The coalescing floor reduces host-visible event density and does not shorten the managed job, the assistant task, or the six-hour managed-step ceiling. Do not busy-loop, do not replace server-side pacing with rapid immediate reads, do not use repeated `list_jobs`, `server_info`, or `diagnose_runtime` calls as substitute polling surfaces, and do not infer or preempt a host/tool deadline from elapsed wall-clock time. Hosted `list_jobs` is aggregate-only and intentionally cannot rediscover lost job IDs/names/recovery handles; detailed global inventory remains local CLI/stdio administration. Return the `job_id`, status, and current phase for later recovery only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint; only a terminal status is task-completion evidence.
101
101
 
@@ -444,3 +444,7 @@ Current audit conclusions and residual review requirements are in [AUDIT.md](AUD
444
444
  ### Worker pending-call budget
445
445
 
446
446
  `server_info.worker.pending_calls` reports bounded aggregate in-memory dispatched ownership, pre-dispatch daemon waiters, and their shared capacity. MCP request bodies and terminal results are not written as a delivery/replay log, and there is no `durable_budget` MCP stream counter. Unexpected growth in active/detached pending calls or pre-dispatch waiters, repeated reconnect expiry, or sustained admission pressure should block release and be diagnosed against daemon readiness and relay health.
447
+
448
+ ### Quota attribution and cooldowns
449
+
450
+ Attribute an account quota event by UTC day, Worker script, KV namespace, and deployed bindings before assigning a project root cause. Workers KV and SQLite-backed Durable Object storage are different products. Idle relay scenario counts include nonce writes and recurring liveness-alarm schedules; they do not bound business traffic, other daemons, WSS reconnects, or other account workloads. The source repository testing guide describes the executed idle-day scenario and its exclusions. HTTP 429/503 `Retry-After` applies a monotonic cooldown bounded to one hour; queue activity and relay lifecycle changes cannot shorten it. A cooldown beyond liveness invalidates readiness until a fresh successful handshake.
package/docs/PRIVACY.md CHANGED
@@ -111,4 +111,4 @@ The MCP Apps managed-job monitor keeps that bearer boundary narrow rather than c
111
111
 
112
112
  Durable Object monitor coordination is metadata-only and remains subordinate to the durable job/authority result it decorates. A storage failure while issuing optional monitor metadata after `start_job` acceptance returns the already-projected accepted job without a UI candidate; a storage failure while probing claim evidence after a successful `read_job` means “no proven handoff” and retains normal model-side continuation. Authority revocation does not become dependent on best-effort monitor-record cleanup: the core revocation still completes, cleanup failure is represented only by bounded operation/error-class telemetry, and the principal-bound recovery capability continues to be revalidated on every app read. Monitor coordination therefore cannot turn an already accepted job or completed revocation into an ambiguous business operation, and neither logs nor persistent monitor state acquire job output or bearer material.
113
113
 
114
- The signed daemon HTTPS fallback does not change those persistence rules. It carries the same daemon control/tool envelopes over TLS when WSS is unavailable and retains only bounded in-memory transport queues on each side. An unacknowledged envelope may be retransmitted with the same transport sequence after response loss, but duplicate sequences are discarded before business handling; no public MCP replay identifier or persisted fallback result store is created. Fallback authentication headers contain a short-lived root-certified session certificate, one-time nonce, timestamp, body hash, and signature; they are validated but not written to operational logs. Relay diagnosis retains only bounded numeric connect/probe timing, sender-backlog byte counts, and strict allowlisted transport reasons such as `connection_timeout` or `network_unreachable`; it never stores the raw socket error, hostname, address, DNS answer, certificate, VPN interface/node, proxy endpoint, arguments, or results. The fallback may retain its last coarse error class/reason after recovery so a later operator can correlate WSS and HTTPS failure windows, while current health remains represented separately by readiness and the current failure count. Browser broker ping/Pong carries only a bounded sequence number and is not content logging.
114
+ The signed daemon HTTPS fallback does not change those persistence rules. It carries the same daemon control/tool envelopes over TLS when WSS is unavailable and retains only bounded in-memory transport queues on each side. An unacknowledged envelope may be retransmitted with the same transport sequence after response loss, but duplicate sequences are discarded before business handling; no public MCP replay identifier or persisted fallback result store is created. Fallback authentication headers contain a short-lived root-certified session certificate, one-time nonce, timestamp, body hash, and signature; they are validated but not written to operational logs. Replay protection does persist only the accepted nonce plus expiry inside bounded Durable Object nonce maps: HTTP relay nonces and WebSocket preflight nonces use separate keys and fixed entry ceilings, expired entries are pruned on consumption, a replay is rejected without another write, and certificate/signature/body-hash values are not copied into that replay store. Relay diagnosis retains only bounded numeric connect/probe timing, sender-backlog byte counts, and strict allowlisted transport reasons such as `connection_timeout` or `network_unreachable`; it never stores the raw socket error, hostname, address, DNS answer, certificate, VPN interface/node, proxy endpoint, arguments, or results. The fallback may retain its last coarse error class/reason after recovery so a later operator can correlate WSS and HTTPS failure windows, while current health remains represented separately by readiness and the current failure count. Browser broker ping/Pong carries only a bounded sequence number and is not content logging.
@@ -37,14 +37,14 @@ The hosted MCP client, its prompts, tools, extensions, and retrieved content are
37
37
  - OAuth state, PKCE, redirect URI, resource binding, and request size;
38
38
  - account status, account version, account role, trusted client binding, token expiry, and refresh-family state;
39
39
  - DPoP proof method, target URL, timestamp, unique identifier, access-token hash, and key thumbprint when DPoP is used;
40
- - Native MCP `2026-07-28` per-request protocol metadata, valid positive `Accept` quality values, actual `/mcp` Origin, header/body consistency for version/method/name/declared primitive parameters, request IDs, role-visible tool exposure, raw argument schemas, and response-stream cancellation. Remote HTTP initialization compatibility is limited to the declared `2025-06-18`/`2025-11-25` dates and the bounded `initialize`/`notifications/initialized`/`ping`/`tools/list`/`tools/call` method set; it dispatches tools only through the current controller and cannot create `Mcp-Session-Id`, recovery/replay state, or a second authorization/session model. Other removed initialization/session requests receive bounded upgrade guidance only.
41
- - The daemon HTTPS fallback is a transport failover surface, not an MCP endpoint or alternate authority. Each POST is authenticated by the same root-certified ephemeral P-256 device session as the daemon, and its signature binds the fixed route, Worker origin, server/version, a 30-second nonce/timestamp window, and exact body SHA-256. Nonces are one-time and bounded; request/response bodies, queue count/bytes, request frequency, and liveness are bounded independently. The Worker admits the fallback only when no verified-ready WSS exists, preserves the same daemon-instance/pending-call ownership, and does not expose the fallback as ready until the daemon has acknowledged all probing controls and returned the sequenced `https_ready` proof. Bidirectional contiguous transport sequences reject gaps and suppress duplicate delivery before business side effects, so an HTTP response retry does not become a mutation retry.
40
+ - MCP `2026-07-28` per-request protocol metadata, valid positive `Accept` quality values, actual `/mcp` Origin, header/body consistency for version/method/name/declared primitive parameters, request IDs, role-visible tool exposure, raw argument schemas, and response-stream cancellation. Removed initialization/session/replay requests receive bounded upgrade guidance and cannot dispatch tool work.
41
+ - The daemon HTTPS fallback is a transport failover surface, not an MCP endpoint or alternate authority. Each POST is authenticated by the same root-certified ephemeral P-256 device session as the daemon, and its signature binds the fixed route, Worker origin, server/version, a 30-second nonce/timestamp window, and exact body SHA-256. Nonces are one-time and bounded; request/response bodies, queue count/bytes, request frequency, and liveness are bounded independently. The Worker normally admits the fallback only when no verified-ready WSS exists. A signed same-instance takeover naming the exact incumbent connection generation may retire a zombie WSS after candidate preconditions pass; a stale generation or invalid candidate cannot evict it. The fallback preserves the same daemon-instance/pending-call ownership, and does not expose the fallback as ready until the daemon has acknowledged all probing controls and returned the sequenced `https_ready` proof. Bidirectional contiguous transport sequences reject gaps and suppress duplicate delivery before business side effects, so an HTTP response retry does not become a mutation retry.
42
42
 
43
43
  A client registration is not authority. Authority begins only after successful account authorization binds the client to one account and role version.
44
44
 
45
45
  Capability discovery is also an authorization boundary. Task routing and application/browser metadata are built from the effective account/daemon policy intersection, not from the daemon ceiling. Route scores and fallbacks are advisory and cannot manufacture authority; direct Bash remains available only when the effective policy already exposes it. A restricted account must not learn hidden local application inventory or receive names of unavailable execution tools through the resolver.
46
46
 
47
- Mirrored MCP headers are an intermediary-routing boundary. The Worker compares every required native current header with the JSON-RPC body before authorization-dependent dispatch; a mismatch fails with `-32020` before the daemon can observe the call. Stateless initialization-compatibility clients may omit the newer method/name/declared-parameter mirrors, but any such current mirror they do send is binding and must match the same JSON-RPC body before compatibility dispatch. The reserved mirror namespace is closed as well: an inapplicable `Mcp-Name` or a `Mcp-Param-*` header that is not declared by the selected tool schema is rejected rather than ignored. Tool schemas are compiled from a bounded JSON Schema 2020-12 subset at startup and runtime traversal charges every array item and own object property to a fixed work budget. Open request metadata/capability JSON also has a fixed structural-node/depth/key budget. This server advertises no change notifications, so `subscriptions/listen` validates and bounds the requested filter, acknowledges an empty supported notification subset, emits the correlated completion result, and closes immediately without retaining subscription state. Unsupported dialects or keywords fail closed, external network `$ref` values are not dereferenced, and validation diagnostics omit argument values and unbounded caller identifiers.
47
+ Mirrored MCP headers are an intermediary-routing boundary. The Worker compares every required native current header with the JSON-RPC body before authorization-dependent dispatch; a mismatch fails with `-32020` before the daemon can observe the call. The reserved mirror namespace is closed as well: an inapplicable `Mcp-Name` or a `Mcp-Param-*` header that is not declared by the selected tool schema is rejected rather than ignored. Tool schemas are compiled from a bounded JSON Schema 2020-12 subset at startup and runtime traversal charges every array item and own object property to a fixed work budget. Open request metadata/capability JSON also has a fixed structural-node/depth/key budget. Remote HTTP advertises `tools.listChanged=true`. A supported `toolsListChanged` subscription receives the correlated acknowledgement and an initial list-change notification, then retains only bounded authority-scoped in-memory stream state until cancellation, revocation, observable abort, or the advertised ten-second lease. A request with no supported notification filter is acknowledged with an empty subset and completed immediately. Stdio advertises `listChanged=false` and completes validated subscription requests immediately. Unsupported dialects or keywords fail closed, external network `$ref` values are not dereferenced, and validation diagnostics omit argument values and unbounded caller identifiers.
48
48
 
49
49
  Current response-stream cancellation deliberately crosses the OAuth boundary only through an unguessable internal stream capability. The public Worker strips caller-provided control headers before the service binding, the cancellation request contains no bearer token or DPoP proof, and the Durable Object consumes the capability before OAuth only to cancel a currently indexed call. Guessing remains bounded by 256-bit randomness; a compromised service binding or Worker runtime is already inside this trust boundary.
50
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "3.0.0-beta.192",
3
+ "version": "3.0.0-beta.196",
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",
@@ -272,10 +272,10 @@
272
272
  "devDependencies": {
273
273
  "@types/node": "26.4.0",
274
274
  "eslint": "10.9.1",
275
- "fast-check": "4.9.0",
275
+ "fast-check": "4.10.0",
276
276
  "globals": "17.11.0",
277
277
  "typescript": "7.0.2",
278
- "wrangler": "4.127.1"
278
+ "wrangler": "4.131.2"
279
279
  },
280
280
  "keywords": [
281
281
  "mcp",
@@ -307,7 +307,7 @@
307
307
  "esbuild@0.28.1": true,
308
308
  "fsevents": false,
309
309
  "sharp@0.35.4": true,
310
- "workerd@1.20260828.1": true
310
+ "workerd@1.20260911.1": true
311
311
  },
312
312
  "packageManager": "npm@12.0.2",
313
313
  "devEngines": {
@@ -7,6 +7,7 @@ import { classifyRelayTransportError } from "./relay-connection-support.mjs";
7
7
  import { createDaemonHttpRelayHeaders } from "./daemon-http-relay-auth.mjs";
8
8
  import { classifyRelayTransportErrorReason } from "./relay-transport-error-state.mjs";
9
9
  import { postDaemonHttpRelay } from "./daemon-http-relay-request.mjs";
10
+ import { boundedRelayRetryAfterMs, relayFailureBackoffMs } from "./daemon-http-retry-policy.mjs";
10
11
  import { RelayInboundSequence, RelayOutboundSequence } from "./daemon-http-relay-sequence.mjs";
11
12
 
12
13
  const HTTP_SESSION_ID = /^relay_http_[A-Za-z0-9_-]{43}$/;
@@ -65,6 +66,8 @@ export class DaemonHttpRelayConnection {
65
66
  this.consecutiveFailures = 0;
66
67
  this.standby = false;
67
68
  this.lastRequestStartedAt = Number.NEGATIVE_INFINITY;
69
+ this.retryNotBeforeAt = 0;
70
+ this.lastRetryStatusCode = null;
68
71
  this.takeoverWebSocket = false;
69
72
  this.takeoverWebSocketConnectionId = "";
70
73
  }
@@ -132,6 +135,8 @@ export class DaemonHttpRelayConnection {
132
135
  last_transport_error_ready: this.lastErrorReady,
133
136
  last_transport_error_authenticated: this.lastErrorAuthenticated,
134
137
  http_poll_failures: this.consecutiveFailures,
138
+ http_retry_delay_ms: Math.max(0, Math.ceil(this.retryNotBeforeAt - this.now())),
139
+ http_retry_status_code: this.lastRetryStatusCode,
135
140
  };
136
141
  }
137
142
 
@@ -177,7 +182,7 @@ export class DaemonHttpRelayConnection {
177
182
  if (this.closed || this.inFlight) return;
178
183
  this.clearPollTimer();
179
184
  const now = this.now();
180
- const earliest = this.lastRequestStartedAt + this.minimumRequestIntervalMs;
185
+ const earliest = Math.max(this.lastRequestStartedAt + this.minimumRequestIntervalMs, this.retryNotBeforeAt);
181
186
  if (now < earliest) { this.schedulePoll(earliest - now); return; }
182
187
  this.lastRequestStartedAt = now;
183
188
  const descriptor = this.activationToken ? null : (this.descriptor() || {});
@@ -209,7 +214,7 @@ export class DaemonHttpRelayConnection {
209
214
  });
210
215
  if (this.inFlight !== controller || this.closed) return;
211
216
  this.networkRoute = response.networkRoute || this.networkRoute;
212
- await this.handleResponse(response.statusCode, response.body);
217
+ await this.handleResponse(response.statusCode, response.body, response.retryAfterMs);
213
218
  } catch (error) {
214
219
  if (this.inFlight !== controller || this.closed) return;
215
220
  this.handleFailure(error);
@@ -219,7 +224,8 @@ export class DaemonHttpRelayConnection {
219
224
  if (!this.closed) this.schedulePoll(this.nextPollDelay());
220
225
  }
221
226
 
222
- async handleResponse(statusCode, text) {
227
+ async handleResponse(statusCode, text, retryAfterMs = 0) {
228
+ this.lastRetryStatusCode = [429, 503].includes(statusCode) ? statusCode : null;
223
229
  if (statusCode === 404 || statusCode === 405 || statusCode === 426) {
224
230
  this.handleFailure(Object.assign(new Error("daemon HTTP fallback is not supported by the Worker"), { code: "daemon_http_unsupported" }));
225
231
  return;
@@ -233,7 +239,9 @@ export class DaemonHttpRelayConnection {
233
239
  return;
234
240
  }
235
241
  if (statusCode < 200 || statusCode >= 300) {
236
- this.handleFailure(Object.assign(new Error("daemon HTTP fallback request failed"), { code: "network_error" }));
242
+ this.handleFailure(Object.assign(new Error("daemon HTTP fallback request failed"), {
243
+ code: "network_error", retryAfterMs: [429, 503].includes(statusCode) ? boundedRelayRetryAfterMs(retryAfterMs) : 0,
244
+ }));
237
245
  return;
238
246
  }
239
247
  let body;
@@ -252,7 +260,9 @@ export class DaemonHttpRelayConnection {
252
260
  this.lastSuccessAt = this.now();
253
261
  this.lastSuccessWallAt = this.wallNow();
254
262
  this.consecutiveFailures = 0;
263
+ this.retryNotBeforeAt = 0;
255
264
  this.standby = false;
265
+ this.lastRetryStatusCode = null;
256
266
  if (body.phase === "standby") {
257
267
  this.resetSession(this.ready);
258
268
  this.standby = true;
@@ -299,7 +309,8 @@ export class DaemonHttpRelayConnection {
299
309
 
300
310
  handleFailure(error) {
301
311
  this.recordFailure(error);
302
- if (!this.ready || !this.lastSuccessAt || this.now() - this.lastSuccessAt < this.livenessTimeoutMs) return;
312
+ if (!this.ready) return;
313
+ if (Math.max(this.now(), this.retryNotBeforeAt) < this.lastSuccessAt + this.livenessTimeoutMs) return;
303
314
  this.resetSession(true);
304
315
  }
305
316
 
@@ -316,6 +327,8 @@ export class DaemonHttpRelayConnection {
316
327
  this.lastErrorAuthenticated = this.authenticated === true;
317
328
  this.standby = false;
318
329
  this.consecutiveFailures += 1;
330
+ const backoff = relayFailureBackoffMs(this.consecutiveFailures, this.failureBackoffBaseMs, this.failureBackoffMaximumMs);
331
+ this.retryNotBeforeAt = Math.max(this.retryNotBeforeAt, this.now() + Math.max(backoff, boundedRelayRetryAfterMs(error?.retryAfterMs)));
319
332
  }
320
333
 
321
334
  resetSession(notify) {
@@ -338,11 +351,7 @@ export class DaemonHttpRelayConnection {
338
351
 
339
352
  nextPollDelay() {
340
353
  if (this.standby) return this.standbyRetryIntervalMs;
341
- if (this.consecutiveFailures > 0) {
342
- const exponent = Math.min(Math.max(0, this.consecutiveFailures - 1), 16);
343
- return Math.min(this.failureBackoffBaseMs * (2 ** exponent),
344
- Math.max(this.failureBackoffBaseMs, this.failureBackoffMaximumMs));
345
- }
354
+ if (this.consecutiveFailures > 0) return Math.max(0, this.retryNotBeforeAt - this.now());
346
355
  if (!this.authenticated || !this.ready || this.outbound.messages.length > 0) return 0;
347
356
  return this.pollIntervalMs;
348
357
  }
@@ -350,10 +359,10 @@ export class DaemonHttpRelayConnection {
350
359
  schedulePoll(delay) {
351
360
  if (this.closed || this.inFlight) return;
352
361
  const now = this.now();
353
- const earliestDelay = Math.max(0, this.lastRequestStartedAt + this.minimumRequestIntervalMs - now);
362
+ const earliestDelay = Math.max(0, this.lastRequestStartedAt + this.minimumRequestIntervalMs - now, this.retryNotBeforeAt - now);
354
363
  const boundedDelay = Math.max(earliestDelay, Number(delay) || 0);
355
364
  const dueAt = now + boundedDelay;
356
- if (this.pollTimer && this.pollTimerDueAt <= dueAt) return;
365
+ if (this.pollTimer && this.pollTimerDueAt <= dueAt && this.pollTimerDueAt >= now + earliestDelay) return;
357
366
  this.clearPollTimer();
358
367
  this.pollTimerDueAt = dueAt;
359
368
  this.pollTimer = this.scheduler.setTimeout(() => {
@@ -1,5 +1,6 @@
1
1
  import http from "node:http";
2
2
  import https from "node:https";
3
+ import { relayRetryAfterMs } from "./daemon-http-retry-policy.mjs";
3
4
  import { proxyAgentForRelayHttp } from "./network-proxy.mjs";
4
5
 
5
6
  export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResponseBytes, signal, request: requestOverride, selectProxy = proxyAgentForRelayHttp }) {
@@ -42,6 +43,7 @@ export function postDaemonHttpRelay({ url, headers, body, timeoutMs, maximumResp
42
43
  });
43
44
  response.on("end", () => finish(null, {
44
45
  statusCode: Number(response.statusCode) || 0,
46
+ retryAfterMs: relayRetryAfterMs(Number(response.statusCode), response.headers["retry-after"]),
45
47
  body: Buffer.concat(chunks, observed).toString("utf8"),
46
48
  networkRoute: proxy.agent ? "application-http-proxy" : "system-network-stack",
47
49
  }));
@@ -0,0 +1,23 @@
1
+ import relayContract from "../shared/relay-contract.json" with { type: "json" };
2
+
3
+ // Convert the bounded public header to a duration once; scheduling uses monotonic time.
4
+ export function relayRetryAfterMs(statusCode, value, now = Date.now()) {
5
+ if (![429, 503].includes(statusCode) || typeof value !== "string" || value.length > 128) return 0;
6
+ const text = value.trim();
7
+ let delay;
8
+ if (/^[0-9]+$/.test(text)) delay = Number(text) * 1000;
9
+ else if (/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(text)) {
10
+ delay = Date.parse(text) - now;
11
+ } else return 0;
12
+ return boundedRelayRetryAfterMs(delay);
13
+ }
14
+
15
+ export function boundedRelayRetryAfterMs(value) {
16
+ return typeof value === "number" && Number.isFinite(value) && value > 0
17
+ ? Math.min(Math.ceil(value), relayContract.httpFallbackMaximumRetryAfterMs) : 0;
18
+ }
19
+
20
+ export function relayFailureBackoffMs(failures, base, maximum) {
21
+ const exponent = Math.min(Math.max(0, failures - 1), 16);
22
+ return Math.min(base * (2 ** exponent), Math.max(base, maximum));
23
+ }
@@ -335,7 +335,7 @@ export class RelayConnection {
335
335
  terminateSocket(socket);
336
336
  return true;
337
337
  }
338
- this.failPermanently("relay_protocol_error");
338
+ this.failPermanently("relay_protocol_error", { diagnostics: message?.diagnostics || null });
339
339
  return true;
340
340
  }
341
341
 
@@ -521,7 +521,7 @@ export class RelayConnection {
521
521
  });
522
522
  }
523
523
 
524
- failPermanently(category, { socketAlreadyClosed = false, wasReady = this.ready } = {}) {
524
+ failPermanently(category, { socketAlreadyClosed = false, wasReady = this.ready, diagnostics = null } = {}) {
525
525
  if (this.closed) return;
526
526
  const socket = this.socket;
527
527
  this.closed = true;
@@ -550,11 +550,12 @@ export class RelayConnection {
550
550
  this.connectedOnceResolve = null;
551
551
  this.connectedOnceReject = null;
552
552
  this.resetOutage();
553
+ if (diagnostics) this.logger.error?.(message, diagnostics);
553
554
  if (!this.hasConnected && reject) {
554
555
  reject(error);
555
556
  return;
556
557
  }
557
- this.logger.error?.(message);
558
+ if (!diagnostics) this.logger.error?.(message);
558
559
  this.logger.debug?.("remote relay fatal details", { category, cause: relayCloseUserCause(category) });
559
560
  queueMicrotask(async () => {
560
561
  try {