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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/GOVERNANCE.md +2 -2
  4. package/README.md +24 -6
  5. package/browser-extension/manifest.json +1 -1
  6. package/docs/AGENT_CONTEXT.md +10 -7
  7. package/docs/ARCHITECTURE.md +35 -22
  8. package/docs/AUDIT.md +85 -1
  9. package/docs/CLIENTS.md +6 -2
  10. package/docs/ENGINEERING.md +31 -9
  11. package/docs/LOCAL_AUTOMATION.md +4 -2
  12. package/docs/LOGGING.md +8 -8
  13. package/docs/OPERATIONS.md +43 -17
  14. package/docs/PRIVACY.md +18 -4
  15. package/docs/PROJECT_STANDARDS.md +2 -2
  16. package/docs/RELEASING.md +35 -11
  17. package/docs/TESTING.md +36 -16
  18. package/docs/THREAT_MODEL.md +20 -5
  19. package/docs/TOOL_REFERENCE.md +18 -12
  20. package/docs/UPGRADING.md +32 -0
  21. package/package.json +15 -6
  22. package/scripts/check-plan.mjs +8 -0
  23. package/scripts/coverage-check.mjs +30 -1
  24. package/scripts/foreground-daemon-recovery.mjs +88 -0
  25. package/scripts/github-release.mjs +22 -16
  26. package/scripts/install-published-prerelease.mjs +7 -7
  27. package/scripts/official-mcp-conformance.mjs +243 -0
  28. package/scripts/persistent-activation-process.mjs +36 -0
  29. package/scripts/release-candidate-manifest.mjs +12 -0
  30. package/scripts/release-publication-guard.mjs +65 -0
  31. package/scripts/release-state.mjs +1 -1
  32. package/scripts/sbom-check.mjs +99 -0
  33. package/scripts/start-release-candidate.mjs +39 -13
  34. package/src/local/agent-context-projection.mjs +26 -7
  35. package/src/local/agent-context.mjs +25 -4
  36. package/src/local/autostart-log-maintenance.mjs +36 -0
  37. package/src/local/capability-observer.mjs +5 -0
  38. package/src/local/child-process-settlement.mjs +103 -0
  39. package/src/local/cli-activate.mjs +42 -5
  40. package/src/local/cli-service.mjs +55 -5
  41. package/src/local/cli.mjs +59 -10
  42. package/src/local/daemon-process.mjs +24 -3
  43. package/src/local/delegated-process-sandbox.mjs +1 -0
  44. package/src/local/execution-routing.mjs +231 -0
  45. package/src/local/git-service.mjs +3 -1
  46. package/src/local/job-runner.mjs +55 -19
  47. package/src/local/macos-trust-broker.mjs +7 -0
  48. package/src/local/managed-job-runner-claim.mjs +54 -0
  49. package/src/local/managed-job-runner.mjs +13 -2
  50. package/src/local/process-execution.mjs +2 -2
  51. package/src/local/process-identity.mjs +11 -0
  52. package/src/local/process-tree-ownership-types.d.ts +37 -0
  53. package/src/local/process-tree-ownership.mjs +49 -41
  54. package/src/local/process-tree.mjs +1 -1
  55. package/src/local/relay-call-recovery.mjs +40 -21
  56. package/src/local/runtime-activation.mjs +357 -38
  57. package/src/local/runtime-capabilities.mjs +22 -6
  58. package/src/local/runtime-diagnostics.mjs +9 -2
  59. package/src/local/runtime.mjs +18 -4
  60. package/src/local/service-convergence.mjs +33 -0
  61. package/src/local/service-owner.mjs +147 -0
  62. package/src/local/service-restart-handoff.mjs +22 -8
  63. package/src/local/service-runtime.mjs +145 -0
  64. package/src/local/service.mjs +143 -25
  65. package/src/local/state.mjs +104 -7
  66. package/src/local/stdio.mjs +139 -45
  67. package/src/local/system-network-route.mjs +76 -0
  68. package/src/local/tool-executor.mjs +24 -6
  69. package/src/local/tools.mjs +6 -5
  70. package/src/local/windows-service-convergence.mjs +49 -0
  71. package/src/local/windows-service.mjs +30 -53
  72. package/src/shared/mcp-protocol.d.mts +27 -0
  73. package/src/shared/mcp-protocol.mjs +256 -0
  74. package/src/shared/mcp-subscriptions.d.mts +4 -0
  75. package/src/shared/mcp-subscriptions.mjs +59 -0
  76. package/src/shared/relay-contract.json +1 -0
  77. package/src/shared/result-projection.d.mts +2 -1
  78. package/src/shared/result-projection.mjs +13 -2
  79. package/src/shared/server-metadata.json +11 -4
  80. package/src/shared/tool-argument-validation.d.mts +17 -0
  81. package/src/shared/tool-argument-validation.mjs +325 -0
  82. package/src/shared/tool-catalog.json +18 -12
  83. package/src/worker/durable-stream-calls.ts +12 -24
  84. package/src/worker/http.ts +36 -2
  85. package/src/worker/index.ts +181 -165
  86. package/src/worker/mcp-http-contract.ts +276 -0
  87. package/src/worker/mcp-jsonrpc.ts +12 -6
  88. package/src/worker/mcp-legacy-dispatch.ts +104 -0
  89. package/src/worker/mcp-modern-controller.ts +199 -0
  90. package/src/worker/mcp-modern-proxy.ts +126 -0
  91. package/src/worker/mcp-modern-stream.ts +71 -0
  92. package/src/worker/mcp-session.ts +12 -3
  93. package/src/worker/mcp-stream-proxy-contract.ts +67 -0
  94. package/src/worker/mcp-stream-proxy.ts +17 -60
  95. package/src/worker/mcp-tool-call-input.ts +23 -0
  96. package/src/worker/tool-catalog.ts +29 -1
  97. package/src/worker/tool-timeout.ts +53 -12
  98. package/src/worker/worker-mcp-config.ts +23 -0
  99. package/src/worker/worker-metadata.ts +10 -1
  100. package/src/worker/worker-runtime-config.ts +19 -0
  101. package/src/worker/worker-static-routes.ts +7 -2
  102. package/tsconfig.local.json +7 -1
package/docs/LOGGING.md CHANGED
@@ -38,7 +38,7 @@ Human mode treats the message as the primary interface: it uses a natural-langua
38
38
 
39
39
  ## Worker deployment and health event policy
40
40
 
41
- Wrangler upload and `/healthz` verification are logged as separate state transitions. A successful upload followed by ambiguous health failure must say that the deployment fingerprint was retained and that retry will verify rather than redeploy. Existing-state timeout, proxy, TLS, network, and temporary server failures must say that no deployment was attempted. Only a persistent stale identity/version result may emit the warning that the same Worker is being redeployed.
41
+ Wrangler upload and `/healthz` verification are logged as separate state transitions. A successful upload followed by ambiguous health failure must say that the deployment fingerprint was retained and that retry will verify rather than redeploy. Existing-state timeout, proxy, TLS, network, and temporary server failures must say that no deployment was attempted. Only a persistent stale identity/version result may emit the ordinary warning that the same Worker is being redeployed. Candidate activation has one narrower exception: an explicit device-authentication rejection after current-version health may emit one warning that the same Worker is being redeployed with the current identity. That warning never includes the Worker name or endpoint, device key ID, public key, signature, certificate, nonce, or secret values.
42
42
 
43
43
  Health routing records only `direct` or `proxy` at debug level. Proxy URLs, credentials, request headers, Worker secrets, and raw response bodies are never fields. Repeated per-attempt health failures remain debug-only; the terminal startup error contains one user-readable classification and corrective commands.
44
44
 
@@ -46,7 +46,7 @@ Autostart installation and daemon startup may report the names of allowlisted pr
46
46
 
47
47
  ## Relay connection event policy
48
48
 
49
- A TCP/WebSocket `open` event is only transport availability, and `hello_ack` proves only authenticated bidirectional control traffic. The daemon is reported as ready only after a random Worker probe returns through the ordinary local dispatcher and session-bound result path and the Worker sends `ready_ack`.
49
+ A TCP/WebSocket `open` event is only transport availability, and `hello_ack` proves only authenticated bidirectional control traffic. The daemon is reported as ready only after a random Worker probe returns through the ordinary local dispatcher and ordinary authenticated result path and the Worker sends `ready_ack`.
50
50
 
51
51
  Brief network interruptions are expected on laptop network changes, Worker deployment, proxy rotation, and ordinary internet transport. They are handled as follows:
52
52
 
@@ -66,7 +66,7 @@ Brief network interruptions are expected on laptop network changes, Worker deplo
66
66
 
67
67
  A WebSocket close code such as `1006` means the transport ended without a normal close handshake. It is useful for debug diagnosis but not useful as the default user message. It is not evidence that the daemon process restarted. Worker `daemon_transport_error` / `daemon_liveness_timeout` messages and their 1012 close frames are likewise retryable connection conditions, not upgrade instructions. Only an unknown/incompatible Worker error, authentication failure, or identity/version mismatch may produce the fatal protocol/configuration log and daemon exit. Default logs therefore describe the affected layer, duration, classification, and recovery behavior rather than printing raw close envelopes.
68
68
 
69
- Persisted streamed-call diagnostics are deliberately coarse. Logs and `server_info` may report aggregate active/detached counts, oldest age, tool-name counts, alarm mutations, unmatched-result counts, and whether a call was transient or durable. They must not include tool arguments, terminal results, command text, request keys, account identifiers, raw call IDs, raw connection generations, private paths, or subscriber payloads. A stale-generation result is counted as unmatched rather than logged with its envelope.
69
+ Streamed-call diagnostics are deliberately coarse. Modern request-scoped stream ownership is memory-only; legacy MCP `2025-11-25` may additionally report aggregate persistent active/detached counts, oldest age, tool-name counts, alarm mutations, unmatched-result counts, and whether a legacy call is transient or durable. 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.
70
70
 
71
71
  Examples:
72
72
 
@@ -84,7 +84,7 @@ All per-tool starts, successes, failures, cancellations, timing, and expected la
84
84
 
85
85
  The layered repository check runner follows the same noise rule. Green child-task output is discarded after the child exits; only task name and elapsed time are printed. Failed tasks expose bounded head/tail stdout and stderr diagnostics. `MBM_CHECK_VERBOSE=1` is an explicit operator choice to stream raw child output and is not used by default or CI.
86
86
 
87
- A completed local result is normally sent on the ready relay connection. If that socket disappears, the runtime queues the bounded result envelope during the shared two-minute same-daemon reconnect window rather than logging a terminal delivery failure. Debug output records only a shortened call ID and queue/reconnect counts. After the same daemon process completes readiness, replay emits one recovery event; a different process cannot inherit the result. Explicit MCP cancellation suppresses eventual delivery; loss of the HTTP/SSE response stream does not. If the relay does not recover before the grace deadline, ordinary calls are cancelled, queued results are discarded, and the existing outage state machine determines whether the persistent failure warrants a warning. Tool arguments, commands, and result content are never logged.
87
+ A completed local result is normally sent on the ready relay connection. If that daemon WebSocket disappears, the runtime may queue the bounded result envelope during the shared two-minute same-daemon reconnect window; this relay-layer queue is independent of the public MCP protocol era. Debug output records only a shortened call ID and queue/reconnect counts. After the same daemon process completes readiness, replay emits one recovery event; a different process cannot inherit the result. For modern MCP `2026-07-28`, closing the public HTTP response stream cancels that request through a random internal capability; the control request contains no Authorization or DPoP header, and the capability is not logged. For legacy MCP `2025-11-25`, disposing the public response does not cancel the session-bound operation because the host may recover it with `Last-Event-ID`; explicit legacy cancellation suppresses eventual delivery. Tool arguments, mirrored header values, validation values, and result content are never logged.
88
88
 
89
89
  Debug per-tool fields may include tool name, duration, coarse outcome class, and a shortened random call identifier. The identifier is for correlating adjacent local events and is not a stable audit identifier. Authorization failures expose a random approval ID, scope, and expiry to the caller; daemon logs still omit normalized targets and request arguments.
90
90
 
@@ -112,7 +112,7 @@ Application discovery and Accessibility operations follow the same rule: permiss
112
112
 
113
113
  ## Bounding and redaction
114
114
 
115
- Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Control characters and Unicode display controls are neutralized. Fields with secret-like names and path-like keys are recursively redacted. Free-form sanitization covers generic private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped values, URLs with embedded credentials, email addresses, and user-home paths.
115
+ Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Tool-argument validation reports only a bounded tool name, JSON Pointer path, schema keyword, and constraint message; it never includes the rejected value. Control characters and Unicode display controls are neutralized. Fields with secret-like names and path-like keys are recursively redacted. Free-form sanitization covers generic private-key headers, AWS/GitHub/GitLab/npm/Slack/Google/live-payment/API token forms, JWT-shaped values, URLs with embedded credentials, email addresses, and user-home paths.
116
116
 
117
117
  Local and Worker free-form strings use the same portable value sanitizer. Worker fields are therefore inspected by content even when their key is not secret-shaped. Both structured loggers assign their authoritative metadata after sanitizing caller fields. Local `timestamp`, `level`, `component`, `message`, and `event`, plus Worker `timestamp`, `level`, `component`, and `event`, therefore cannot be forged or replaced by an event payload. Local-only recursive path-key redaction and environment-derived home aliases remain additional protections around the portable rules.
118
118
 
@@ -135,7 +135,7 @@ logs/daemon.out.log
135
135
  logs/daemon.err.log
136
136
  ```
137
137
 
138
- Existing files are opened without following symbolic links where supported and tail-trimmed on UTF-8/line boundaries before startup. Background services use `warn`, so ordinary tool traffic and brief relay interruptions do not cause sustained growth.
138
+ Existing files are opened without following symbolic links where supported and tail-trimmed on UTF-8/line boundaries before startup. The active background daemon repeats the same secure trim every 15 minutes, so a long-lived process remains bounded even under repeated warning-level failures. Maintenance errors expose only a coarse error class. Background services use `warn`, so ordinary tool traffic and brief relay interruptions do not cause sustained growth.
139
139
 
140
140
  The log format has an explicit schema marker. If the marker differs from the current format, the daemon clears the active files before startup and writes the current marker. Runtime code recognizes only `daemon.out.log` and `daemon.err.log`; it does not parse or archive other log formats.
141
141
 
@@ -145,7 +145,7 @@ Each managed job has owner-only runner diagnostic logs. Child-step output is ret
145
145
 
146
146
  `network_route` describes only Machine Bridge's application-level proxy decision. `system-network-stack` does **not** mean a direct physical path: an operating-system VPN, TUN, packet tunnel, DNS interceptor, or endpoint-security product may still carry the connection. `network_route_scope` therefore remains `application-proxy-selection-only`.
147
147
 
148
- During an outage, `server_info.runtime.relay` and `diagnose_runtime` expose bounded operational fields: outage count/start/duration, last close category/code, coarse transport error class, last disconnect/ready time, prior ready duration, and next retry timing. `relay.outage.active` and `relay.outage.recovered` carry the same safe fields. Raw WebSocket close reasons, IP addresses, proxy endpoints/credentials, DNS answers, tool arguments, and results are not promoted to default logs.
148
+ During an outage, `server_info.runtime.relay` and `diagnose_runtime` expose bounded operational fields: outage count/start/duration, last close category/code, coarse transport error class, last disconnect/ready time, prior ready duration, and next retry timing. On macOS, `diagnose_runtime` may also return a coarse default-route class and `operating_system_interception` boolean. That diagnostic is returned on demand and is not promoted to default logs; interface names, IP addresses, DNS answers, proxy endpoints/credentials, Worker endpoints, tool arguments, and results remain absent. `relay.outage.active` and `relay.outage.recovered` carry the existing safe relay fields.
149
149
 
150
150
  Schema 4 is strict NDJSON. Before daemon startup, both active log files are opened as owner-only regular single-link files. A schema change clears both only after validation and commits the marker only after the transition succeeds. A symlink, multiple-hard-link inode, permission error, or marker-write failure blocks startup rather than mixing formats or repeatedly erasing evidence.
151
151
 
@@ -153,7 +153,7 @@ Schema 4 is strict NDJSON. Before daemon startup, both active log files are open
153
153
 
154
154
  Canonical `full` does not remove tools based on filenames. For remote execution, the operation classifier treats credential-sensitive paths and persistence targets as hard authorization boundaries: delegated roles are denied, while owner requests remain risk-classified and audited. An MCP host, connector, model provider, desktop application, operating system, or endpoint-security layer may independently reject a request before it reaches Machine Bridge.
155
155
 
156
- Use `server_info`, `project_overview`, `machine-mcp status`, `machine-mcp doctor`, and `diagnose_runtime` to distinguish local policy from host-side enforcement. Capability-routing status is returned on demand rather than written as task logs; it stores a runtime-keyed task fingerprint, not raw task text. Changing the Machine Bridge profile cannot override another layer.
156
+ Use `server_info`, `project_overview`, `machine-mcp status`, `machine-mcp doctor`, and `diagnose_runtime` to distinguish local policy from host-side enforcement. Capability-routing status is returned on demand rather than written as task logs. The in-memory observer stores a runtime-keyed task fingerprint, selected skill/match counts, recommended tool names, primary route, ambiguity class, and score gap; it never stores raw task text, static instruction content, application inventory, or route explanations. Changing the Machine Bridge profile cannot override another layer.
157
157
 
158
158
  ## Adding or changing logs
159
159
 
@@ -45,6 +45,8 @@ The standard public endpoint remains the automatically provisioned `workers.dev`
45
45
  | `worker.sockets_live.authenticated` is nonzero but `worker.sockets_live.ready` is zero | Transport authentication exists, but the end-to-end result probe has not completed. The stable account catalog remains discoverable, while `authorization.effective_tools` contains no executable daemon tool and calls fail retryably until readiness |
46
46
  | `capability_routing.bootstrap_observed` is false | The current local runtime has not received `session_bootstrap`; reconnect or inspect host initialization handling |
47
47
  | `task_resolution_observed` is false after a substantive task | The host/model did not call `resolve_task_capabilities`; server-side discovery cannot force that host decision |
48
+ | `primary_route` is unexpected or `routing_ambiguity` is high | Inspect the returned competing routes and tool-description boundaries; routing is advisory, so use any tool allowed by the effective policy. Do not treat a fallback as permission to bypass a host or policy denial. |
49
+ | Capability responses repeat large unchanged instructions | Return the previous lowercase SHA-256 `refresh.fingerprint` as `known_refresh_fingerprint`; static context is omitted only when its identity still matches, while task routing is recomputed. |
48
50
  | Task resolution ran but all match counts are zero | Check `application_discovery`: `available=false` or a nonzero warning count means application inventory was partial or unavailable; otherwise the resolver ran successfully but found no sufficiently relevant local skill, command, or application |
49
51
  | No structured result because the host rejects the call | Host/connector approval or safety layer, or transport before daemon delivery |
50
52
  | `mcp-host-to-daemon` passes but `local-filesystem` fails | Local state/runtime permissions, disk policy, sandbox, or endpoint security |
@@ -57,29 +59,33 @@ A successful diagnostic result applies only to that probe. An MCP host can still
57
59
 
58
60
  ### Concurrent chat windows and pending calls
59
61
 
60
- Machine Bridge supports concurrent calls: the Worker admits up to 32 pending daemon calls and the local runtime admits up to 16 active tool calls. These are capacity limits, not a single global execution queue. Each successful MCP initialization receives a signed `MCP-Session-Id`; JSON-RPC ids and cancellation are scoped to that session, so separate chat windows may reuse the same numeric ids safely even when they share one OAuth account and token.
62
+ Machine Bridge supports concurrent calls: the Worker admits up to 32 pending daemon calls and the local runtime admits up to 16 active tool calls. These are capacity limits, not a single global execution queue. Modern MCP `2026-07-28` HTTP requests are independent: JSON-RPC IDs are scoped to each request/response stream, so separate clients may reuse the same numeric ID even when they share one OAuth account and token. Legacy MCP `2025-11-25` initialization still receives a signed `Mcp-Session-Id`; duplicate detection, explicit cancellation, and replay for that compatibility path remain session-scoped.
61
63
 
62
- `server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, `by_tool`, `transient`, and `durable_streams`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and `authorization.effective_tools`. The stable role-filtered `tools/list` catalog does not disappear during a brief outage. A nonzero `active` count means work is in flight, not that the bridge is locked. `detached > 0` means a daemon socket was lost and those requests are inside the bounded two-minute same-instance reconnect window. Calls for simple reads and probes should continue while another independent process call runs. Only explicit session-scoped MCP cancellation, timeout, or reconnect-grace expiry removes the pending record and its request key; an HTTP response disconnect is not cancellation. A daemon-socket closure detaches only calls assigned to its opaque connection generation; the same daemon process can reclaim them after completing readiness, while another process cannot. A verified same-instance replacement transfers both detached and still-attached calls before the incumbent closes, and delayed old-socket results are rejected. JSON-only and streamed calls pass through one FIFO admission gate and share the reported 32-call ceiling. JSON-only calls use an in-event timer plus the shared alarm/sweep backstop. Streamed calls persist their operation or reconnect deadline and use the Durable Object alarm plus event-entry sweep; no JavaScript timer is their durable owner. Each detach/rebind monotonically extends active-record expiry over the new reconnect and remaining-operation budgets. Therefore `detached > 0` with `oldest_ms` materially beyond the two-minute grace is a lifecycle defect rather than normal recovery. Grace expiry rejects the request and cancels the local ordinary operation. Refreshing a chat page is not the recovery mechanism and should not be required.
64
+ `server_info.worker.pending_calls` reports `active`, `detached`, `request_keys`, `maximum`, `oldest_ms`, `by_tool`, `transient`, and `durable_streams`. `worker.sockets_live` separately reports `authenticated`, `probing`, `ready`, and `candidates`; only `ready` sockets contribute to `daemon.connected` and `authorization.effective_tools`. A nonzero `active` count means work is in flight, not that the bridge is globally locked. `detached > 0` means the daemon WebSocket was lost and calls are inside the bounded same-daemon reconnect interval. This relay-layer state exists below both MCP eras.
65
+
66
+ For modern MCP `2026-07-28`, the public response stream is the request owner: closing it cancels the transient pending call, and no request-key or replay record should remain. For legacy MCP `2025-11-25`, the signed session and typed JSON-RPC ID own duplicate detection and explicit `notifications/cancelled`; closing a resumable public stream alone does not cancel the operation. Legacy terminal completion, explicit cancellation, timeout, or reconnect-grace expiry must eventually return active/detached/request-key counts to zero. A verified same-daemon replacement may reclaim detached relay calls after readiness, while a new daemon process cannot. Delayed results from the old socket are rejected. `detached > 0` materially beyond the two-minute grace, a modern transient call surviving response closure, or a legacy request-key count remaining after active calls reach zero is a lifecycle defect.
63
67
 
64
68
  A Worker-requested `daemon_transport_error` or `daemon_liveness_timeout` is a retryable connection invalidation. The local daemon must close only the affected socket, preserve pending-call detach semantics, and reconnect; it must not enter the fatal `relay_protocol_error` path or exit for launchd to restart. The Worker uses close code 1012 for these transient cases. The daemon also recognizes the bounded close reasons `daemon pong failed`, `daemon send failed`, and `daemon liveness timeout` if the preceding error frame is not delivered. Unknown error codes, authentication rejection, and server identity/version mismatch remain permanent failures and require operator action.
65
69
 
66
- For Streamable HTTP clients such as ChatGPT that advertise `text/event-stream`, the outer Worker returns an immediate sequence-zero SSE event identifier and a keepalive comment every ten seconds until the terminal sequence-one JSON-RPC result. `BridgeRoom` never owns the long-lived public stream or an unresolved terminal Promise. Stream initiation commits recovery state and then durably attaches the opaque call ID, daemon instance, WebSocket generation, request key, deadlines, and bounded transform metadata before sending work and returning a descriptor. A later WebSocket result, explicit cancellation, operation timeout, send failure, or reconnect-grace expiry writes the terminal result through one generation-checked transaction. One internal hibernatable WebSocket subscription therefore coexists with concurrent `server_info`, recovery, and session-scoped `notifications/cancelled` requests while SSE remains open, without creating a request per poll interval. Caller-supplied internal stream headers are removed at the public boundary. If the client or an intermediary closes the stream, Machine Bridge keeps the bounded operation alive; only `notifications/cancelled` carries cancellation semantics. A compatible host resumes the original stream with authenticated `GET /mcp`, the original `MCP-Session-Id`, and `Last-Event-ID`; it must not repeat the POST. Recovery records are token/session-bound, retained for at most two minutes, limited to 64 streams, and persist at most 1.5 MiB of terminal JSON. Error `-32002` means the online result exceeded the replay budget. Error `-32003` is reserved for a pending stream record that has no durable call owner after restart; a valid persisted call remains pending and recoverable. Error `-32005` means stored replay data failed integrity validation.
70
+ For modern MCP `2026-07-28`, every POST advertises both `application/json` and `text/event-stream` with valid positive HTTP quality values, carries protocol version and capabilities in request `_meta`, and mirrors the version/method/applicable name into validated HTTP headers. The actual `/mcp` Origin must be absent, same-origin, built-in, or explicitly allowlisted; CORS preflight accepts only fixed protocol headers plus exact catalog-declared `Mcp-Param-*` names. A JSON response completes immediately; a streamed `tools/call` receives a request-scoped SSE stream without event IDs. Closing that response stream is cancellation: the outer Worker observes request abort, response-body cancellation, or failed bounded keepalive delivery and sends one random internal capability without Authorization or DPoP headers. The Durable Object consumes it before OAuth only to remove the already-active matching pending call and send `cancel_call` when work has been dispatched; caller-supplied internal headers are stripped. Modern streams are never resumed through GET or `Last-Event-ID`. Local Wrangler does not propagate a raw TCP close into the Worker cancellation callbacks reliably, so deterministic proxy tests enforce this control path and live candidate verification must exercise it on the deployed edge.
71
+
72
+ Legacy MCP `2025-11-25` retains the older delivery contract for existing hosts. Name, account-visible tool membership, and raw arguments are validated before any resumable record is allocated; malformed or role-hidden calls return `-32602` with no daemon dispatch. For a valid call, the outer Worker emits sequence-zero and sequence-one event IDs while `BridgeRoom` persists bounded session-bound stream/call ownership before daemon dispatch. A compatible legacy host may recover with authenticated `GET /mcp`, its original `Mcp-Session-Id`, and `Last-Event-ID`; it must not repeat the POST. Legacy records are token/session-bound, retained for at most two minutes, limited to 64 streams, and persist at most 1.5 MiB of terminal JSON. Errors `-32002`, `-32003`, and `-32005` in this area are legacy recovery diagnostics, not modern protocol errors. Caller-supplied internal stream headers are removed at the public boundary in both eras.
67
73
 
68
- `server_info.worker.observability.calls.unmatched_results` counts results that reached the Worker after their pending record was already removed. A small increase can accompany cancellation or timeout races, especially during mixed-version upgrade convergence; sustained growth together with old pending calls indicates incompatible components or a lifecycle defect. The counter contains no tool arguments or result data.
74
+ The daemon-to-Worker terminal protocol is at-least-once until `tool_result_ack`. Queueing a WebSocket frame is not durable delivery: the runtime retains a bounded terminal envelope, replays it after same-daemon reconnect or heartbeat, and removes it only after acknowledgement or the authoritative `resume_calls` reconciliation excludes it. The modern public stream has no replay surface; the legacy terminal store is generation-checked and exactly-once from the client's recovery perspective. `server_info.worker.observability.calls.unmatched_results` counts late results whose pending owner was already removed. A small increase may accompany cancellation or timeout races; sustained growth together with old pending calls indicates mixed versions or a lifecycle defect. The counter contains no arguments or result data.
69
75
 
70
76
  ### MCP host or connector internal-storage errors
71
77
 
72
- An error naming an internal shard mapper, temporary keyspace, backfill store, or connector database is not automatically a Machine Bridge Worker or daemon error. During the beta.15 incident, the exact error text was absent from repository source, Worker events, daemon logs, and local process output; Worker HTTP `server_error` counters also did not increase, while the host temporarily failed even `server_info`. That evidence places the original failure before or outside the deployed Worker/daemon boundary, but it does not identify which upstream platform component owned the temporary store. Do not rotate OAuth/device credentials, delete local state, or restart a healthy daemon solely because of such a message.
78
+ An error naming an internal shard mapper, temporary keyspace, backfill store, connector database, or host-side cache is not automatically a Machine Bridge Worker or daemon error. Check whether the exact text appears in repository source, Worker events, daemon logs, or local process output, and whether Worker `requests.server_error` increased. If even `server_info` fails before reaching the Worker while local readiness remains healthy, preserve credentials and state; report the host/connector incident separately rather than rotating OAuth/device secrets or redeploying blindly.
73
79
 
74
- After the host path recovers, run `server_info`, `machine-mcp doctor`, and `machine-mcp service status`. Compare Worker `requests.server_error`, pending-call age, ready socket count, daemon PID/start time, and local logs. If the upstream text never appears locally and Worker server errors remain unchanged, report the host/connector incident separately. If pending calls remain older than their operation or reconnect deadline, that is a Machine Bridge lifecycle issue and should be investigated independently rather than attributed to the upstream shard error.
80
+ After the host path recovers, compare `server_info`, `machine-mcp doctor`, and `machine-mcp service status`: ready socket count, pending age, daemon PID/start time, relay outage fields, and local logs. A host-storage incident and a genuine stale pending call can coexist; investigate the latter independently if it exceeds its operation or reconnect deadline.
75
81
 
76
82
  ### Relay interruption messages
77
83
 
78
- A reconnect warning is evidence of a transport outage, not proof that the daemon process exited. Compare daemon PID/process start, `connected_at`, `last_seen_at`, `runtime.relay.last_disconnected_at`, close category/code, and outage count. A system VPN/TUN may remain shown as connected while its internal route is unavailable; Machine Bridge reports that route only as `system-network-stack` with application-proxy scope. The reconnect schedule now tops out at fifteen seconds.
84
+ A reconnect warning proves a transport interruption, not a daemon crash. Compare daemon PID and process start time with `connected_at`, `last_seen_at`, relay close category/code, outage count, and the coarse system-route diagnostic. A VPN/TUN UI may remain connected while its upstream route is unusable. Machine Bridge reports only coarse route/proxy classes and never logs interface names, addresses, DNS answers, proxy credentials, or Worker secrets.
79
85
 
80
- A brief relay interruption is retried automatically and is visible only with `--verbose`. Default logs do not print raw WebSocket values such as `code=1006` with an empty reason. If a transient outage persists for 10 seconds, the daemon emits a readable duration/cause/reconnect summary; later reminders use autonomous exponential backoff capped at 15 minutes, and recovery produces one warn-level closure summary so the default background-service log contains both ends of the incident. Each transport connection attempt also has a deadline, so a socket stuck in `CONNECTING` cannot freeze retries. Identity/version mismatch, authentication rejection, and unexpected protocol messages are not retried as ordinary network faults: the daemon emits an immediate actionable error and exits, requiring upgrade/redeployment or credential repair. Worker-side hello and end-to-end readiness timeouts remain retryable. Authentication is not reported as usable service readiness until a session-bound probe result returns.
86
+ Brief retryable outages reconnect automatically. A persistent outage emits bounded summaries; identity/version mismatch, authentication rejection, and unexpected protocol messages remain permanent failures requiring version convergence or credential repair. Compare outage intervals with sleep/wake records before classifying them as active network faults. Use `--verbose` only when close codes, heartbeat deadlines, and retry delays are required.
81
87
 
82
- Use `--verbose` only when close codes, close reasons, heartbeat timeouts, and retry delays are needed for diagnosis. A close code of 1006 means the transport ended without a normal close handshake; it does not by itself identify the cause.
88
+ A foreground MCP tool is not a durable job. Hosted clients can impose a request ceiling below the local/stdio schema, so the remote catalog accepts at most 85 seconds and reserves terminal-delivery margin. Longer work belongs in `start_process` plus bounded `read_process`, or in a managed job. Keep mutation and verification in independently terminal calls when a host exposes only a foreground shell tool.
83
89
 
84
90
  The daemon honors `HTTPS_PROXY`/`HTTP_PROXY` and `NO_PROXY` through standard environment-proxy resolution for remote Worker health and relay traffic. `wss:` targets use HTTPS proxy selection and `ws:` targets use HTTP proxy selection. Only HTTP and HTTPS proxy URLs are accepted. Invalid URLs or unsupported protocols fail startup with corrective guidance instead of entering the reconnect loop. `server_info.runtime.relay.network_route` reports `system-network-stack`, `application-http-proxy`, or `invalid-application-proxy-configuration`. This field describes only Machine Bridge application-level proxy selection: an operating-system VPN/TUN may still intercept `system-network-stack` traffic. `network_route_scope`, outage timestamps/durations, close category/code, transport error class, and next retry timing make that distinction explicit; proxy endpoints and credentials are never returned or logged. The browser-broker CLI health probe is a separate loopback-only path: it accepts only canonical `127.0.0.1`, uses direct Node HTTP with no proxy agent, and does not depend on `NO_PROXY`.
85
91
 
@@ -117,6 +123,8 @@ Application UI inspection/actions require Accessibility permission for the Node/
117
123
 
118
124
  A global npm install changes the CLI files on disk but does not replace an already running Node process. Startup and other state-changing CLI operations use a token/process-identity lock and wait up to 30 seconds for a normal concurrent startup to finish; duration limits use monotonic elapsed time, so NTP or manual wall-clock correction does not lengthen or shorten the wait; a short launchd/systemd overlap is therefore serialized rather than reported immediately as an error. On a normal foreground start, Machine Bridge unloads the platform service and then independently examines the workspace daemon lock. This second path handles a detached/orphan `--daemon-only` process that launchd, systemd, or Task Scheduler no longer tracks. Only current lock records containing service mode, version, PID, process start time, entrypoint, workspace, and state root are eligible for takeover. Before sending `SIGTERM`, Machine Bridge verifies PID and process start time plus the live command line, entrypoint, and daemon-only flag. Explicit `--workspace` and `--state-dir` must both match the active state when present; a recovery daemon started with only `--daemon-only` is accepted when the lock owner already records that workspace and state root. Partial path identity (only one of the two flags) is rejected. If the verified daemon ignores graceful termination, Machine Bridge waits for the grace period, then repeats process-instance and full daemon-identity verification before sending `SIGKILL`. PID reuse, identity drift, foreground mode, or any ambiguity blocks escalation. The total stop remains bounded at 15 seconds and stale lock reclamation still uses token-aware ownership. A foreground or unverifiable process is left untouched; stop a foreground instance with `Ctrl+C`.
119
125
 
126
+ Foreground shell, process-session, and managed-job timeouts terminate the complete process group rather than only the direct child. On macOS, ownership capture and revalidation use `ps -g <PGID>` so unrelated system-wide process-table load cannot erase all identity evidence. Other POSIX fallbacks share one three-second monotonic inspection budget and revalidate exact PID, start time, and PGID before `SIGKILL`. Empty or ambiguous ownership still fails closed; the operation may require manual cleanup rather than risk signaling a reused process.
127
+
120
128
  `machine-mcp service status [WORKSPACE]` reports two independent layers: the platform service (`active`) and `workspace_daemon`, plus `effective_active` and `orphaned_workspace_daemon` summary flags. On macOS it is possible for launchd to report inactive while a prior Node process remains alive with parent PID 1; that is an orphan-daemon condition, not proof that the daemon stopped. `service stop` unloads the provider when present and then terminates only a verified service-style workspace daemon. `service uninstall` and full uninstall are ordered fail-closed operations: provider stop → verified daemon stop(s) → definition removal. A failed or ambiguous stop leaves definitions and state intact. If takeover reaches its deadline, run:
121
129
 
122
130
  ```sh
@@ -145,13 +153,31 @@ After global installation, Windows users may open any `cmd.exe` window and run `
145
153
 
146
154
  ### Start, restart, and cross-workspace ownership
147
155
 
148
- `service start` is an idempotent ensure-running operation. It does not stop a verified already-running daemon, so calling it through that daemon cannot destroy the control connection. `service restart` is separate: macOS and systemd-user use a detached service-manager handoff so the command can return before the old daemon exits. Windows restart is fail closed from inside the running task because Task Scheduler `/End` may terminate the helper with the daemon; use an independent terminal stop/start sequence until a behaviorally verified Windows handoff exists.
156
+ `service install` writes a machine-global owner record before it touches launchd, systemd, or Task Scheduler. The record is `pending` during provider mutation and becomes `committed` only after the exact canonical workspace, state root, runtime entrypoint, and package version are installed. A partial or ambiguous provider failure remains pending; `service start` and `service restart` then refuse to run until installation is repeated successfully. A missing or malformed owner is reported by `service status` as `missing` or `invalid` rather than hiding the provider diagnostics.
157
+
158
+ `service start` is an idempotent ensure-running operation, but provider state is only an intermediate observation. It loads the committed owner, verifies that no foreground, unverifiable, or orphan service daemon conflicts with it, starts the provider, and waits for the exact service-mode daemon to publish its token-protected startup-readiness checkpoint. The daemon publishes that checkpoint once, only after device authentication, relay probe, and `ready_ack`; a stable launchd PID or a Windows task that briefly reports `Running` is not sufficient. Failed readiness triggers a bounded provider stop and reports whether cleanup itself was verified. A Windows task that completes and returns to `Ready` remains `completed_without_persistence`, even when its process exit code was zero.
159
+
160
+ `service restart` uses the same owner/readiness convergence after its detached service-manager handoff. Windows restart remains fail closed from inside the running task because Task Scheduler `/End` may terminate the helper with the daemon; use an independent terminal stop/start sequence until a behaviorally verified Windows handoff exists.
161
+
162
+ A workspace/state selector does not grant authority over the machine-global service label. All service mutations share one fixed per-user machine-service lock. Operations that also need a workspace startup lock acquire machine-service first; foreground startup releases the machine lock after service takeover and daemon-lock acquisition rather than holding it for the lifetime of the foreground runtime. Stop, restart, foreground takeover, secret rotation, and uninstall still require exact live ownership evidence. Service status returns only provider state, bounded owner metadata, verified daemon state, PID/run counters, readiness, and classified termination state; provider environment dumps and owner paths are never returned.
163
+
164
+ ### Candidate activation authentication recovery
165
+
166
+ Exact candidate activation distinguishes ordinary health from device-authenticated relay readiness. If a current-version candidate receives an explicit authentication rejection before WebSocket admission, activation redeploys the same Worker once with the unchanged selected device identity and retries candidate startup within a three-attempt bound. It does not rotate credentials or create another Worker.
167
+
168
+ If activation still fails after remote preparation and reports that a compatible candidate service was installed and started, preserve the state root and logs, then inspect:
169
+
170
+ ```sh
171
+ machine-mcp service status
172
+ machine-mcp status
173
+ machine-mcp doctor
174
+ ```
149
175
 
150
- A workspace/state selector does not grant authority over the machine-global service label. Stop, restart, foreground takeover, secret rotation, and uninstall require the exact live verified `service` daemon ownership record. Service status returns only provider, installed/loaded/active state, PID, bounded run count, and classified termination state; provider environment dumps are never returned.
176
+ Do not use secret rotation, state deletion, manual version edits, or repeated forced deployment as a generic repair. The compatible service is forward recovery for an already advanced Worker; the reported primary error still requires diagnosis. Candidate activation checks foreground/unverifiable ownership before any service-manager mutation. If a foreground instance is reported, leave it running until the command prints a verified recovery sequence; only a recovery helper that revalidates the PID, daemon lock, command line, workspace, state root, package name, version, and real entrypoint may name the older CLI used to restore the previous login service. Never substitute the candidate CLI for that older runtime.
151
177
 
152
178
  ## Current upgrade convergence
153
179
 
154
- The current release advertises only MCP protocol `2025-11-25`. Upgrade the MCP client or host if it cannot negotiate that version; Machine Bridge does not retain an obsolete protocol dispatcher. Version 3 also requires matching Worker, daemon, CLI, and browser-extension components and may perform a two-phase device-root migration. Preserve the state root and follow [UPGRADING.md](UPGRADING.md); do not delete state to force apparent convergence.
180
+ The current release advertises modern MCP `2026-07-28` as the primary protocol and retains MCP `2025-11-25` only as an initialization-based compatibility adapter. A modern client must send per-request metadata and the required Streamable HTTP headers; it must not expect a session ID, GET stream, or `Last-Event-ID` replay. A legacy client may continue to initialize and use the signed-session recovery contract. Version 3 also requires matching Worker, daemon, CLI, and browser-extension components and may perform a two-phase device-root migration. Preserve the state root and follow [UPGRADING.md](UPGRADING.md); do not delete state to force apparent convergence.
155
181
 
156
182
  Use this sequence:
157
183
 
@@ -159,13 +185,13 @@ Use this sequence:
159
185
  2. Run `machine-mcp --verbose` once. Startup verifies package/Worker versions, performs the ordinary authenticated Worker convergence when needed, stops only a verified service-style old daemon, waits for its lock, and starts the installed version.
160
186
  3. Run `machine-mcp status` and `machine-mcp doctor`.
161
187
  4. Reload the unpacked browser extension and revisit the pairing page. Exact package version and capability equality are required before browser readiness is reported.
162
- 5. Reconnect MCP clients so they initialize with the current protocol and tool metadata.
188
+ 5. Reconnect MCP clients. Modern clients should rediscover the server and send fresh per-request metadata; legacy clients should reinitialize and retain the returned session only for the legacy connection.
163
189
 
164
190
  A failed state read, unverifiable process owner, active managed job, Worker authentication failure, or extension version mismatch remains fail closed. Preserve the state root and logs for diagnosis rather than deleting them to force apparent success.
165
191
 
166
192
  ## State-root safety and removal
167
193
 
168
- The state root must be a dedicated directory and must not equal, contain, or be contained by the selected workspace. Do not point `--state-dir` at a project directory. On POSIX, every state, profile, job, service-log, browser-pairing, and temporary-secret directory is descriptor-opened without following the final symlink, restricted to `0700`, and revalidated; failure stops the operation. State/config and lock files are owner-only, bounded, and committed through flushed atomic primitives. A permission, type, symbolic-link, size, encoding, or I/O failure is reported; only successfully read invalid JSON is moved to a bounded `.corrupt-*` backup. Removal applies the same fail-closed rule to global config, every profile state, and daemon ownership records.
194
+ The state root must be a dedicated directory and must not equal, contain, or be contained by the selected workspace. Do not point `--state-dir` at a project directory. The default profile state is `~/.local/state/machine-bridge-mcp` on POSIX (or the application directory under XDG/APPDATA); the machine-global service lock and owner ledger use a separate sibling control directory ending in `-control`. Never use the control root as `--state-dir` or copy profile state into it. On POSIX, every state, profile, job, service-log, browser-pairing, and temporary-secret directory is descriptor-opened without following the final symlink, restricted to `0700`, and revalidated; failure stops the operation. State/config and lock files are owner-only, bounded, and committed through flushed atomic primitives. A permission, type, symbolic-link, size, encoding, or I/O failure is reported; only successfully read invalid JSON is moved to a bounded `.corrupt-*` backup. Removal applies the same fail-closed rule to global config, every profile state, and daemon ownership records.
169
195
 
170
196
  Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state operations and state-backed operations from already constructed managed-job/browser managers, then scans all known profiles, active managed jobs, daemon/startup locks, global workspace selection, profile state, daemon lock workspace metadata, the state marker, and directory shape. It rechecks jobs and locks after stopping services/daemons. An unreadable lock is treated as a blocker, not as inactivity. Do not manually delete a lock merely because it looks old; inspect the recorded PID and command first.
171
197
 
@@ -173,7 +199,7 @@ Uninstall acquires a state-root `maintenance.lock` that blocks new profile/state
173
199
 
174
200
  ### Lifecycle and pending-call diagnosis
175
201
 
176
- `server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports the internal-call index, client request-key index, and detached-call count. All three must return to zero after a terminal result, explicit cancellation, timeout, or reconnect-grace expiry. An HTTP/SSE client disconnect alone is transport disposal and may leave a recoverable call active until terminal delivery or the normal lifecycle boundary. During a brief daemon interruption, `active` and `request_keys` may remain nonzero while `detached` identifies the recoverable subset; after same-instance readiness, `detached` returns to zero without losing those requests. Nonzero request-key counts after active calls reach zero indicate a lifecycle defect rather than normal load. `worker.observability.calls.unmatched_results` is the bounded counter for late results that no longer have a receiver.
202
+ `server_info.runtime.lifecycle` reports `ready`, `starting`, `running`, `failed`, `stopping`, or `stopped`. `server_info.observability.in_flight_calls` and `server_info.runtime.processes` distinguish a blocked call from a surviving process. `server_info.runtime.execution_guardrails` reports the enforced local concurrency/timeout/stdin/output/session limits and explicitly states that CPU quota, memory quota, and network isolation are not enforced in process. Worker `server_info.worker.pending_calls` reports the internal-call index, legacy request-key index, and detached-call count. Modern HTTP stream closure should remove its transient stream owner and pending daemon call; there is no modern replay record or request-key entry. Legacy terminal result, explicit cancellation, timeout, or reconnect-grace expiry must return active/detached/request-key counts to zero. During a brief daemon interruption, legacy `active` and `request_keys` may remain nonzero while `detached` identifies the recoverable subset; after same-instance readiness, `detached` returns to zero without losing those requests. Nonzero legacy request-key counts after active calls reach zero indicate a lifecycle defect rather than normal load. `worker.observability.calls.unmatched_results` is the bounded counter for late results that no longer have a receiver.
177
203
 
178
204
  Stable errors include `policy_denied`, `invalid_request`, `timeout`, `cancelled`, `network_error`, `unavailable`, `limit_exceeded`, and `integrity_error`, with retryability metadata. Diagnose by code first; free-form messages are guidance, not an API contract.
179
205
 
@@ -182,7 +208,7 @@ Windows Task Scheduler limits the `/TR` action text, so the platform adapter wri
182
208
 
183
209
  The Windows trigger is current-user `ONLOGON` with `LIMITED` run level. After a reboot, signing in to that user is sufficient; no terminal command is required. Pre-login operation is intentionally not provided by the default design because it would require a different service-account/credential boundary. Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the current Node/package directories, the operator's inherited absolute PATH entries, and platform defaults. When installation runs through npm, all nested run-script prefixes through the final npm private `node-gyp-bin` marker are discarded; paths belonging to inactive candidate runtimes are also removed. This prevents prerelease activation from persisting source-repository shims or the prior runtime that activation subsequently prunes, while an ordinary user-supplied `node_modules/.bin` remains valid. A private allowlisted `service-environment.json` preserves `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, matching lowercase forms, optional `ALL_PROXY`, Node proxy selection, and custom-CA path variables. Existing saved values survive an environment-free reinstall, while explicitly supplied values replace case-insensitive prior variants. Values may include proxy credentials, so the file stays in owner-only state and is never returned or logged; status exposes key names only. Re-run `machine-mcp service install` after changing Node installation families, PATH layout, proxy, or CA configuration. Custom Windows state paths used for autostart must also remain within the Task Scheduler path limit and must not contain a literal `%`.
184
210
 
185
- A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup. If the log schema marker does not match the current format, the active files are cleared before startup and the current marker is written. Runtime code reads and maintains only the active filenames.
211
+ A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Installed services pass `--log-level warn --log-format json`, so each active line is a bounded JSON event suitable for ingestion. Files are owner-only where supported and tail-trimmed before daemon startup and every 15 minutes while the background daemon remains active. Runtime maintenance reuses the same no-follow, regular-file, single-link, `0600`, schema, UTF-8, and line-boundary checks. If the log schema marker does not match the current format, the active files are cleared before startup and the current marker is written. Runtime code reads and maintains only the active filenames.
186
212
 
187
213
  Logging is level-based:
188
214
 
@@ -280,7 +306,7 @@ Defense-in-depth limits include:
280
306
  - process stdin write: 64 KiB per call;
281
307
  - local simultaneous tool calls: 16;
282
308
  - Worker pending daemon calls: 32;
283
- - command timeout: 1–600 seconds;
309
+ - local/stdio command timeout schema: 1–600 seconds; the remote Worker schema is 1–85 seconds with tool-specific 30- or 60-second defaults, reserves five seconds for terminal delivery, and rejects larger values before dispatch;
284
310
  - process-session read wait: at most 30 seconds, measured with monotonic elapsed time;
285
311
  - direct directory result: 10,000 entries and 4 MiB of path metadata;
286
312
  - recursive walk: 200,000 visited entries;
package/docs/PRIVACY.md CHANGED
@@ -34,12 +34,26 @@ Ignored does not mean safe for secrets: do not store passwords, tokens, private
34
34
 
35
35
  ## Runtime instruction context
36
36
 
37
- When automatic project context is enabled, Machine Bridge sends a small generated block through the same authorized MCP transport as other session instructions. It may contain the target path relative to the repository root, recognized project/build filenames, package-manager and lockfile names, package script names, runtime constraints, common documentation filenames, and CI workflow filenames.
37
+ When automatic project context is enabled, Machine Bridge sends a small generated block through the authorized MCP transport. Modern clients retrieve it explicitly through `session_bootstrap` or capability resolution; legacy initialization may include the same bounded material through its compatibility response. It may contain the target path relative to the repository root, recognized project/build filenames, package-manager and lockfile names, package script names, runtime constraints, common documentation filenames, and CI workflow filenames.
38
38
 
39
39
  The generator does not include package script bodies, dependency names or versions, source/document contents, environment values, absolute home paths, or command output. It executes nothing and writes no repository or user files. File and script names can still reveal project structure, so users who do not want that metadata to traverse a remote Worker/host can set `"automatic_project_context": false` in `~/.config/machine-bridge-mcp/agent.json`. Built-in instructions can be disabled separately with `"builtin_instructions": false`.
40
40
 
41
41
  Neither generated nor explicit instruction content is written to ordinary operational logs.
42
42
 
43
+ ## Capability-routing privacy
44
+
45
+ Task routing is computed only from the current request, bounded project/skill/command metadata, public tool descriptions, and policy-visible application/browser metadata. The effective authenticated account policy is applied before local application discovery or route construction; a restricted role cannot use the resolver as an inventory side channel for hidden shell, browser, application, or write capabilities.
46
+
47
+ The returned route set contains tool names, coarse scores, named reasons, ambiguity, and fallbacks. It does not include command bodies, secret values, application documents, browser page data, or tool arguments. Runtime observability keeps only an HMAC task fingerprint and coarse route fields; raw task text and route explanations are not logged. A client-supplied `known_refresh_fingerprint` is a content identity for static context, not a bearer credential.
48
+
49
+ ## Protocol validation privacy
50
+
51
+ Modern Streamable HTTP mirrors protocol version, method, tool/resource/prompt name, and explicitly annotated primitive tool parameters into headers. These values are used only for routing consistency and are compared with the body before dispatch. Operational logs omit all request headers and every `Mcp-Param-*` value; mismatch errors identify only the field class and do not echo either side. Unknown names, methods, metadata keys, extension keys, and unsupported-version data are bounded or omitted rather than reflected verbatim.
52
+
53
+ Modern response closure is conveyed by a random internal stream capability. Public requests cannot set it because the outer Worker removes both internal headers, and the credential-free cancel control forwards no access token or DPoP proof. The capability is used only to remove one active pending call and is never logged or persisted as user-visible evidence.
54
+
55
+ Tool schemas are compiled locally. Network `$ref` dereference is unsupported, so schema validation cannot turn a catalog entry into an outbound metadata request or SSRF channel. Runtime validation also has a total step budget: each array item and own object property consumes work without first allocating an unbounded key list. Open `_meta`, capability-extension, and subscription-filter JSON has an independent 4,096-node/32-level/bounded-key limit; resource subscription lists are count/length bounded. Failures report only JSON Pointer path, keyword, and constraint text; the rejected value is never copied into an error or log, even when it resembles a credential.
56
+
43
57
  ## Review rules
44
58
 
45
59
  Before committing or publishing:
@@ -57,8 +71,8 @@ The scanner is heuristic. It cannot identify every personal or organizational na
57
71
  ## Incident response
58
72
 
59
73
  For an accidental publication, remove the value from the current tree and release artifacts, determine whether it is merely identifying metadata or an active credential, and rotate/revoke any credential immediately. Public Git and npm history are immutable in ordinary workflows: replacing the current file does not erase old commits or a published package. A coordinated history rewrite, cache invalidation request, or replacement release may be appropriate, but those actions are disruptive and require an explicit repository-owner decision.
60
- ## Transient resumable result storage
74
+ ## Legacy transient resumable result storage
61
75
 
62
- For Streamable HTTP recovery, the workspace Durable Object may temporarily persist the terminal JSON-RPC response of a remote tool call. This response can contain source text, command output, file metadata, images encoded by the protocol, or other user data returned by the requested tool. It is operational delivery state, not anonymized telemetry and not publication-safe evidence.
76
+ Modern MCP `2026-07-28` response streams are not persisted or resumable: closing the stream cancels the request and releases its transient in-memory owner. For legacy MCP `2025-11-25` Streamable HTTP recovery, the workspace Durable Object may temporarily persist the terminal JSON-RPC response of a remote tool call. This response can contain source text, command output, file metadata, images encoded by the protocol, or other user data returned by the requested tool. It is operational delivery state, not anonymized telemetry and not publication-safe evidence.
63
77
 
64
- Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute terminal-retention window. While a streamed call is active, the record also contains the tool name, opaque call and WebSocket-generation identifiers, daemon-process identifier, client request correlation, operation/reconnect deadlines, and bounded account metadata needed only to project `project_overview`. It does not persist tool arguments or an in-progress result. Records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value after terminal serialization, and are removed on expiry or completed-record eviction. Opaque call, connection, stream, and event identifiers are correlation values rather than bearer credentials. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
78
+ Persistence is bounded to 64 streams, at most 1.5 MiB per terminal response, and a two-minute terminal-retention window. While a streamed call is active, the record also contains the tool name, opaque call and WebSocket-generation identifiers, daemon-process identifier, client request correlation, operation/reconnect deadlines, and bounded account metadata needed only to project `project_overview`. It does not persist tool arguments or an in-progress result. Legacy records are bound to the OAuth access-token identity and signed MCP session, carry a SHA-256 integrity value after terminal serialization, and are removed on expiry or completed-record eviction. Opaque call, connection, stream, and event identifiers are correlation values rather than bearer credentials. The digest detects accidental corruption; it is not a signature against an attacker who controls the Durable Object. Normal logs continue to omit tool arguments and results.
@@ -26,9 +26,9 @@ Repository automation owns implementation, local validation, candidate preparati
26
26
 
27
27
  The tracked `release-acceptance/v<version>.json` binds npm hashes, a portable package digest, and a version-normalized promotion digest. Any packaged change invalidates acceptance. `npm run github:push`, CI, and source-release commands verify the record. Raw pushes of package branches are prohibited.
28
28
 
29
- The accepted prerelease is released with `npm run prerelease:release`; npm publication remains an explicit owner operation through `npm run prerelease:publish`. The owner installs the exact registry version with `npm run prerelease:install -- --allow-worker-deploy`. Formal soak begins only from this registry-verified activation. Minimum soak is seven days for major, three days for minor, and one day for patch releases. A blocking defect increments the prerelease number and restarts the soak interval.
29
+ The accepted prerelease's Git tag and GitHub Prerelease are created only by the repository owner from a real interactive terminal with `npm run prerelease:release -- --owner-terminal-confirm`; npm publication remains a separate explicit owner operation through `npm run prerelease:publish`. The owner installs the exact registry version with `npm run prerelease:install -- --allow-worker-deploy`. Formal soak begins only from this registry-verified activation. Minimum soak is seven days for major, three days for minor, and one day for patch releases. A blocking defect increments the prerelease number and restarts the soak interval.
30
30
 
31
- Stable promotion requires a tracked `release-soak/v<stable>.json` and identical promotion-content digest. Only normalized release metadata may differ from the soaked prerelease. The stable candidate is activated and observed again before `npm run release`; npm stable publication remains an explicit owner operation through `npm run stable:publish`.
31
+ Stable promotion requires a tracked `release-soak/v<stable>.json` and identical promotion-content digest. Only normalized release metadata may differ from the soaked prerelease. The stable candidate is activated and observed again before the repository owner creates the final Git tag and GitHub Release from a real interactive terminal with `npm run release -- --owner-terminal-confirm`; npm stable publication remains a separate explicit owner operation through `npm run stable:publish`.
32
32
 
33
33
  GitHub-only repository infrastructure changes that do not alter npm package contents do not require a synthetic version, candidate activation, or soak. They still require review and applicable checks.
34
34
 
package/docs/RELEASING.md CHANGED
@@ -44,12 +44,25 @@ npm version 3.0.0-beta.1 --no-git-tag-version
44
44
 
45
45
  The version hook synchronizes package metadata, Worker version, and browser-extension metadata.
46
46
 
47
+ When MCP protocol behavior changes, an official conformance checkout may be run without adding the alpha runner to the package dependency graph:
48
+
49
+ ```sh
50
+ MBM_OFFICIAL_CONFORMANCE_CHECKOUT=/path/to/modelcontextprotocol-conformance \
51
+ MBM_OFFICIAL_CONFORMANCE_SCENARIOS=http-header-validation,caching,server-stateless \
52
+ MBM_OFFICIAL_CONFORMANCE_TIMEOUT_MS=75000 \
53
+ npm run worker:integration-test
54
+ ```
55
+
56
+ The test-only loopback proxy injects the integration account's short-lived bearer token; it must never be enabled in production. Expected failures are check-scoped in `tests/mcp-conformance-baseline.yml` and may cover only intentionally absent capabilities. A new failure or stale baseline blocks release.
57
+
47
58
  Run the complete local gate and inspect the diff:
48
59
 
49
60
  ```sh
50
61
  npm run check
51
62
  npm audit --audit-level=high
52
63
  npm audit --omit=dev --audit-level=high
64
+ npm audit signatures
65
+ npm run sbom:test
53
66
  npm run worker:dry-run
54
67
  npm pack --dry-run
55
68
  ```
@@ -60,7 +73,7 @@ Generate the exact tarball:
60
73
  npm run release:candidate
61
74
  ```
62
75
 
63
- The candidate manifest records npm SHA-1/SHA-512 values and a promotion-content digest. Any packaged-file change invalidates the candidate.
76
+ The candidate manifest records npm SHA-1/SHA-512 values and a promotion-content digest. Any packaged-file change invalidates the candidate. Every candidate start or activation recomputes the current digest and compares package identity before tarball verification, npm installation, Worker deployment, or service mutation; a stale but internally self-consistent tarball cannot be installed. Preparing or testing a candidate never authorizes npm publication; only the repository owner may invoke a publication command. An existing tag, GitHub Release, or npm version is immutable and must never be reused after source changes.
64
77
 
65
78
  ## 2. Owner activates the exact candidate
66
79
 
@@ -72,17 +85,20 @@ npm run release:candidate:activate -- --allow-worker-deploy
72
85
 
73
86
  The command:
74
87
 
75
- - verifies the exact pending tarball;
76
- - installs it under the owner-only Machine Bridge state root, separate from the normal global installation;
88
+ - verifies that the pending manifest still matches the current packaged source, then verifies the exact pending tarball;
89
+ - installs it under the owner-only ordinary Machine Bridge profile state root, separate from both the normal global installation and the machine-service control root;
90
+ - acquires the machine-global service lock before the workspace startup lock and rejects a foreground or unverifiable daemon before changing the service manager;
77
91
  - stops only a verified existing service daemon;
78
92
  - updates the configured same-name Worker;
79
93
  - starts the candidate in-process and verifies device authentication plus relay readiness;
80
- - installs the candidate as the login service runtime;
94
+ - if that current-version candidate receives an explicit device-authentication rejection, redeploys the same Worker once with the unchanged selected identity and retries within a three-start bound;
95
+ - installs the candidate as the login service runtime and atomically commits the canonical workspace/state/entrypoint/version owner record;
81
96
  - performs a controlled foreground-to-service handoff;
97
+ - accepts the background runtime only after its matching daemon lock publishes the post-authentication, post-relay-probe `ready_ack` checkpoint;
82
98
  - verifies that both the Worker and verified background daemon report the candidate version;
83
99
  - exits while the background daemon continues running.
84
100
 
85
- It may request one macOS user-presence or Touch ID operation to certify the daemon session key. It does not ask for per-tool approval.
101
+ It may request one macOS user-presence or Touch ID operation to certify the daemon session key. It does not ask for per-tool approval. The wrapper does not impose a transaction-wide hard kill: each internal deployment, health, relay, service-manager, and convergence stage is independently bounded so lock release and compensation cannot be skipped.
86
102
 
87
103
  The private candidate runtime is not stored under the Git checkout, so cleaning `.release-candidate/`, switching branches, or regenerating a candidate cannot delete the daemon currently under test. The previous global installation remains available as recovery information.
88
104
 
@@ -119,10 +135,18 @@ npm run github:push
119
135
 
120
136
  Create/update the pull request, satisfy required checks, squash-merge, fetch, and fast-forward local `main`.
121
137
 
122
- Create the annotated prerelease tag, GitHub Prerelease, and exact tarball asset:
138
+ The repository owner creates the annotated prerelease tag, GitHub Prerelease, and exact tarball asset from a real interactive terminal:
139
+
140
+ ```sh
141
+ npm run prerelease:release -- --owner-terminal-confirm
142
+ ```
143
+
144
+ The flag is necessary but not sufficient: stdin, stdout, and stderr must all be TTYs. MCP calls, managed jobs, CI, redirected sessions, and other ordinary background automation fail before fetch, full verification, tag creation, or remote mutation. One common-Git-dir owner-only publication lock serializes tag/Release writes across the main checkout and linked worktrees. This ceremony prevents accidental and standard non-interactive publication; it is not an authentication boundary against arbitrary code already executing as the same OS user, which can emulate a terminal. Adversarial separation requires an external user-presence or isolated release environment.
145
+
146
+ Historical GitHub Release backfill is governed by the same boundary and must be run by the owner from a real interactive terminal:
123
147
 
124
148
  ```sh
125
- npm run prerelease:release
149
+ npm run release:backfill -- --owner-terminal-confirm
126
150
  ```
127
151
 
128
152
  Publish npm through the repository-controlled channel command:
@@ -195,14 +219,14 @@ The owner activates the exact stable candidate with the same persistent command:
195
219
  npm run release:candidate:activate -- --allow-worker-deploy
196
220
  ```
197
221
 
198
- The coding agent verifies the live stable candidate and records its exact acceptance. Then commit, push with `npm run github:push`, merge, and run:
222
+ The coding agent verifies the live stable candidate and records its exact acceptance. Then commit, push with `npm run github:push`, and merge. The repository owner runs the following from a real interactive terminal:
199
223
 
200
224
  ```sh
201
- npm run release
225
+ npm run release -- --owner-terminal-confirm
202
226
  npm run stable:publish
203
227
  ```
204
228
 
205
- `npm run release` creates the final annotated tag and GitHub Release only after the soak record, promotion digest, exact candidate acceptance, exact `origin/main`, and all required push-triggered checks pass. `stable:publish` always uses `latest` and repeats the same gates.
229
+ `npm run release -- --owner-terminal-confirm` creates the final annotated tag and GitHub Release only after the soak record, promotion digest, exact candidate acceptance, exact `origin/main`, and all required push-triggered checks pass. `stable:publish` always uses `latest` and repeats the same gates.
206
230
 
207
231
  ## Rollback and recovery
208
232
 
@@ -214,7 +238,7 @@ Candidate and prerelease activation retain the previous global installation meta
214
238
  - fix forward when Worker/state protocol has changed;
215
239
  - restore a complete pre-upgrade backup only when package, Worker, browser extension, service definition, and local state can be restored as one unit.
216
240
 
217
- The activation state machine verifies candidate relay readiness before service handoff and cleans up its temporary runtime and locks on installation failure. A failure after the Worker has changed is reported explicitly rather than hidden by an unsafe automatic downgrade.
241
+ The activation state machine verifies candidate relay readiness before service handoff and cleans up its temporary runtime and locks on failure. Before remote preparation changes or verifies the candidate deployment, a provider whose verified stop result requires restoration is restarted after lock cleanup. After remote preparation, activation never revives a daemon known to be incompatible with the current Worker: cleanup installs and starts the compatible candidate service definition instead. This is forward recovery, not a fabricated distributed rollback; the primary failure remains visible, and any candidate-service installation or start failure is aggregated with it.
218
242
 
219
243
  ## External credentials and controls
220
244