machine-bridge-mcp 3.0.0-beta.146 → 3.0.0-beta.154
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 +50 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +8 -6
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +6 -6
- package/docs/AUDIT.md +88 -0
- package/docs/CLIENTS.md +1 -1
- package/docs/ENGINEERING.md +4 -4
- package/docs/GETTING_STARTED.md +6 -6
- package/docs/LOGGING.md +4 -3
- package/docs/MANAGED_JOBS.md +3 -3
- package/docs/OPERATIONS.md +9 -9
- package/docs/PROJECT_STANDARDS.md +2 -2
- package/docs/RELEASING.md +10 -4
- package/docs/TESTING.md +7 -5
- package/docs/TOOL_REFERENCE.md +1 -1
- package/package.json +8 -8
- package/scripts/accepted-candidate-tarball.mjs +41 -3
- package/scripts/check-plan.mjs +14 -4
- package/scripts/coverage-check.mjs +4 -2
- package/scripts/github-backlog.mjs +4 -0
- package/scripts/github-release.mjs +41 -13
- package/scripts/install-published-prerelease.mjs +1 -0
- package/scripts/publish-npm.mjs +52 -13
- package/scripts/source-dependency-tree.mjs +21 -0
- package/src/local/browser-pairing-store.mjs +4 -5
- package/src/local/cli.mjs +1 -1
- package/src/local/exclusive-file.mjs +9 -4
- package/src/local/exclusive-publication-recovery.mjs +70 -0
- package/src/local/hardened-npm.mjs +3 -3
- package/src/local/managed-job-listing.mjs +10 -12
- package/src/local/managed-job-lock.mjs +4 -2
- package/src/local/managed-job-recovery-listing.mjs +10 -1
- package/src/local/managed-job-retention-policy.mjs +5 -2
- package/src/local/managed-job-runner-claim.mjs +7 -6
- package/src/local/managed-job-transient-recovery.mjs +30 -0
- package/src/local/managed-jobs.mjs +8 -2
- package/src/local/owner-state-lock.mjs +24 -13
- package/src/local/relay-connection.mjs +19 -26
- package/src/local/relay-diagnostics.mjs +21 -1
- package/src/local/relay-peer-diagnostics.mjs +13 -0
- package/src/local/relay-reconnect.mjs +40 -0
- package/src/local/resource-transaction-lock.mjs +18 -7
- package/src/local/runtime-diagnostic-projection.mjs +1 -1
- package/src/local/secure-file.mjs +9 -9
- package/src/local/state.mjs +4 -2
- package/src/local/system-sleep-diagnostics.mjs +31 -2
- package/src/local/wrangler-toolchain/package-lock.json +37 -37
- package/src/local/wrangler-toolchain/package.json +2 -2
- package/src/local/wrangler-toolchain.mjs +2 -2
- package/src/shared/log-redaction.mjs +6 -2
- package/src/shared/relay-contract.json +1 -1
- package/src/shared/server-metadata.json +6 -6
- package/src/shared/tool-catalog.json +1 -1
- package/src/worker/daemon-registry.ts +10 -6
- package/src/worker/daemon-relay-diagnostics.ts +34 -1
- package/src/worker/daemon-socket-attachment.ts +2 -0
- package/src/worker/daemon-sockets.ts +1 -1
- package/src/worker/index.ts +9 -3
- package/src/worker/tool-catalog.ts +1 -1
- package/src/worker/worker-continuity-evidence.ts +31 -32
- package/src/worker/worker-socket-disconnect-evidence.ts +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.154 - 2026-08-29
|
|
4
|
+
|
|
5
|
+
- Supersede the locally accepted beta.153 candidate after exact-head PR #114 failed the required JavaScript/TypeScript CodeQL gate with `js/file-system-race` in `exclusive-publication-recovery.mjs`. The finding was actionable: beta.153 recognized a committed two-link exclusive-publication crash residue by inspecting target/alias paths and then deleted the reserved staging pathname. Rechecking identity before `unlink` could not make a pathname-based destructive operation race-free under Node's path APIs, so no CodeQL suppression or accepted-SARIF exception is added.
|
|
6
|
+
- Replace destructive staging-alias cleanup with a non-destructive, descriptor-verified residue token. The verifier opens the canonical target first with the shared no-follow/path-identity boundary, requires exactly two links, opens only candidates in the reserved `.<target>.<pid>.<16hex>.tmp` namespace, accepts exactly one same-generation/same-inode two-link alias, rechecks the held target descriptor, and returns only that exact filesystem identity. Explicitly coupled readers may use the token for one bounded read of the same two-link generation; arbitrary hard links, extra/mismatched aliases, symlinks, changed generations, and ordinary readers remain fail-closed. The verifier itself contains no `unlink`/`rm` path.
|
|
7
|
+
- Stale owner reclamation may remove only the canonical target after the usual owner/token and full generation checks, with a verified two-link token when required. It never deletes the staging alias by pathname. A process killed in the tiny post-link/pre-cleanup window can therefore leave an owner-only internal alias until its containing job/state generation is retired; this is an explicit availability/privacy tradeoff rather than hidden cleanup. Browser-pairing residue remains owner-private and is never returned through MCP or logs. Deterministic process-lock coverage proves live residue is readable without alias mutation, stale canonical ownership can be reclaimed, and arbitrary external hard links are still rejected.
|
|
8
|
+
- Keep dependency freshness closed in the superseding candidate: Wrangler advances from 4.127.0 to 4.127.1 and its exact private control-plane dependency/allowlist advances from workerd 1.20260826.1 to 1.20260828.1. Root and isolated lockfiles, exact install-script policy, private toolchain validation, consumer package security, Worker type generation, package contract, `npm outdated`, and audit all pass after the update.
|
|
9
|
+
- These packaged security/toolchain changes invalidate beta.153 acceptance. Package/runtime identity advances to beta.154; hosted tool schema generation remains 20 because no MCP tool input, result, or owner-visible diagnostic semantics change. Beta.154 requires fresh frozen verification, exact candidate/install-only proof, detached activation, candidate-bound canaries/live checks, acceptance, and new exact-head provider gates before GitHub prerelease creation. npm publication remains a separate exact-version owner authorization boundary.
|
|
10
|
+
|
|
11
|
+
## 3.0.0-beta.153 - 2026-08-29
|
|
12
|
+
|
|
13
|
+
- Supersede the prepared but unaccepted beta.152 candidate after review of the recovered-outage timeline found that one completed multi-attempt outage mixed two different causal timestamps. `recent_outages[].duration_ms` already measured from the first outage transition through final readiness, but `disconnected_at` was populated from `lastDisconnectedAt`, which advances again on every failed reconnect attempt. A three-attempt outage could therefore report a late final-disconnect timestamp next to a duration that began at the first disconnect, causing downstream sleep/wake correlation to evaluate the wrong interval. Beta.153 keeps `disconnected_at` as the first outage start, adds bounded `last_disconnect_at` for the final failed reconnect transition, and makes active-outage correlation prefer the existing `outage_started_at` field.
|
|
14
|
+
- Preserve bounded first-failure liveness context instead of letting later reconnect attempts overwrite the only evidence about how the outage began. The local relay freezes the initial protocol-Ping dispatch/outstanding state, transport-confirmation state, and application-inbound silence when the first outage transition is recorded, retains those fixed boolean/millisecond fields in the newest-first eight-entry outage ring, and sanitizes the same fields for peer diagnostics. Tests cover a multi-attempt timestamp split and a synthetic protocol-Ping dispatch failure so the first-disconnect evidence cannot silently collapse back to final-attempt state. No endpoint, interface, address, DNS answer, close reason, account/client identity, call ID, tool argument, or result is added.
|
|
15
|
+
- Beta.152 had completed frozen verification and candidate packaging but remained `pending` and had no tracked acceptance record. Because beta.153 changes packaged runtime bytes and owner-visible diagnostics after that freeze, the beta.152 tarball cannot authorize activation or release. Package/runtime identity advances to beta.153 and hosted tool schema generation advances from 19 to 20 because the owner-visible relay diagnostic contract now adds first/final disconnect and first-failure liveness fields. Beta.153 requires fresh frozen verification, candidate/install-only proof, detached activation, activated-package canaries, live generation-20/default-40s/explicit-60s continuation verification, diagnostic verification, new acceptance, and exact-head provider gates. npm publication remains a separate exact-version owner authorization boundary.
|
|
16
|
+
|
|
17
|
+
## 3.0.0-beta.152 - 2026-08-29
|
|
18
|
+
|
|
19
|
+
- Reject beta.151 as a release candidate after the owner-visible interruption class reproduced during the PR-check phase on a second explicit `read_job(wait_ms=180000)` call. The host returned `mcp_network_error: Connection failed` for the deployed Worker `/mcp` endpoint. Generation-18 continuity evidence after recovery showed the same daemon WebSocket had remained ready since 15:44:20Z, `outage_count=0`, no recorded relay disconnect, and `unplanned_ready_socket_disconnects=0`; local runtime diagnostics also showed no event-loop stall, resource pressure, or system-sleep correlation. This narrows the reproduced failure to the outer hosted HTTP/tool-settlement path rather than Worker-to-daemon WSS.
|
|
20
|
+
- Remove the contradictory five-minute hosted-read allowance. Remote `read_job` still defaults to a 40-second server-side long-poll, but its public `wait_ms` maximum is now 60 seconds, matching the longest explicit per-call duration already carried to a structured result by the target host. Values above 60 seconds are rejected at the hosted Worker schema/daemon relay extension instead of letting callers enter an unverified long HTTP request. Long jobs keep durable ownership and autonomous same-response progress by issuing another paced `read_job` for the same `job_id`; local/stdio inspection retains its existing 40-second maximum. The independent 315-second relay result-retention/acknowledgement ceiling is unchanged because it protects settlement ownership rather than authorizing one host request to stay open that long.
|
|
21
|
+
- These host-visible schema and orchestration semantics supersede beta.151 acceptance. Package/runtime identity advances to beta.152 and hosted tool schema generation advances from 18 to 19. Beta.152 requires fresh frozen verification, exact candidate/install-only proof, detached activation, activated-package OAuth and freshness canaries, live default-40s plus explicit-60s continuation probes, new acceptance, and exact-head provider gates. npm publication remains a separate exact-version owner authorization boundary.
|
|
22
|
+
|
|
23
|
+
## 3.0.0-beta.151 - 2026-08-28
|
|
24
|
+
|
|
25
|
+
- Treat the owner's continuing severe interruption report as release-blocking and correct Worker continuity evidence before drawing another transport conclusion. Durable continuity schema 1 could over-count stale close/error callbacks because the per-isolate cleanup `WeakMap` disappears across Durable Object isolate replacement; a socket already serialized as `expired` could later deliver another close in a new isolate and increment the durable counters again. Planned-drain intent also lived only in an isolate-local `WeakSet`, so a planned shutdown crossing isolate replacement could be mislabeled unplanned. Schema 2 records a socket disconnect only for the first cleanup transition, persists WebSocket draining intent in the socket attachment, classifies the bounded pre-cleanup role/`was_ready`/`connected_at`, and adds `ready_socket_disconnects`, `unplanned_ready_socket_disconnects`, and `last_ready_socket_disconnect` so candidate/probing churn cannot be read as an authoritative ready-channel outage. Legacy schema-1 disconnect counters are deliberately reset instead of inheriting unqualified history. No account/client identity, daemon/connection ID, call ID, endpoint, close reason, argument, or result is added.
|
|
26
|
+
- Repair a separate crash-consistency blocker exposed by the beta.149 canonical release verification. Exclusive owner-state publication commits with `link(staging,target)` before unlinking the private staging name; SIGKILL in that narrow window can leave the valid committed target at `nlink=2`, after which secure readers correctly reject it but previously had no bounded recovery path. Owner-state readers now retry the normal transient window and may remove exactly one same-inode alias inside the reserved internal `.<target>.<pid>.<random>.tmp` publication namespace. Recovery holds the target open with `O_NOFOLLOW`, revalidates the alias, unlinks only that reserved staging name, and proves the target path still references the held `dev+ino` with one link. Hard links outside that exact internal namespace, symlinks, multiple aliases, and identity changes remain fail-closed. Process-lock regression reproduces the post-link crash window while retaining the existing hostile-hardlink rejection.
|
|
27
|
+
- These packaged runtime and owner-visible diagnostic changes supersede beta.150 release evidence. Package/runtime identity advances to beta.151 and hosted tool schema generation advances from 17 to 18. Beta.151 requires fresh frozen verification, exact candidate/install-only proof, detached activation, activated-package canary, generation-18 live `server_info` verification, new acceptance, and exact-head provider gates before GitHub prerelease creation. npm registry publication remains separately owner-authorized for the exact beta.151 version.
|
|
28
|
+
|
|
29
|
+
## 3.0.0-beta.150 - 2026-08-28
|
|
30
|
+
|
|
31
|
+
- Supersede the accepted but unpublished beta.149 candidate after the canonical GitHub prerelease command exposed another ignored-worktree coupling. The merged `main` exact head passed its provider gates and `prerelease:release` then passed the complete 131-task frozen plan plus acceptance/CI revalidation, but publication stopped before tag creation because the canonical main worktree still held an ignored beta.146 `.release-candidate/manifest.json`. The documented workflow moves an accepted candidate through review/squash-merge and then runs `prerelease:release` from fast-forwarded `main`; an ignored candidate directory does not follow that Git transition and therefore cannot be release authority. Read-only reconciliation confirmed beta.149 never created a remote tag or GitHub Release and remains absent from npm.
|
|
32
|
+
- Make accepted-candidate staging independent of stale or missing checkout-local candidate caches without weakening exact-byte guarantees. When GitHub or npm publication supplies its integrity-pinned hardened npm CLI, the shared staging layer deterministically rematerializes the current package in a private temporary directory, requires package name/version/filename/SHA-1/SRI and the promotion-content digest to equal the tracked acceptance record, verifies the resulting tarball again, and writes the publication copy as an owner-private `0600` file. Low-level candidate resolution without an explicit npm CLI still validates the existing `.release-candidate` manifest/tarball and fails closed, so activation/developer workflows do not silently repair stale local evidence. GitHub and npm publication now pass their already-established hardened npm CLI into this shared boundary. Package/runtime identity advances to beta.150; hosted tool schema generation remains 17 because no MCP catalog/result semantics change.
|
|
33
|
+
|
|
34
|
+
## 3.0.0-beta.149 - 2026-08-28
|
|
35
|
+
|
|
36
|
+
- Supersede the locally accepted beta.148 candidate after a third independent live review found a sleep/wake causality error in `diagnose_runtime`. Four recovered WebSocket outages were first recorded exactly when macOS sleep intervals ended, including a latest 299.5-second daemon event-loop stall independently matching a 305-second maintenance sleep, yet `relay_outage_analysis` reported `no_matching_recent_system_sleep` because the existing algorithm considered only overlap between `last_disconnected_at` and `last_ready_at`. JavaScript cannot observe a socket close while the process is suspended, so a failure discovered on the first post-wake timer/probe can legitimately have zero close-to-ready sleep overlap. The diagnostic now classifies that narrow case as `wake_boundary_system_sleep_aftermath` only when the disconnect is within the fixed thirty-second wake tolerance and the same sleep independently matches the runtime event-loop stall in both end time and duration. A coincidental near-wake reset without matching stall evidence remains `no_matching_recent_system_sleep`; ordinary overlap classifications are unchanged.
|
|
37
|
+
- Update owner diagnostic guidance and hosted metadata for the new causal distinction. Tool schema generation advances to 17 because `diagnose_runtime.runtime.relay_outage_analysis.classification` has a new observable value and the host-visible interpretation changes. Beta.148 acceptance is removed because packaged local runtime, shared metadata, tests, and documentation change; package/runtime identity advances to beta.149 and requires fresh frozen verification, candidate/install-only proof, guarded activation, activated-package OAuth canary, live wake-boundary diagnostic verification, acceptance, and exact-head provider checks before publication. npm registry publication remains separately owner-authorized.
|
|
38
|
+
|
|
39
|
+
## 3.0.0-beta.148 - 2026-08-28
|
|
40
|
+
|
|
41
|
+
- Supersede the accepted but unpublished beta.147 candidate after a second independent review found a release-control defect during the first canonical GitHub prerelease attempt. That attempt passed 126 full-plan tasks and then failed at the real `npm sbom` gate because the checkout's ignored `node_modules` still contained the pre-upgrade direct dependency versions even though `package.json` and the lockfile were current. No beta.147 local/remote tag, GitHub Release, or npm package exists. GitHub and npm publication paths now create hardened npm first, rebuild the source dependency tree from the committed lockfile with explicit `npm ci`, and only then run full verification / acceptance revalidation; npm publication no longer uses the ambient lifecycle npm to regenerate acceptance bytes. Long GitHub-release npm stages now reuse the shared process-tree hard-timeout settlement instead of direct-child-only `spawnSync`, and the full plan runs the real SBOM check first so an invalid dependency tree fails before expensive platform/coverage work. Standalone GitHub backlog probes also gain a fixed two-minute hard timeout and bounded output.
|
|
42
|
+
- Close a free-form operational-log privacy gap: shared redaction now recognizes `mcp_rt_*` refresh tokens plus stable account/client/refresh-family identifiers both by field name and when embedded in ordinary diagnostic text. Local logger, Worker observability, Worker edge logger, and release-diagnostic regressions cover those forms without changing the intentional raw owner-terminal provisioning boundary. Architecture growth budgets now also cover the previously unbounded `state.mjs` and `service.mjs` composition modules so further growth requires an explicit responsibility decision rather than silent accumulation. A package-mode regression also caught the new dependency helper at `0600`; it is normalized to ordinary packaged-file mode `0644`.
|
|
43
|
+
- Invalidate and remove beta.147 candidate acceptance because these shipped script/shared-runtime/documentation changes alter the accepted package bytes after live verification. Package/runtime identity advances to beta.148 while hosted tool schema generation remains 16; beta.148 requires a fresh frozen full verification, candidate/install-only proof, guarded activation, activated-package OAuth canary, live verification, acceptance, and exact-head provider gates before any GitHub prerelease. npm registry publication remains separately owner-authorized.
|
|
44
|
+
|
|
45
|
+
## 3.0.0-beta.147 - 2026-08-28
|
|
46
|
+
|
|
47
|
+
- Reject beta.146 as a GA-soak candidate after an independent live continuity review reproduced loss of a one-step durable helper's recovery record under shared managed-job churn. A hosted helper that still requires `read_job` follow-up now carries a private `transient_recovery_pending` marker and receives stronger retention priority for the existing thirty-minute grace; once Machine Bridge produces a terminal hosted response, that marker is cleared under the transition lock and the result returns to the bounded newest-16 transient delivery reserve. `recent_process_recovery` remains capped at 16 public handles, prioritizes retained follow-up-required helpers, and still exposes no step output or internal retention metadata. Hosted tool schema generation advances to 16 because recovery inventory semantics changed.
|
|
48
|
+
- Keep WebSocket reconnect failure history across short verified-ready flaps. `ready_ack` still restores business traffic immediately, but reconnect backoff is no longer reset at that instant; a generation-fenced five-second stable-ready window clears the prior attempt history, while a socket that drops sooner keeps its exponential backoff position. This follows the minimum-uptime pattern found during a 30-project source review without delaying relay readiness or changing the existing Ping/Pong/application-heartbeat liveness proof.
|
|
49
|
+
- Upgrade all outdated direct dependencies to current exact releases: `ws` 8.21.3, `@types/node` 26.4.0, ESLint 10.9.1, `globals` 17.11.0, and Wrangler 4.127.0. Advance the repository/package-manager and hardened publication/install bootstrap from npm 12.0.1 to current npm 12.0.2 using its exact registry SHA-512 identity while retaining the reviewed undici 6.28.0 and brace-expansion 5.0.9 replacements; a real temporary hardened 12.0.2 construction passes the existing package/tree verifier. Synchronize the isolated Wrangler control-plane toolchain to 4.127.0 / workerd 1.20260826.1, review the exact install-script allowlist, regenerate Worker types, and keep the Worker dry-run green. Workspace supply-chain verification reports zero audit vulnerabilities, no unreviewed install scripts, 109 verified registry signatures, and 35 verified attestations.
|
|
50
|
+
- Harden the npm publication authentication boundary exposed by the real beta.146 EOTP attempt. Non-TTY automation captures npm upload output instead of inheriting one-time browser-auth challenge URLs; real owner terminals still inherit stdio so npm can complete Web OTP in the same process. EOTP plus registry-confirmed absence is reported as authentication-required rather than ambiguous, challenge URLs are redacted by release diagnostics, and all publication npm stages use `--logs-max=0` so those one-time URLs are not persisted in npm cache debug logs. Published-prerelease activation now also reminds operators to reload the unpacked Chromium extension before treating browser behavior as soak evidence.
|
|
51
|
+
- Expand independent security/test review rather than relying on the existing green suite. Device root/session identity tests now exercise malformed JWK, origin, transcript, certificate, timestamp, and challenge branches that the previous critical-module coverage gate could execute only sparsely. The review also read implementation source from 30 pinned related GitHub projects across official MCP SDKs/servers, MCP frameworks, browser/local bridges, durable job systems, credential stores, and reconnect libraries; the strongest recurring invariants were compared against Machine Bridge and recorded in `docs/AUDIT.md`.
|
|
52
|
+
|
|
3
53
|
## 3.0.0-beta.146 - 2026-08-27
|
|
4
54
|
|
|
5
55
|
- Fix the hosted `start_job` initial-settlement result boundary exposed by the live beta.145 candidate. The shared settlement helper had explicitly copied process-carrier-only acceptance fields onto ordinary managed-job results; those properties were absent on `start_job` and therefore became JavaScript `undefined`. Machine Bridge's real tool-result normalization intentionally rejects `undefined` as non-JSON, so the job was durably accepted and could complete while the initiating hosted call returned a non-retryable `internal_error`. The helper now relies on the original accepted-object spread to preserve only fields that actually exist instead of manufacturing absent properties.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -48,7 +48,7 @@ Repository tests are verification inputs, not npm tarball entries under the curr
|
|
|
48
48
|
8. only after the candidate-bound canary and observed live verification both succeed, record exact candidate acceptance;
|
|
49
49
|
9. commit and push only with `npm run github:push`, then complete review and required checks;
|
|
50
50
|
10. create the GitHub Prerelease with `npm run prerelease:release` once exact-commit and release-integrity gates pass;
|
|
51
|
-
11. stop only for npm publication authorization; when explicitly authorized, run `npm run prerelease:publish -- --owner-confirm
|
|
51
|
+
11. stop only for npm publication authorization; when explicitly authorized, run `npm run prerelease:publish -- --owner-confirm`; if npm returns an interactive one-time-authentication challenge, rerun that same canonical command in a real owner TTY and complete the challenge while the process remains running, without passing OTPs/tokens/challenge URLs through automation; then continue automatically with `npm run prerelease:install -- --allow-worker-deploy`;
|
|
52
52
|
12. use the published prerelease for at least seven days for a major, three days for a minor, or one day for a patch;
|
|
53
53
|
13. every blocking defect increments the prerelease number and restarts the interval;
|
|
54
54
|
14. after explicit owner confirmation, record the soak result; stable promotion must pass `npm run release:soak:verify` and preserve the functional promotion digest;
|
package/README.md
CHANGED
|
@@ -79,8 +79,8 @@ macOS/Linux:
|
|
|
79
79
|
install_dir="$(mktemp -d)"
|
|
80
80
|
(
|
|
81
81
|
cd "$install_dir"
|
|
82
|
-
npx --yes npm@12.0.
|
|
83
|
-
npx --yes npm@12.0.
|
|
82
|
+
npx --yes npm@12.0.2 install --global npm@12.0.2
|
|
83
|
+
npx --yes npm@12.0.2 install --global --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
|
|
84
84
|
)
|
|
85
85
|
rm -rf "$install_dir"
|
|
86
86
|
npm --version
|
|
@@ -93,8 +93,8 @@ Windows Command Prompt:
|
|
|
93
93
|
set "MBM_INSTALL_DIR=%TEMP%\machine-bridge-mcp-install-%RANDOM%-%RANDOM%"
|
|
94
94
|
mkdir "%MBM_INSTALL_DIR%"
|
|
95
95
|
pushd "%MBM_INSTALL_DIR%"
|
|
96
|
-
npx --yes npm@12.0.
|
|
97
|
-
npx --yes npm@12.0.
|
|
96
|
+
npx --yes npm@12.0.2 install --global npm@12.0.2
|
|
97
|
+
npx --yes npm@12.0.2 install --global --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
|
|
98
98
|
popd
|
|
99
99
|
rmdir /s /q "%MBM_INSTALL_DIR%"
|
|
100
100
|
npm --version
|
|
@@ -172,7 +172,7 @@ The shared source of truth is `src/shared/policy-contract.json`. The generated m
|
|
|
172
172
|
|
|
173
173
|
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.
|
|
174
174
|
|
|
175
|
-
`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. Initialization-era 2025 compatibility retains `listChanged=false` because that protocol family uses different notification semantics. 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:
|
|
175
|
+
`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. Initialization-era 2025 compatibility retains `listChanged=false` because that protocol family uses different notification semantics. 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 deadline; a call that may have executed is never automatically replayed. 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.
|
|
176
176
|
|
|
177
177
|
`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).
|
|
178
178
|
|
|
@@ -193,7 +193,7 @@ For stateful GUI trajectories, owner/full callers can use the higher-level `comp
|
|
|
193
193
|
|
|
194
194
|
## Durable work and local resources
|
|
195
195
|
|
|
196
|
-
Remote request-owned foreground work uses the hosted reply-safe budgets described above; configurable browser/application calls may explicitly request at most 45 seconds, while remote `exec_command`, `run_process`, and `run_local_command` are durable one-step jobs with a 10-second acceptance envelope and an independent 1–600-second child execution budget after admission. A continuous process that legitimately needs more than 600 seconds must use `start_job`: managed-job main/finally steps default to 600 seconds and may explicitly request up to 21,600 seconds (six hours), with resource admission occurring before that execution timer begins. The Worker retains separate settlement ownership for five additional seconds, but neither that margin nor its internal stream metrics prove that an external MCP host consumed the terminal frame. Keep unrelated mutations and validation independently terminal, but batch one coherent non-interactive command sequence into a repository umbrella command or multi-step `start_job` instead of creating one host-visible one-step job per tiny probe. A timeout is a protocol result, not proof that descendant cleanup has already completed; a remote owner can inspect `diagnose_runtime.runtime.processes`, while local stdio exposes `server_info.runtime.processes`. Non-owner accounts receive authority-scoped readiness rather than machine-wide process activity. Remote process sessions are for interactive stdin or incremental output, not a substitute for ordinary durable work: hosted `read_process` reports `status_polling_mode=paced_followup`, caps the actual output/exit blocking wait at one second, and paces a repeated would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. When the current task needs more output or terminal state, the same session may be read again in the same assistant response without busy-looping. Non-interactive work should use durable `run_process`/`read_job`; multi-step, cleanup-sensitive, or daemon-restart-surviving workflows should use managed jobs, which persist ordered argv steps and `finally_steps` under owner-only local state and continue across an MCP disconnect. Durable acceptance does not force a hosted-turn handoff: active relay-origin `read_job` reports `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Its hosted default is a 40-second server-side long-poll, so an unchanged long job occupies one bounded live MCP response rather than forcing rapid host-side checkpoints. 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 available only when an immediate checkpoint is actually wanted, and
|
|
196
|
+
Remote request-owned foreground work uses the hosted reply-safe budgets described above; configurable browser/application calls may explicitly request at most 45 seconds, while remote `exec_command`, `run_process`, and `run_local_command` are durable one-step jobs with a 10-second acceptance envelope and an independent 1–600-second child execution budget after admission. A continuous process that legitimately needs more than 600 seconds must use `start_job`: managed-job main/finally steps default to 600 seconds and may explicitly request up to 21,600 seconds (six hours), with resource admission occurring before that execution timer begins. The Worker retains separate settlement ownership for five additional seconds, but neither that margin nor its internal stream metrics prove that an external MCP host consumed the terminal frame. Keep unrelated mutations and validation independently terminal, but batch one coherent non-interactive command sequence into a repository umbrella command or multi-step `start_job` instead of creating one host-visible one-step job per tiny probe. A timeout is a protocol result, not proof that descendant cleanup has already completed; a remote owner can inspect `diagnose_runtime.runtime.processes`, while local stdio exposes `server_info.runtime.processes`. Non-owner accounts receive authority-scoped readiness rather than machine-wide process activity. Remote process sessions are for interactive stdin or incremental output, not a substitute for ordinary durable work: hosted `read_process` reports `status_polling_mode=paced_followup`, caps the actual output/exit blocking wait at one second, and paces a repeated would-block read inside the fifteen-second cooldown within that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. When the current task needs more output or terminal state, the same session may be read again in the same assistant response without busy-looping. Non-interactive work should use durable `run_process`/`read_job`; multi-step, cleanup-sensitive, or daemon-restart-surviving workflows should use managed jobs, which persist ordered argv steps and `finally_steps` under owner-only local state and continue across an MCP disconnect. Durable acceptance does not force a hosted-turn handoff: active relay-origin `read_job` reports `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Its hosted default is a 40-second server-side long-poll, so an unchanged long job occupies one bounded live MCP response rather than forcing rapid host-side checkpoints. 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 available only when an immediate checkpoint is actually wanted, and public hosted `wait_ms` is capped at 60 seconds; longer work continues through another paced read of the same `job_id`. At the default, a synthetic 100-minute unchanged job has an anti-amplification ceiling of 150 status reads, while continuously changing nonterminal progress is separately bounded by the 30-second coalescing floor; those are density estimates rather than proof of aggregate same-response host lifetime. A known job may be followed through paced same-response `read_job` calls while those calls continue to be accepted and the task still needs the result; after an actual host/tool boundary, later recovery must continue from the same `job_id`. Completed one-step process carriers are lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first under the 512-state durable cap. A hosted helper whose current response still requires `read_job` continuation keeps stronger private recovery priority for the fixed thirty-minute grace because Machine Bridge explicitly told the caller to follow that durable handle. Once Machine Bridge produces a terminal hosted response for the helper—during initial settlement or a later `read_job`—the private marker drops to the bounded newest-16 transient delivery reserve because only outer response-delivery uncertainty remains. `list_jobs.jobs` keeps a 50-record durable-first primary window, while `recent_process_recovery` remains capped at 16 additional authority-visible public handles and prioritizes retained follow-up-required helpers when the primary window omits them. That secondary projection includes no step output or internal retention metadata and remains inventory rather than MCP replay/session state or a polling surface. Owner/local `capacity` diagnostics expose only coarse `durable_terminal` and `transient_terminal` counts; this improves recovery visibility without pretending that Worker acknowledgement proves an external host rendered the final assistant message. Long cross-job workflows can declare `depends_on`: the dependent job remains pre-execution `queued/dependency_wait` without spawning its main child until all upstream jobs succeed, and an upstream failure settles `dependency_failed` instead of leaving a file-poll loop waiting for an artifact that can never appear. Active/staged dependency plans pin referenced retained results until the dependency-bearing plan is terminal. A valid `job_id` that is no longer retained returns typed `not_found`; that absence is not proof that its underlying side effect never executed. `list_jobs` remains inventory rather than a substitute polling loop, and `server_info`/`diagnose_runtime` remain diagnostic surfaces rather than alternate wait channels. Elapsed minutes are not evidence that an external host deadline is near; return the durable recovery identifier for a later turn only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint.
|
|
197
197
|
|
|
198
198
|
On macOS, authorized remote activity uses a bounded idle-sleep assertion so ordinary system Idle Sleep does not suspend an active remote workflow. Relay handlers share the assertion for their execution lifetime plus a fixed thirty-minute rolling inactivity grace; each new authorized remote activity cancels a pending release and restarts the full grace after the last concurrent handler settles. An admitted remote process session extends daemon-side ownership until its child settles, and an account-backed managed-job runner owns a runner-bound assertion from confirmed claim through terminal persistence. Local managed jobs do not acquire the remote-continuity assertion. These protections do not override explicit sleep or lid-close behavior.
|
|
199
199
|
|
|
@@ -281,6 +281,8 @@ npm run prerelease:publish -- --owner-confirm
|
|
|
281
281
|
npm run prerelease:install -- --allow-worker-deploy
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
The full release gate starts with a real source-tree SBOM check. The GitHub and npm publication commands also rebuild ignored `node_modules` from the committed lockfile through the integrity-pinned hardened npm before their release verification, so publication does not rely on a stale ambient dependency tree.
|
|
285
|
+
|
|
284
286
|
Formal soak begins only after the exact published prerelease is installed and activated. Minimum soak is seven days for a major release, three days for a minor release, and one day for a patch. Every blocking fix creates a new prerelease and restarts the clock.
|
|
285
287
|
|
|
286
288
|
Stable promotion must retain the soaked package's functional digest. After the owner reports successful soak, the agent records the soak result and prepares and verifies the stable candidate. Final GitHub tag/Release publication uses `npm run release` automatically once its gates pass; npm stable publication is the sole separately authorized operation and uses `npm run stable:publish -- --owner-confirm`.
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"action": {
|
|
31
31
|
"default_title": "Machine Bridge Browser"
|
|
32
32
|
},
|
|
33
|
-
"version_name": "3.0.0-beta.
|
|
33
|
+
"version_name": "3.0.0-beta.154",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -45,7 +45,7 @@ A canonical workspace receives an independent profile, Worker name, secret set,
|
|
|
45
45
|
- `browser-request-registry.mjs`, `browser-broker-routes.mjs`, `browser-broker-server.mjs`, and `browser-bridge-http.mjs` separate direct request ownership, runtime-client proxy routing, authenticated loopback WebSocket upgrades/listening, and loopback HTTP handling from broker startup and extension handover;
|
|
46
46
|
- managed jobs, local resources, application automation, browser automation, and snapshot-bound Computer Use remain separate managers.
|
|
47
47
|
|
|
48
|
-
Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, capability-scoring, heartbeat-policy, or audit-storage logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, `hello_ack` authentication, end-to-end `relay_probe`/`ready_ack` readiness, reconnect backoff, outage logging, and a monotonically increasing in-memory transport generation. `RelayLiveness` owns the two-layer connection liveness policy: protocol Ping remains the first-stage half-open detector, but one missed dispatched Pong on a ready socket enters a bounded application-confirmation state rather than immediately terminating WSS; the independent periodic application heartbeat still refreshes Worker daemon activity and runs only after verified readiness. `RelayHeartbeatMonitor` remains the shared timer/recovery loop beneath that policy; `RelayHeartbeatStall` owns expected-versus-actual timer-lag diagnostics and wall-clock stall timestamps, `RelayProbeDeadline` owns the transport probe's dispatch-relative response deadline, and `RelayTransportConfirmation` owns the second-stage confirmation window so delayed local scheduling, sender backpressure, one transient persistent-flow stall, and a confirmed two-stage black hole remain distinguishable. The generation still protects the pre-ready probe and prevents arbitrary use of a stale socket. An explicit stop while the first connection is still waiting for readiness settles that pending start as cancelled/no-readiness rather than leaving its Promise unresolved; `LocalRuntime` also refuses to overwrite a concurrent `stopping`/`stopped` lifecycle with a late relay success or failure. Ordinary tool calls additionally bind to an ephemeral identifier generated once per local daemon process. If a ready socket drops, the Worker detaches its pending calls for at most the shared two-minute reconnect grace; only a replacement socket that presents the same daemon-process identifier and completes the full readiness probe can reclaim them. The local runtime keeps those calls alive and retains completed results while ownership evidence remains valid. A resumed call ID is reported as daemon-proven missing only while `RelayResultRetention` can still prove that absence means the call did not execute locally. If acknowledgement retention expires, emergency retention is consumed, or reconnect-grace cleanup discards a completed result, that proof becomes permanently false for the current recovery owner; subsequent reconciliation suppresses transparent missing-ID replay rather than risk duplicate side effects. `diagnose_runtime` exposes only aggregate recovery counts and this Boolean safety state. A different process instance, an explicit cancellation, or grace expiry cannot receive queued results. Stdio mode invokes `LocalRuntime` directly without that adapter.
|
|
48
|
+
Architecture tests cap the orchestration module and each extracted service independently and reject a return of low-level process, patch, diagnostic, capability-scoring, heartbeat-policy, or audit-storage logic to `LocalRuntime`. `RelayConnection` owns remote WebSocket transport, `hello_ack` authentication, end-to-end `relay_probe`/`ready_ack` readiness, reconnect backoff, outage logging, and a monotonically increasing in-memory transport generation. Verified readiness restores business traffic immediately, but reconnect failure history is cleared only after five seconds of generation-stable ready uptime; a shorter ready/close flap keeps its prior exponential backoff position instead of returning to the shortest delay. `RelayLiveness` owns the two-layer connection liveness policy: protocol Ping remains the first-stage half-open detector, but one missed dispatched Pong on a ready socket enters a bounded application-confirmation state rather than immediately terminating WSS; the independent periodic application heartbeat still refreshes Worker daemon activity and runs only after verified readiness. `RelayHeartbeatMonitor` remains the shared timer/recovery loop beneath that policy; `RelayHeartbeatStall` owns expected-versus-actual timer-lag diagnostics and wall-clock stall timestamps, `RelayProbeDeadline` owns the transport probe's dispatch-relative response deadline, and `RelayTransportConfirmation` owns the second-stage confirmation window so delayed local scheduling, sender backpressure, one transient persistent-flow stall, and a confirmed two-stage black hole remain distinguishable. The generation still protects the pre-ready probe and prevents arbitrary use of a stale socket. An explicit stop while the first connection is still waiting for readiness settles that pending start as cancelled/no-readiness rather than leaving its Promise unresolved; `LocalRuntime` also refuses to overwrite a concurrent `stopping`/`stopped` lifecycle with a late relay success or failure. Ordinary tool calls additionally bind to an ephemeral identifier generated once per local daemon process. If a ready socket drops, the Worker detaches its pending calls for at most the shared two-minute reconnect grace; only a replacement socket that presents the same daemon-process identifier and completes the full readiness probe can reclaim them. The local runtime keeps those calls alive and retains completed results while ownership evidence remains valid. A resumed call ID is reported as daemon-proven missing only while `RelayResultRetention` can still prove that absence means the call did not execute locally. If acknowledgement retention expires, emergency retention is consumed, or reconnect-grace cleanup discards a completed result, that proof becomes permanently false for the current recovery owner; subsequent reconciliation suppresses transparent missing-ID replay rather than risk duplicate side effects. `diagnose_runtime` exposes only aggregate recovery counts and this Boolean safety state. A different process instance, an explicit cancellation, or grace expiry cannot receive queued results. Stdio mode invokes `LocalRuntime` directly without that adapter.
|
|
49
49
|
|
|
50
50
|
The control plane has explicit availability budgets at both admission layers. The Worker admits thirty ordinary pending daemon calls and reserves two additional slots for `diagnose_runtime`/`list_roots`; one serialized admission gate and the transient pending-call registry enforce that per-tool capacity. There is no second durable MCP call pool to merge into the accounting. Pending operation/reconnect delays are positive finite safe integers bounded by the shared relay contract before any timer or Durable Object alarm deadline is armed; malformed internal timing cannot become an infinite timer. The local relay independently rejects malformed call IDs, tool-name shapes, authorization field types, and timeout values before dispatch instead of coercing them into a usable envelope. The local runtime independently admits fourteen ordinary tools and reserves two control slots; the total ceilings remain thirty-two and sixteen. A timed-out or cancelled process call settles at the protocol boundary before operating-system cleanup necessarily completes, so `process-tracker.mjs` keeps that process under a draining call until `close` and reports pending escalation supervision. Neither process ownership inspection nor security-audit persistence performs synchronous process creation or disk `fsync` on the daemon event loop.
|
|
51
51
|
|
|
@@ -87,7 +87,7 @@ See [Local application and browser automation](LOCAL_AUTOMATION.md).
|
|
|
87
87
|
|
|
88
88
|
### Managed job runner
|
|
89
89
|
|
|
90
|
-
`ManagedJobManager` persists bounded per-workspace job envelopes below the owner-only profile directory. Managed-job active and terminal lifecycle classifications have one shared source in `managed-job-terminal.mjs`; manager reconciliation, retention, detached-runner fatal settlement, and production full-access diagnostics consume that source instead of maintaining parallel status lists. `start_job` validates the complete plan, snapshots referenced resource metadata/hashes, writes an owner-only plan/status, and launches `job-runner.mjs` as a detached process with runner-level logs redirected to owner-only files. `stage_job` performs the same acceptance validation but writes a non-running, review-only `staged` envelope; staged records have no promotion/approval execution path, so execution requires a separate trusted `start_job` request or an explicit local `job submit` plan. `managed-job-dependency-admission.mjs` binds optional `depends_on` references to same-authority durable job identities and rejects staged or already-failed dependencies before acceptance. `managed-job-dependencies.mjs` then keeps a dependent runner in pre-execution `queued/dependency_wait` state until all upstream jobs succeed, or settles it with `dependency_failed` when one later fails; no dependent main child or process resource lease exists during that wait. Because Windows atomic replacement/runner-exit recovery can make an otherwise valid status file transiently unreadable, dependency polling gives only `permission_denied`, `identity_changed`, and generic `resource_unavailable` reads a fixed 45-second monotonic recovery grace. One successful secure read clears that grace; persistent unavailability still fails closed as `dependency_unavailable`, while missing, corrupt, witness-mismatched, staged, or otherwise invalid evidence is never converted into a retry. `managed-job-relaunch.mjs` preserves the pre-execution distinction across a dead dependency-wait runner by restarting the original `dependency_wait` job rather than converting it to cleanup-only recovery. `managed-job-retention.mjs` owns staged expiry timing and seven-day terminal retention; `managed-job-capacity.mjs` bounds durable retained state at 512 while the public `list_jobs.jobs` primary response window remains capped at 50 records.
|
|
90
|
+
`ManagedJobManager` persists bounded per-workspace job envelopes below the owner-only profile directory. Managed-job active and terminal lifecycle classifications have one shared source in `managed-job-terminal.mjs`; manager reconciliation, retention, detached-runner fatal settlement, and production full-access diagnostics consume that source instead of maintaining parallel status lists. `start_job` validates the complete plan, snapshots referenced resource metadata/hashes, writes an owner-only plan/status, and launches `job-runner.mjs` as a detached process with runner-level logs redirected to owner-only files. `stage_job` performs the same acceptance validation but writes a non-running, review-only `staged` envelope; staged records have no promotion/approval execution path, so execution requires a separate trusted `start_job` request or an explicit local `job submit` plan. `managed-job-dependency-admission.mjs` binds optional `depends_on` references to same-authority durable job identities and rejects staged or already-failed dependencies before acceptance. `managed-job-dependencies.mjs` then keeps a dependent runner in pre-execution `queued/dependency_wait` state until all upstream jobs succeed, or settles it with `dependency_failed` when one later fails; no dependent main child or process resource lease exists during that wait. Because Windows atomic replacement/runner-exit recovery can make an otherwise valid status file transiently unreadable, dependency polling gives only `permission_denied`, `identity_changed`, and generic `resource_unavailable` reads a fixed 45-second monotonic recovery grace. One successful secure read clears that grace; persistent unavailability still fails closed as `dependency_unavailable`, while missing, corrupt, witness-mismatched, staged, or otherwise invalid evidence is never converted into a retry. `managed-job-relaunch.mjs` preserves the pre-execution distinction across a dead dependency-wait runner by restarting the original `dependency_wait` job rather than converting it to cleanup-only recovery. `managed-job-retention.mjs` owns staged expiry timing and seven-day terminal retention; `managed-job-capacity.mjs` bounds durable retained state at 512 while the public `list_jobs.jobs` primary response window remains capped at 50 records. Completed one-step process carriers are marked internally as `transient_process`, and a separate private `transient_recovery_pending` bit records whether the initiating hosted response actually told the caller that `read_job` follow-up is required. If such a helper later becomes terminal, retention preserves it with stronger priority for the fixed thirty-minute transient grace. When any hosted read path produces the terminal result—during the initial two-second settlement or a later `read_job`—`managed-job-transient-recovery.mjs` clears that private bit under the job transition lock and the result drops to the bounded newest-16 transient delivery reserve. The downgrade is best effort: failure to clear only keeps stronger recovery protection and cannot weaken continuity. `recent_process_recovery` remains a separate capped-at-16 authority-visible projection without step output or internal retention metadata; it prioritizes retained follow-up-required helpers before ordinary recent transient history when the durable-first primary window omits them. This recovery-discovery projection does not create MCP replay/session state or change request ownership. The two-tier policy does not enlarge the 512-state cap: protected follow-up-required transient results may displace older ordinary durable terminal history, ordinary terminal-delivered helpers remain bounded by the newest-16 reserve, and active/staged/unreadable/dependency-pinned state remains non-evictable. `managed-job-dependency-retention.mjs` pins terminal records referenced by active/staged dependency plans and fails closed if that protection cannot be read safely. `managed-job-directory.mjs` maps a syntactically valid but no-longer-retained job ID to fixed non-retryable `not_found`; that absence is recovery-evidence loss rather than proof that the underlying operation never executed. `managed-job-terminal-maintenance.mjs` owns post-settlement evidence validation and artifact scrubbing; `managed-job-directory-generation.mjs` binds whole-directory retirement to the exact filesystem generation that retention inspected. Retirement first revalidates the full observed generation, atomically renames that directory to an internal `retired_job_*` name carrying its device/inode identity, revalidates the moved object, and only then recursively deletes it. The retirement namespace deliberately does not match the public `MANAGED_JOB_ID` grammar, so list/read/lock scans cannot reinterpret internal cleanup state as an ordinary job. A crash after rename therefore leaves recognizable state rather than an anonymous orphan: a later maintenance pass reclaims it only when the encoded generation still matches, while a type mismatch, generation mismatch, or unreadable retired entry is projected into active-state inventory as a privacy-bounded `retired_managed_job`/`unreadable` blocker without exposing the internal filename or filesystem identity. Before destructive terminal cleanup, status/result must describe the same directory job ID, terminal state, and `finished_at` generation; the degraded `result_persisted=false` form must carry an explicit terminal-record error class. Corrupt terminal evidence is therefore retained as unreadable state and also blocks state removal instead of authorizing plan/runtime scrubbing or capacity eviction. Expiry is a real per-job state transition: it acquires `transition.lock`, re-reads the staged state, and commits through the same result-first terminal persistence path as cancellation/runner settlement. Seven-day retention is measured from terminal `finished_at`, not an older directory mtime. Admission may evict only safely removable terminal records and never active, staged, unreadable, generation-replaced, or unreclaimed retired state merely to make room; every recognized retired entry still counts toward the same hard retained-state capacity until safely removed. Cross-process create transactions serialize through an owner-identity-checked root `capacity.lock` across prune/recheck and status publication, and state inventory treats a live capacity lock as an uninstall blocker.
|
|
91
91
|
|
|
92
92
|
The runner:
|
|
93
93
|
|
|
@@ -294,7 +294,7 @@ Worker-name mutation is a separate identity transition. Existing state rejects a
|
|
|
294
294
|
|
|
295
295
|
The local `RelayConnection` treats proxy selection, transport construction, WebSocket open, authentication, end-to-end readiness, and outage recovery as separate states. The shared proxy module maps WebSocket targets to standard HTTP(S) environment-proxy resolution, honors `NO_PROXY`, rejects non-HTTP(S) proxy schemes, and creates the proxy agent without exposing its URL or credentials. Invalid proxy configuration is a fatal configuration error rather than a retryable outage.
|
|
296
296
|
|
|
297
|
-
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts liveness monitoring, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once authenticated, `RelayLiveness` sends a protocol-level WebSocket Ping every five seconds. The full ten-second Pong deadline begins only after the sender write callback proves the control frame left the local queue. Ping/application-send completions are fenced to the exact current WebSocket generation, so a callback from a superseded socket cannot mutate the replacement connection; a Ping callback that never completes has its own thirty-second local dispatch bound and becomes `relay_transport_send_timeout` even when the receive direction remains active. Reaching that first-stage deadline no longer immediately kills a ready WSS: `relay-transport-confirmation.mjs` opens one bounded fifteen-second application-confirmation window and emits the existing JSON heartbeat only after the connection is fully ready. A later protocol Pong or the explicit JSON application `pong` is bidirectional transport proof and clears suspicion; unrelated application messages update receive-side liveness only and cannot prove that daemon-to-Worker writes are succeeding. `ResilientRelayConnection` concurrently prewarms signed HTTPS in standby without Worker-side takeover; confirmed WSS recovery stops that prewarm, while a real disconnect upgrades it to exact-generation takeover and preempts any stale standby request. A scheduling-responsive true black hole therefore remains bounded to one five-second probe interval plus ten-second Pong response and fifteen-second independent confirmation, while a single ten-to-fifteen-second persistent-flow stall no longer becomes an avoidable reconnect storm. A detected local event-loop stall cancels remote suspicion and follows the separate recovery-grace branch rather than being counted as network failure. Transport Ping remains active during authenticated probing, but application heartbeat/confirmation is gated on verified readiness because the Worker probing state accepts only the readiness-probe result. Same-instance reconnect also performs explicit call-ownership reconciliation: the Worker sends its still-waiting IDs; the daemon snapshots the union of active calls and unacknowledged results, completes replacement-channel readiness, then returns `resume_calls_ack.missing_ids` only for IDs absent from that ownership union. Those IDs alone may receive one same-ID transport redelivery inside the original remaining deadline; if that cannot be done safely they settle retryably with `side_effects_started=false`. Active calls and retained terminal results continue on existing ownership, and no possibly executed tool call is automatically replayed. A separate JSON application heartbeat remains at twenty-five seconds and retains a seventy-five-second application-silence timeout, so protocol-level Pong cannot mask a Worker application path that has stopped responding. On the Worker, authenticated application-heartbeat activity refresh is synchronous and its JSON `pong` is queued before Durable Object alarm inspection or mutation; heartbeat and terminal-result paths then own one explicit coalesced alarm schedule. Persistent deadline work therefore cannot sit in front of application-liveness acknowledgement or be scheduled twice through a hidden touch helper. The Worker's ninety-second daemon-liveness deadline remains an independent wider fallback across Durable Object hibernation. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. Local protocol-watchdog expiry is classified separately as `relay_transport_timeout`; local application-silence expiry retains `relay_heartbeat_timeout`. The same Worker classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback. Each new authenticated hello also carries a schema-versioned, bounded relay diagnostic summary, including `previous_ready_inbound_silence_ms` so the silent half-open interval before close is not collapsed into the shorter close-to-ready outage duration. The local connection additionally keeps `recent_outages`, a newest-first in-memory ring capped at eight completed reconnect episodes. The ring contains only bounded outage numbers/timestamps
|
|
297
|
+
A connection-attempt deadline terminates sockets stuck in `CONNECTING`. After open, the daemon sends `hello`; `hello_ack` establishes an authenticated relay generation and starts liveness monitoring, but does not resolve startup or advertise readiness. The Worker then sends a random `relay_probe`. Its result must traverse the local runtime dispatcher and `sendForSession` on that generation before `ready_ack` marks the connection usable. The daemon rejects ordinary tool calls before that state and rejects a premature readiness acknowledgement without locally recorded probe delivery. Independent handshake and readiness deadlines terminate candidates that authenticate but cannot return results. Once authenticated, `RelayLiveness` sends a protocol-level WebSocket Ping every five seconds. The full ten-second Pong deadline begins only after the sender write callback proves the control frame left the local queue. Ping/application-send completions are fenced to the exact current WebSocket generation, so a callback from a superseded socket cannot mutate the replacement connection; a Ping callback that never completes has its own thirty-second local dispatch bound and becomes `relay_transport_send_timeout` even when the receive direction remains active. Reaching that first-stage deadline no longer immediately kills a ready WSS: `relay-transport-confirmation.mjs` opens one bounded fifteen-second application-confirmation window and emits the existing JSON heartbeat only after the connection is fully ready. A later protocol Pong or the explicit JSON application `pong` is bidirectional transport proof and clears suspicion; unrelated application messages update receive-side liveness only and cannot prove that daemon-to-Worker writes are succeeding. `ResilientRelayConnection` concurrently prewarms signed HTTPS in standby without Worker-side takeover; confirmed WSS recovery stops that prewarm, while a real disconnect upgrades it to exact-generation takeover and preempts any stale standby request. A scheduling-responsive true black hole therefore remains bounded to one five-second probe interval plus ten-second Pong response and fifteen-second independent confirmation, while a single ten-to-fifteen-second persistent-flow stall no longer becomes an avoidable reconnect storm. A detected local event-loop stall cancels remote suspicion and follows the separate recovery-grace branch rather than being counted as network failure. Transport Ping remains active during authenticated probing, but application heartbeat/confirmation is gated on verified readiness because the Worker probing state accepts only the readiness-probe result. Same-instance reconnect also performs explicit call-ownership reconciliation: the Worker sends its still-waiting IDs; the daemon snapshots the union of active calls and unacknowledged results, completes replacement-channel readiness, then returns `resume_calls_ack.missing_ids` only for IDs absent from that ownership union. Those IDs alone may receive one same-ID transport redelivery inside the original remaining deadline; if that cannot be done safely they settle retryably with `side_effects_started=false`. Active calls and retained terminal results continue on existing ownership, and no possibly executed tool call is automatically replayed. A separate JSON application heartbeat remains at twenty-five seconds and retains a seventy-five-second application-silence timeout, so protocol-level Pong cannot mask a Worker application path that has stopped responding. On the Worker, authenticated application-heartbeat activity refresh is synchronous and its JSON `pong` is queued before Durable Object alarm inspection or mutation; heartbeat and terminal-result paths then own one explicit coalesced alarm schedule. Persistent deadline work therefore cannot sit in front of application-liveness acknowledgement or be scheduled twice through a hidden touch helper. The Worker's ninety-second daemon-liveness deadline remains an independent wider fallback across Durable Object hibernation. Worker error codes `daemon_transport_error` and `daemon_liveness_timeout` are connection-recovery signals, not protocol incompatibility: they terminate only the current socket, run normal disconnect cleanup, and enter bounded reconnect. Local protocol-watchdog expiry is classified separately as `relay_transport_timeout`; local application-silence expiry retains `relay_heartbeat_timeout`. The same Worker classification is applied to the close frame if the preceding error frame is lost. Failure to send `hello`, or failure to deliver a readiness-probe result because its socket/session ended, follows the same transport-recovery path rather than being promoted to authentication or protocol failure. Unknown Worker errors, authentication rejection, malformed readiness sequencing, and identity/version mismatch remain fatal. Outage reminders run on their own exponential-backoff timer rather than depending on another transport callback. Each new authenticated hello also carries a schema-versioned, bounded relay diagnostic summary, including `previous_ready_inbound_silence_ms` so the silent half-open interval before close is not collapsed into the shorter close-to-ready outage duration. The local connection additionally keeps `recent_outages`, a newest-first in-memory ring capped at eight completed reconnect episodes. The ring contains only bounded outage numbers, first/final disconnect and ready timestamps, durations, close/error classes, prior-ready duration/silence, the first disconnect's fixed protocol-Ping/application-confirmation phase and millisecond ages, coarse application-route class, and connection-stage timings; a protocol/application Pong that clears transport suspicion without rebuilding WSS is not a completed outage and stays only in heartbeat diagnostics. Because the hello is sent before the recovering WebSocket can receive its final `ready_ack`, its carried ring cannot yet contain that current episode. The Worker therefore sanitizes the prior ring into the probing attachment and, only when promotion proves end-to-end readiness, synthesizes the now-completed current episode from the already bounded scalar fields before marking `outage_active=false`. Authenticated `server_info.daemon.relay_transport` can consequently retain several sub-warning-threshold reconnects instead of losing all but the latest one, without logging them by default, persisting a tool transcript, exposing endpoints/interfaces/DNS data, or treating near-miss liveness suspicions as outages.
|
|
298
298
|
|
|
299
299
|
Reconnect uses bounded exponential backoff with jitter. Brief self-healing interruptions are debug-only. An unresolved outage is promoted to a rate-limited warning after a grace period, and recovery produces one summary. Raw close codes and reason strings remain debug-only.
|
|
300
300
|
|
|
@@ -335,7 +335,7 @@ Cloudflare sampling is size control rather than an audit log. The project intent
|
|
|
335
335
|
|
|
336
336
|
## Deployment toolchain isolation
|
|
337
337
|
|
|
338
|
-
Wrangler and Miniflare are not part of the published runtime dependency graph. Source development retains an exact Wrangler dev dependency, while installed runtimes create a package-owned private toolchain under the owner-only state root from `src/local/wrangler-toolchain/package.json` and its committed lockfile. The directory name binds the Wrangler version and template digest. A process-identity owner lock serializes installation and audit refresh. Before `npm ci`, `hardened-npm.mjs` downloads exact npm 12.0.
|
|
338
|
+
Wrangler and Miniflare are not part of the published runtime dependency graph. Source development retains an exact Wrangler dev dependency, while installed runtimes create a package-owned private toolchain under the owner-only state root from `src/local/wrangler-toolchain/package.json` and its committed lockfile. The directory name binds the Wrangler version and template digest. A process-identity owner lock serializes installation and audit refresh. Before `npm ci`, `hardened-npm.mjs` downloads exact npm 12.0.2, undici 6.28.0, and brace-expansion 5.0.9 registry tarballs with redirect rejection, byte ceilings, and fixed SHA-512 SRI. Each immutable GET uses a referenced 60-second **no-progress** deadline that is refreshed only by response/body progress plus an independent five-minute per-attempt absolute ceiling, and makes at most three attempts with bounded backoff for explicit transient network codes, HTTP 429, and HTTP 5xx. A continuously progressing slow transfer may therefore exceed sixty seconds without restarting from byte zero, while a stalled or indefinitely dribbling peer remains bounded. Redirects, proxy-configuration errors, certificate/policy failures, size violations, and other non-transient failures are not retried. It replaces the stock npm bundle's vulnerable undici/brace-expansion directories and verifies all three package identities plus the npm-reported version. CI and runtime deployment use this same non-global CLI. The runtime then verifies exact toolchain template bytes, dependency-tree validity, Wrangler/undici/sharp versions, a zero-vulnerability production audit, and registry signatures before invoking Wrangler. The audit marker is bounded, contains no path or credential, and expires after 24 hours. Failure leaves the Worker untouched and a later invocation reconstructs the private directory from the same immutable package bytes. Reconstruction occurs only for positively identified integrity corruption; permission, I/O, quota, memory, retry, stale-handle, descriptor, read-only, and timeout failures preserve the existing tree and fail closed. Primary and cleanup errors are aggregated rather than allowing cleanup to replace the causal failure. Candidate installation, registry installation, and npm publication use an ephemeral instance of the same hardened npm rather than the ambient npm bundle. Candidate and registry-package activation use one session-settlement helper: each caller clears its live reference, then preserves the primary failure and aggregates temporary cleanup failure instead of maintaining a divergent cleanup state machine.
|
|
339
339
|
|
|
340
340
|
## Release integrity
|
|
341
341
|
|
|
@@ -353,9 +353,9 @@ Third-party workflow actions are pinned to immutable commit SHAs. Dependabot gro
|
|
|
353
353
|
|
|
354
354
|
The relay connection owns transport lifecycle, while liveness policy is decomposed instead of accumulating in that class: `relay-liveness.mjs` composes transport and application monitors; `relay-liveness-actions.mjs` owns suspect/recovered/hard-failure effects; `relay-heartbeat.mjs` owns timer and event-loop-stall scheduling; `relay-heartbeat-transport-state.mjs` advances transport probe/confirmation phases; `relay-heartbeat-options.mjs` normalizes bounded timing; `relay-probe-dispatch.mjs` owns sender-dispatch identity/race state and `relay-probe-dispatch-metrics.mjs` its bounded diagnostics; `relay-probe-deadline.mjs` owns the post-dispatch Pong deadline; `relay-transport-confirmation.mjs` owns the second-stage application-confirmation state; `relay-transport-probe.mjs` owns socket-generation fencing plus bounded sender backlog while `relay-transport-probe-send.mjs` owns low-level Ping/fallback dispatch. `relay-transport-error-state.mjs`, `relay-heartbeat-stall.mjs`, `relay-connect-timing.mjs`, `relay-connection-classification.mjs`, and `relay-diagnostics.mjs` retain their error, stall, connect-timing, classification, and public-projection responsibilities. Application proxy selection is intentionally separated from the operating-system network path. Reconnect backoff is bounded at fifteen seconds, but each WebSocket connect attempt now has a thirty-second per-attempt connect budget because live successful upgrades were observed within roughly fourteen seconds of the former fifteen-second cutoff. Transport liveness requests a protocol probe after five seconds. `ws.ping()` queueing is not treated as dispatch: the local sender has a separate thirty-second no-progress bound, and only its write callback starts the full ten-second Pong response deadline. If fresh inbound traffic arrives while that callback is delayed, the logical dispatch retires at its deadline rather than letting a stale callback arm a future timeout. If a dispatched Ping reaches its ten-second deadline, a ready connection receives a separate fifteen-second application confirmation before `relay_transport_timeout` is allowed to terminate it; HTTPS is prewarmed during that suspect interval and becomes exact-generation takeover only after actual WSS loss. The twenty-five-second periodic application heartbeat / seventy-five-second application watchdog and ninety-second Worker daemon-liveness fallback remain separate checks with separate purposes. Worker-issued `connection_id` is part of the same-version welcome contract and binds an HTTPS fallback takeover to the exact disconnected WebSocket generation, so a delayed takeover request cannot retire a newer same-instance WebSocket that has already recovered. The root-signed daemon session remains intentionally bounded to 24 hours; expiry is a terminal runtime condition rather than a transport retry. Runtime cleanup precedes nonzero exit, after which installed service supervision obtains a new session on daemon restart instead of extending the certificate lifetime or looping forever with expired WSS/HTTPS credentials.
|
|
355
355
|
|
|
356
|
-
Exclusive lock publication is itself a two-name hard-link commit: after the complete staging inode is linked at the final lock path and before the private staging alias is removed, `nlink` is transiently two. Process/startup locks, owner-state locks, managed-job transition/recovery locks, browser pairing creation, and managed-job runner claims are the only current consumers allowed to bridge that internal window with the shared fixed multiple-link retry.
|
|
356
|
+
Exclusive lock publication is itself a two-name hard-link commit: after the complete staging inode is linked at the final lock path and before the private staging alias is removed, `nlink` is transiently two. Process/startup locks, owner-state locks, managed-job transition/recovery locks, browser pairing creation, and managed-job runner claims are the only current consumers allowed to bridge that internal window with the shared fixed multiple-link retry. If that window survives a publisher crash, `exclusive-publication-recovery.mjs` does not delete the alias: it opens the canonical target first, opens only candidates in the reserved internal staging grammar, requires exactly one same-generation/same-inode two-link alias, rechecks the held target descriptor, and returns the exact filesystem identity as a bounded read token. Default readers still reject multiple links. A stale owner may remove only the canonical target after the usual owner/token/generation proof, potentially leaving the owner-only internal alias until the containing state/job generation is retired. Runner claims use the same no-follow path-identity boundary and never gain general hard-link acceptance.
|
|
357
357
|
|
|
358
|
-
Owner-state mutations use shared complete-before-visible lock primitives with separate lock files per concern. Process/startup, owner-state, managed-job transition/recovery, and shared owned-JSON removal derive ownership bytes and lossless filesystem generation identity from the same opened descriptor; ownership files reject multiple hard links before stale/release decisions, and known change-time generation must still match at
|
|
358
|
+
Owner-state mutations use shared complete-before-visible lock primitives with separate lock files per concern. Process/startup, owner-state, managed-job transition/recovery, and shared owned-JSON removal derive ownership bytes and lossless filesystem generation identity from the same opened descriptor; ownership files reject multiple hard links before stale/release decisions except for the exact descriptor-verified internal residue token above, and known change-time generation must still match at canonical-target removal. The internal staging alias itself is never destructively removed by the recovery reader. Security-audit writes, obsolete authorization-state migration cleanup, daemon/startup ownership, and managed-job transition/recovery are visible to state inventory so uninstall cannot race an active mutation. Workspace trust-state recovery requires a complete canonical envelope before its recovery marker is removed.
|
|
359
359
|
|
|
360
360
|
Static package identity is separate from persistence ownership. `package-identity.mjs` is the single local source for package root/name/version and shared application name; CLI, stdio, Worker-deployment helpers, browser identity, shell integration, and trust checks do not reparse the root package manifest independently. State-root/profile/workspace path and hash semantics remain deliberately in `state.mjs`, where platform/environment injection, canonical workspace identity, case-folding, schemas, locks, recovery, and removal share one contract. The unused divergent `state-locations.mjs` experiment was removed; a future extraction is valid only when real consumers migrate and parity tests prove identical path semantics.
|
|
361
361
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## 2026-08-29 beta.154 exclusive-publication CodeQL and TOCTOU review
|
|
4
|
+
|
|
5
|
+
**Beta.153 is blocked by hosted security analysis despite complete local/live acceptance.** Exact-head PR #114 reported `js/file-system-race` at `src/local/exclusive-publication-recovery.mjs`. The beta.153 recovery path opened and revalidated the committed target and its reserved same-inode staging alias, but then performed a pathname `unlink` of that alias. The finding is substantive: another same-user filesystem mutation can change a path entry between the last identity check and a destructive pathname syscall, and Node does not expose a portable descriptor-relative unlink primitive that would make this particular cleanup proof atomic. The repository therefore does not suppress the alert, add a SARIF exception, or treat repeated pre-unlink stats as a security repair.
|
|
6
|
+
|
|
7
|
+
**Beta.154 changes the recovery capability from deletion authority to a read-only filesystem-generation token.** `verifyExclusiveFilePublicationResidueSync()` opens the target before classification, requires an ordinary regular file with exactly two links, enumerates only the private exclusive-publisher filename grammar, opens each candidate alias with the same no-follow/path-identity boundary, requires exactly one alias whose descriptor has the same full device/inode/change-time generation and two-link state, and rechecks the held target descriptor before returning that identity. No alias pathname is deleted or renamed. `secure-file.mjs` accepts a two-link read only when its freshly opened descriptor exactly matches that explicit token; the default remains hard-link rejection. The token is passed only by the known exclusive-publication readers. Unknown names, arbitrary external hard links, extra aliases, symbolic links, generation changes, and ordinary readers remain fail-closed.
|
|
8
|
+
|
|
9
|
+
**Destructive stale-owner handling remains canonical-target-only and identity-bound.** Owner-state and resource-transaction reclamation may unlink the canonical target after its existing owner/token checks, repeated bounded snapshot, and exact filesystem-generation comparison; a two-link target additionally requires the verified residue identity. The internal alias is deliberately not deleted by pathname. Consequently a crash precisely after hard-link publication but before the successful publisher removes its private alias may leave an owner-only orphan after canonical stale-owner reclamation. Job/state generation retirement ultimately removes the containing directory. For long-lived state such as browser pairing, that extremely narrow crash can retain an older owner-private token inode locally until state retirement; the token is never exposed through MCP results, diagnostic logs, or public inventory. This residual local-at-rest footprint is accepted in preference to a destructive TOCTOU cleanup primitive and remains covered by owner-only directory/file permissions.
|
|
10
|
+
|
|
11
|
+
**Regression and dependency evidence.** Process-lock tests reproduce both a live-owner and proven-stale post-link crash residue: the live owner is read without deleting its alias, stale canonical ownership is reclaimed while the old alias survives, and a separately created hard link outside the reserved namespace is rejected. Browser-pairing, secure-file, resource-admission, architecture, lint, and type checking pass on the new model. The same review closes the newly available direct dependency update: Wrangler 4.127.1 is paired with workerd 1.20260828.1 in both root and private control-plane lockfiles/allowlists; `npm outdated` is empty and npm audit reports zero vulnerabilities after toolchain, consumer-security, package, and Worker-type checks.
|
|
12
|
+
|
|
13
|
+
**Release consequence.** The security repair and dependency update change packaged bytes after beta.153 acceptance, so that acceptance is removed. Package/runtime identity advances to beta.154 while hosted tool schema generation remains 20 because the MCP/diagnostic contract is unchanged. Fresh full verification, candidate/install-only proof, guarded activation, candidate-bound live evidence, acceptance, and a new exact-head CodeQL/provider run are mandatory; only that hosted rerun can close the original provider finding. npm publication remains separately owner-authorized for the exact beta.154 version.
|
|
14
|
+
|
|
15
|
+
## 2026-08-29 beta.153 relay outage timeline review
|
|
16
|
+
|
|
17
|
+
**The beta.152 candidate exposed a diagnostic consistency defect before acceptance.** Completed relay history already retained one `duration_ms` from the first outage transition through final verified readiness, but `recent_outages[].disconnected_at` was serialized from `lastDisconnectedAt`. That scalar is updated again by later failed reconnect attempts, so a multi-attempt episode could pair the timestamp of the final failed attempt with a duration measured from the first failure. Any consumer that reconstructed an interval from `disconnected_at` and `ready_at`, including system-sleep correlation, could therefore analyze a materially shorter and causally different interval than the one represented by `duration_ms`. The defect affects attribution only; it is not evidence that any particular outage was caused by sleep, network, Worker, or daemon failure.
|
|
18
|
+
|
|
19
|
+
**Beta.153 makes the completed-outage timeline internally consistent and preserves the first-failure liveness phase.** The first transition into an outage fixes `outageStartedWallAt`; completed history now uses that value for `disconnected_at`, retains the later final-attempt timestamp separately as bounded `last_disconnect_at`, and uses the completed history entry's own `ready_at` for recovered sleep correlation. Active correlation prefers `outage_started_at` rather than the most recent disconnect. The local relay also freezes only bounded booleans/millisecond ages from the first disconnect's liveness snapshot: protocol-Ping dispatch pending/age, protocol-Ping outstanding/age, transport-confirmation pending, and application-inbound silence. Those fields are carried through the bounded eight-entry peer diagnostic projection. They contain no endpoint, address, interface, DNS answer, proxy value, close reason, account/client identity, daemon/connection identity, call/tool identity, argument, or result.
|
|
20
|
+
|
|
21
|
+
**Release consequence.** Beta.152 completed frozen verification and candidate packaging, but its manifest remained `pending` and there is no tracked beta.152 acceptance record. The new runtime and diagnostic bytes invalidate that frozen candidate. Package/runtime identity therefore advances to beta.153 and hosted tool schema generation advances from 19 to 20: generation 19 represents the beta.152 `read_job` public-schema change, while generation 20 also identifies the new owner-visible recovered-outage fields and their changed timeline interpretation. Fresh verification, candidate/install-only proof, guarded activation, candidate-bound canaries, live diagnostic confirmation, acceptance, and exact-head provider gates are required. npm publication remains the sole separate current-task owner authorization boundary.
|
|
22
|
+
|
|
23
|
+
## 2026-08-29 beta.152 hosted read request-lifetime review
|
|
24
|
+
|
|
25
|
+
**Beta.151 fixed continuity attribution and then used that evidence to falsify the WSS hypothesis for the next interruption.** During the beta.151 PR-check wait, one explicit 180-second `read_job` returned a normal structured running checkpoint, but a later explicit 180-second read failed at the hosted MCP boundary with `mcp_network_error: Connection failed`. Immediately after that failure, generation-18 `server_info` still showed the same ready daemon WebSocket, `outage_count=0`, `last_disconnected_at=null`, and continuity schema 2 retained only the three planned clean activation closes with `unplanned_ready_socket_disconnects=0`. `diagnose_runtime` likewise showed zero event-loop stalls, Green resource pressure, no relay outage, and no matching system sleep. Machine Bridge still cannot identify which outer connector/gateway/client hop terminated the HTTP request, but the new evidence is sufficient to say that this reproduced failure did not require a Worker-to-daemon relay disconnect.
|
|
26
|
+
|
|
27
|
+
**The public contract was internally inconsistent.** Project guidance already prohibited one overlong host call and documented 40 seconds as the safe default, yet the hosted `read_job.wait_ms` schema, Worker timeout budget, daemon relay schema extension, and `server_info.tool_delivery.remote_managed_job_read_wait_max_ms` all advertised 300000 ms. That made an unverified 120/180/300-second request a valid model action even though the target host had only demonstrated 40-second default and 60-second explicit survival. Beta.152 makes the executable contract match the safety guidance: public hosted reads cap at 60000 ms, default at 40000 ms, and long work uses repeated server-paced reads of the same durable job. Local/stdio remains capped at 40000 ms. The 315000 ms relay acknowledgement/result-retention envelope remains independent so a completed result can still be owned safely across response settlement without claiming that an external host can keep one request open for that duration.
|
|
28
|
+
|
|
29
|
+
**Release consequence.** Beta.151 acceptance remains valid evidence only for its exact bytes and for the continuity diagnosis it enabled; the new blocking host-request defect means it cannot authorize publication. Beta.152 changes the public tool schema and hosted orchestration semantics, so tool schema generation advances to 19 and fresh release evidence is required. No npm publication is authorized by this diagnostic or by the GitHub review flow.
|
|
30
|
+
|
|
31
|
+
## 2026-08-28 beta.151 severe-interruption continuity and owner-state crash review
|
|
32
|
+
|
|
33
|
+
**The continuing owner-visible interruption report remains a release blocker, while the existing Worker counter was not sufficiently qualified to identify its layer.** Live beta.150 diagnostics around the report showed the local daemon operational, resource pressure green, no matching system sleep, no event-loop stall, and a ready relay after one short locally observed WebSocket recovery. The Durable Object continuity record simultaneously showed 158 socket disconnects / 109 unplanned disconnects and a later close-1006 timestamp. Source review found those totals could not be treated as equivalent user-visible outages: cleanup ownership was kept in an isolate-local `WeakMap`, yet durable disconnect recording occurred even when `beginCleanup()` returned `first=false`. After an isolate replacement, an already expired socket could therefore deliver a delayed close/error and be counted again. Planned-drain intent was likewise kept only in an isolate-local `WeakSet`, allowing a planned close after isolate replacement to be mislabeled unplanned. This explains the conflict between daemon-local ready history and much larger Worker totals; it does not prove the user's interruption is harmless or identify the external host-settlement layer, which Machine Bridge still cannot observe.
|
|
34
|
+
|
|
35
|
+
**Continuity schema 2 makes future evidence causal enough to separate handshake churn from authoritative-channel loss.** Socket close/error evidence is persisted only for the first cleanup transition. Draining intent is serialized into the hibernation-safe WebSocket attachment and is used both to exclude a draining channel from ready routing after isolate replacement and to classify its later close as planned. Each bounded disconnect records only role, `was_ready`, `connected_at`, close code/cleanliness, kind, planned flag, and timestamp. Aggregate transport churn remains available as `socket_disconnects` / `unplanned_socket_disconnects`, while `ready_socket_disconnects` / `unplanned_ready_socket_disconnects` and `last_ready_socket_disconnect` are the qualified evidence for sockets that had actually reached the ready role. Legacy schema-1 socket counters are reset on upgrade because their accumulated classification cannot be repaired retrospectively; compatible planned-drain and client-cancellation evidence is preserved. No account/client identity, daemon instance ID, connection ID, call/tool identity, argument/result, endpoint, or close reason is persisted.
|
|
36
|
+
|
|
37
|
+
**The same review also closes a distinct exclusive-publication crash window exposed by release verification.** `createExclusiveFileSync` establishes exclusive publication by hard-linking a private staging inode to its destination and then removing the staging name. A process killed between those operations leaves a valid destination with two links. Secure readers intentionally reject multiply linked state, so without recovery the lock can become permanently unusable. The repair reserves the generator's hidden `.<target>.<pid>.<random>.tmp` namespace for crash recovery: after the existing transient retries, only a destination with exactly two links and exactly one same-inode alias in that namespace is eligible. Recovery opens the destination with `O_NOFOLLOW`, proves the same inode before mutation, removes only that reserved staging alias, then verifies through the held descriptor that the destination path still resolves to the same `dev+ino` with one link. Hard links outside the reserved namespace and all ambiguous identity states remain fail-closed.
|
|
38
|
+
|
|
39
|
+
**Release consequence.** Both repairs change packaged runtime bytes and the owner-visible continuity contract. Beta.150 acceptance cannot authorize the revised tree. Package/runtime identity advances to beta.151 and hosted tool schema generation advances to 18. Fresh frozen verification, candidate preparation/install-only proof, detached activation, activated-package OAuth canary, live generation-18 continuity baseline, acceptance, and exact-head provider gates are required before GitHub prerelease publication. npm publication remains the sole separate exact-version owner authorization boundary.
|
|
40
|
+
|
|
41
|
+
## 2026-08-28 beta.150 merged-worktree accepted-candidate publication review
|
|
42
|
+
|
|
43
|
+
**The first beta.149 canonical GitHub prerelease attempt falsified an unstated worktree assumption.** The accepted beta.149 source was squash-merged and the canonical `main` worktree fast-forwarded to exact merge commit `d9bf66d8c4f922683aa35bad19c5ee0aca7bf15d`. Exact main CI, CodeQL, Governance, OpenSSF Scorecard, and Workflow Policy were green. `npm run prerelease:release` independently rebuilt dependencies with hardened npm, passed all 131 full-plan tasks, revalidated candidate SHA-1 `9a61cacda78c2afba66e75c3772efcefac5352a7`, and revalidated those exact provider checks. It then failed before local tag creation with `release candidate manifest version does not match the current package`. Direct bounded inspection showed why: canonical `main` still had an ignored `.release-candidate/manifest.json` for beta.146. The feature worktree's beta.149 tarball/manifest were never tracked and therefore did not follow squash-merge/fast-forward. Remote reconciliation confirmed no beta.149 tag, GitHub Release, or npm object was created.
|
|
44
|
+
|
|
45
|
+
**Tracked acceptance is now the publication authority; ignored candidate state is not.** `stageAcceptedCandidateTarball` keeps its strict local-manifest path when no explicit npm CLI is supplied, preserving activation/developer fail-closed behavior for stale, unsafe, or mismatched local candidate files. GitHub and npm publication, however, already establish an integrity-pinned hardened npm CLI and current-source acceptance before mutation. Those production callers now pass that CLI into the shared staging layer. The layer packs the current exact source only into a private temporary directory, compares package name/version/filename/SHA-1/SRI against the verified acceptance metadata, independently recomputes and compares the promotion-content digest, verifies the tarball bytes, and only then writes the final staged upload copy at mode `0600`. A missing or stale `.release-candidate` therefore cannot block a valid merged accepted source, while any nondeterministic or changed pack byte still fails before tag, GitHub Release, or npm upload mutation.
|
|
46
|
+
|
|
47
|
+
**Release consequence.** This repair changes packaged release tooling and documentation after beta.149 acceptance, so beta.149 evidence cannot authorize the revised package even though its runtime diagnostic behavior remains valid evidence for the incident. The beta.149 acceptance record is removed and package/runtime identity advances to beta.150. Hosted tool schema generation remains 17. Beta.150 must repeat frozen verification, exact candidate preparation/install-only proof, guarded activation, activated-package OAuth canary, live verification, acceptance, review/merge, exact-head provider gates, and GitHub prerelease creation. npm registry publication remains the sole separate current-task owner authorization boundary.
|
|
48
|
+
|
|
49
|
+
## 2026-08-28 beta.149 third independent sleep/wake causality review
|
|
50
|
+
|
|
51
|
+
**Live beta.148 evidence exposed a false awake-network inference in the existing sleep correlation.** The owner diagnostic retained five short recovered WSS episodes. Four disconnect timestamps were exactly aligned with the ends of recent macOS sleep intervals: `08:26:06Z`, `08:30:50Z`, `08:46:00Z`, and `08:51:33Z`. The latest runtime heartbeat simultaneously recorded a `299497ms` event-loop stall ending at `08:51:32.984Z`, while bounded `pmset` history recorded a `305000ms` maintenance sleep ending at `08:51:33Z`; previous-ready inbound silence was also approximately the sleep duration. Despite that independent suspension evidence, `relay_outage_analysis` returned `no_matching_recent_system_sleep` because its beta.143 algorithm intersected only the observed close-to-ready interval (`08:51:33Z` -> `08:51:36.978Z`) with sleep. That interval necessarily begins after the sleeping JavaScript process resumes and can therefore have zero overlap even when sleep caused the stale socket to be discovered.
|
|
52
|
+
|
|
53
|
+
**Beta.149 adds a conservative wake-boundary aftermath classification rather than weakening awake-reset evidence.** A zero-overlap recovered outage becomes `wake_boundary_system_sleep_aftermath` only when three conditions identify the same bounded sleep interval: its end is within thirty seconds of the observed disconnect, the recorded event-loop-stall end is within the same tolerance of that sleep end, and the stall duration matches the sleep duration within that tolerance. Merely occurring near a wake boundary is insufficient. Existing `majority_system_sleep_overlap` / `partial_system_sleep_overlap` remain authoritative when the close-to-ready interval itself overlaps sleep, while a near-wake reset without same-sleep stall evidence remains `no_matching_recent_system_sleep`. The projection adds no path, interface, endpoint, account, call, argument, or result data; it changes only the fixed causal classification of already-bounded timing evidence.
|
|
54
|
+
|
|
55
|
+
**Release consequence.** Owner-visible diagnostic semantics and host guidance change, so tool schema generation advances from 16 to 17 and package/runtime identity advances from beta.148 to beta.149. The previously accepted beta.148 bytes remain diagnostic evidence for the observed incident but cannot authorize the revised tree. Its acceptance record is removed. Beta.149 requires fresh frozen verification, exact candidate preparation/install-only proof, guarded activation, activated-package OAuth canary, live confirmation that the retained wake-boundary episode is classified as sleep aftermath, acceptance, and exact-head provider gates. npm publication remains the sole separate current-task owner authorization boundary.
|
|
56
|
+
|
|
57
|
+
## 2026-08-28 beta.148 second independent release-path, logging-privacy, and maintainability review
|
|
58
|
+
|
|
59
|
+
**A second independent release-path pass found a real ambient dependency-tree defect after the beta.147 review was already green.** The canonical beta.147 GitHub prerelease attempt ran the frozen full plan from merged `main`; 126 tasks passed, then `sbom:test` failed because `npm sbom` correctly classified five root packages as invalid: the ignored `node_modules` tree still held the pre-upgrade `ws`, `@types/node`, ESLint, `globals`, and Wrangler versions while the committed manifest/lock required the new exact pins. A clean `npm ci` immediately restored an exact tree, after which the real CycloneDX gate passed with 117 components / 118 dependency records, the fast plan passed, package testing passed, and both development and production `npm audit` reported zero vulnerabilities. This is not a source-behavior regression; it is a release-control defect because the canonical release command depended on an unrecorded mutable workspace artifact.
|
|
60
|
+
|
|
61
|
+
**Release publication now establishes its source dependency generation instead of trusting that ignored state.** Both GitHub release and npm publication create the integrity-pinned hardened npm session first and run explicit lockfile-only `npm ci` with inherited execution-mode overrides removed before full verification. GitHub publication then performs its full check and acceptance verification against that rebuilt tree. npm publication similarly moves default acceptance regeneration after hardened npm creation, so the ambient lifecycle npm is only the launcher and no longer repacks acceptance bytes. GitHub release's long npm lifecycle stages use the shared isolated `runExecutable(..., hardTimeout:true)` process-tree settlement, preventing a timeout from returning while resistant npm lifecycle descendants continue. The real `sbom:test` is now the first full-plan task, so this class of invalid source tree fails in roughly one second rather than after the long managed-job and coverage phases. The source tree still remains frozen by Git status/digest/acceptance checks; rebuilding ignored dependencies is not permission to repair tracked source during publication.
|
|
62
|
+
|
|
63
|
+
**The same pass found one concrete privacy blind spot in defense-in-depth log sanitization.** The shared free-form pattern covered `mcp_at_*` access tokens and `mcp_code_*` authorization codes but omitted `mcp_rt_*` refresh tokens. A refresh token embedded by a lower-level exception into an otherwise ordinary `detail` or `message` field could therefore bypass field-name redaction when it lacked a Bearer prefix. The portable sanitizer now recognizes refresh tokens and stable `acct_*`, `mcp_client_*`, and `mcp_family_*` authorization identities in free-form text; account/client/family ID field-name variants are also treated as privacy-sensitive. Local structured logging, Worker metrics events, throttled Worker-edge events, and release diagnostics inject synthetic forms and require the raw values to be absent. Initial owner credentials remain an intentional exact stdout/JSON provisioning result, not an operational logging path, and the privacy guide already requires treating that output as secret material.
|
|
64
|
+
|
|
65
|
+
**Two smaller robustness/maintainability gaps were also closed rather than left as review notes.** The standalone `github-backlog.mjs` runner previously had no child timeout even though the guarded `github:push` injection used a 120-second network bound; the standalone command now has the same fixed two-minute hard deadline and 8 MiB output ceiling. Architecture line budgets already protected the large Computer Use, browser, runtime, relay, and job modules, but `state.mjs` (961 lines, 29 exports) and `service.mjs` (728 lines, 18 exports) had no growth ceiling despite being broad composition surfaces. New 980/750-line limits do not pretend those modules need an immediate risky pre-release split; they make the next material growth require deliberate extraction or an explicit architecture-budget review. The package-manifest gate also caught the newly added dependency helper at owner-only mode `0600`; it was normalized to the repository's required ordinary-file mode `0644`, and `package:test` then passed with 573 files. This was a local authoring artifact rather than a runtime permission defect, but retaining the evidence confirms the package-mode gate is exercising new files rather than only historical fixtures.
|
|
66
|
+
|
|
67
|
+
**Beta.147 acceptance cannot survive this review.** The repair changes packaged `scripts/`, shared runtime redaction, tests, and shipped documentation after beta.147 had already been candidate-accepted. Read-only publication reconciliation confirmed there is no local or remote `v3.0.0-beta.147` tag, no GitHub Release, and npm returns E404 for `machine-bridge-mcp@3.0.0-beta.147`; therefore no immutable public object needs rollback. The tracked beta.147 acceptance record is removed and package/runtime identity advances to beta.148. Hosted tool schema generation remains 16 because this pass does not change the public MCP catalog/result contract. Beta.148 must repeat frozen verification, exact candidate preparation/install-only proof, guarded activation, activated-package OAuth canary, live verification, acceptance, and exact-head provider checks before GitHub prerelease publication. npm publication remains the sole separate current-task owner authorization boundary.
|
|
68
|
+
|
|
69
|
+
## 2026-08-28 beta.147 independent architecture, continuity, dependency, privacy, and external-source review
|
|
70
|
+
|
|
71
|
+
**Beta.146 is rejected as the stable-soak candidate by a reproduced continuity-contract failure.** During an otherwise read-only dependency probe, a one-step durable `run_process` returned `status=running`, an exact `job_id`, and `follow_up_read_required=true`; the later protocol-required read of that same ID returned typed `not_found`. The operation itself was not proven to have failed. Source tracing showed the behavior matched the beta.141/142 design: every recent transient process result competed for one fixed sixteen-result recovery reserve, even when Machine Bridge had explicitly told the caller that another `read_job` was required. Shared helper churn could therefore evict a still-promised recovery record before a reasonable follow-up. This is a blocking durable-continuity defect rather than a package-style concern, so elapsed beta.146 soak evidence cannot carry into stable promotion.
|
|
72
|
+
|
|
73
|
+
**The retention fix distinguishes retrieval obligation from outer delivery uncertainty.** One-step durable process status now carries a private `transient_recovery_pending` bit from acceptance. If the hosted response remains active and later needs `read_job`, a terminal result with that bit set keeps stronger eviction priority for the fixed thirty-minute transient grace instead of competing for the sixteen ordinary delivery slots. The first successfully produced terminal hosted response—either the initial two-second settlement or a later `read_job`—clears the bit under the job transition lock after securely validating the status identity; failure to clear only over-protects the result and therefore cannot weaken recovery. Already-delivered helper terminals remain bounded by the newest-sixteen reserve. `recent_process_recovery` stays capped at sixteen public handles, but prioritizes retained follow-up-required helpers and exposes neither the private marker nor step output. The durable store remains capped at 512. This implements the same general crash-state principle seen in Trigger.dev's dual-store snapshot code: when an intermediate write can fail, choose the failure direction that leaves excess recoverability rather than false loss of ownership.
|
|
74
|
+
|
|
75
|
+
**The release path had a separate npm authentication/privacy defect.** npm 12 can answer publication with `EOTP` and emit one-time `/auth/cli/...` and `/-/v1/done?...` browser challenge URLs. The previous publisher inherited upload stderr in non-TTY automation, then wrapped the same stderr in its release diagnostic, and after registry reconciliation still reported the state as generic ambiguity even when the exact version was confirmed absent. The repaired publisher captures non-TTY upload output, preserves inherited stdio only for a real owner terminal where npm must complete its Web OTP in the same process, classifies EOTP explicitly after bounded registry reconciliation, redacts the challenge URL forms, and passes `--logs-max=0` to every publication npm stage so npm itself does not persist those URLs in its cache debug log. Owner authorization remains the separate conversational boundary; TTY is only a transport requirement for npm's interactive authentication and is not treated as proof of human authorization.
|
|
76
|
+
|
|
77
|
+
**Dependency freshness is now part of the repaired generation instead of a deferred maintenance note.** All direct outdated pins were advanced exactly: `ws` 8.21.1 -> 8.21.3, `@types/node` 26.1.2 -> 26.4.0, ESLint 10.8.0 -> 10.9.1, `globals` 17.8.0 -> 17.11.0, and Wrangler 4.120.0 -> 4.127.0. The separate package-manager/bootstrap pin was reviewed too rather than being hidden by `npm outdated`: registry `latest` is npm 12.0.2, so `packageManager`, documented bootstrap commands, and the integrity-pinned hardened npm artifact advance from 12.0.1 to 12.0.2 with exact SRI `sha512-uIXokLlBj6FpNUTQX1PmT5pz7BlIN9QlixX+zdaSNHsd0qUXsbDLr50xzY6Sw7cJVr0uzHKDOle0swmPW/p5Qw==`. A real temporary hardened 12.0.2 construction passed the existing identity/tree verifier while retaining the reviewed undici 6.28.0 and brace-expansion 5.0.9 replacements. The isolated Wrangler control-plane manifest/lock/validator and exact install-script allowlist advance with it to workerd 1.20260826.1 rather than leaving two toolchain generations. The new workerd postinstall was initially blocked by the exact allowlist as designed, then approved only after the dependency identity was reconciled. The upgraded tree reports zero npm audit vulnerabilities, no unreviewed install scripts, 109 verified registry signatures, 35 verified attestations, a passing Wrangler toolchain regression, regenerated Worker types, and a successful Wrangler 4.127.0 dry-run. `npm outdated --json` is empty after the update.
|
|
78
|
+
|
|
79
|
+
**External implementation review covered source, not README summaries.** Thirty related repositories were indexed through the repository-required local `gh` control plane, representative implementation files were fetched, and their exact default-branch heads were pinned for reproducibility. Official MCP implementations: `modelcontextprotocol/typescript-sdk@7b781ed4`, `python-sdk@6705402e`, `go-sdk@165121e5`, `rust-sdk@12db0283`, `csharp-sdk@609499b2`, `java-sdk@b31841ee`, `servers@562feeb2`, and `registry@6036804f`. MCP frameworks/services: `jlowin/fastmcp@89a7d7a9`, `punkpeye/fastmcp@f320abe6`, `mark3labs/mcp-go@56af04b2`, `mcp-use/mcp-use@40ee6c3a`, `stacklok/toolhive@0a740048`, `tadata-org/fastapi_mcp@e5cad13c`, and `lastmile-ai/mcp-agent@f62d8493`. Browser/local/credential implementations: `microsoft/playwright-mcp@d0c29a56`, `ChromeDevTools/chrome-devtools-mcp@d1baa90e`, `mobile-next/mobile-mcp@f0564b66`, `supermemoryai/apple-mcp@08e2c531`, `cloudflare/mcp-server-cloudflare@08d74365`, and `cloudflare/workers-sdk@eb018505`. Durable execution systems: `temporalio/sdk-typescript@5373d58f`, `taskforcesh/bullmq@86d539d8`, `triggerdotdev/trigger.dev@acaa5ec2`, `inngest/inngest@e405defa`, and `hatchet-dev/hatchet@3d586d3e`. Reconnect/WebSocket implementations: `socketio/socket.io@ae7fb46e`, `pladaria/reconnecting-websocket@05a2f7cb`, `joewalnes/reconnecting-websocket@fd7c819b`, and `websockets/ws@d9b89544`.
|
|
80
|
+
|
|
81
|
+
**The useful external patterns mostly validated existing Machine Bridge choices rather than justifying imports.** The official TypeScript SDK's per-request transport intentionally excludes public session/replay state, reinforcing Machine Bridge's separation between request-scoped MCP and private daemon continuity. Rust/FastMCP event stores commit before advertising replay IDs and scope replay to a session/stream; Machine Bridge already uses exact-generation/session ownership and fail-closed replay evidence. C# session management distinguishes active/closing sessions from idle capacity history, matching the rationale for the new two-tier helper retention. mcp-go makes missing token state distinct from storage failure and validates authorization-server URLs; Machine Bridge's Worker OAuth reader already creates an empty store only for genuinely absent state and returns 503 on present-but-invalid persisted state. Trigger.dev deliberately orders cross-store writes so crash gaps leave a repairable state and reuses one repair identity; Machine Bridge's result-first terminal settlement, idempotency, and recovery locks already follow that model. ChromeDevTools MCP clears/sets browser ownership state in an order that prevents concurrent close from orphaning a launched browser; Machine Bridge's `startGeneration`, exact-upstream comparison, and extension-generation fences are stronger. Cloudflare's encrypted credential store demonstrates a possible future OS-keyring hardening direction, but same-user malicious processes remain outside the current repository-only isolation claim and no concrete secret leak was found to justify expanding beta.147 scope.
|
|
82
|
+
|
|
83
|
+
**One external reconnect pattern did expose a real resilience gap.** The older joewalnes reconnecting WebSocket resets its retry counter immediately on `open`, while the newer pladaria implementation waits for a configurable `minUptime` before accepting the connection as stable. Machine Bridge likewise reset `reconnectAttempt` immediately on `ready_ack`. That does not explain the observed long established-flow black holes, but it makes a rapid `ready -> close -> ready -> close` flap repeatedly return to the shortest reconnect delay. Beta.147 keeps `ready_ack` fully authoritative for business traffic but moves only the backoff reset behind a generation-fenced five-second stable-ready timer. A close before that boundary preserves the prior exponential attempt; a stable ready interval resets it. The close path also uses measured monotonic ready duration as a fallback so a delayed timer caused by event-loop suspension cannot falsely retain old backoff after genuinely stable uptime.
|
|
84
|
+
|
|
85
|
+
**The independent test review found a critical-coverage policy blind spot rather than a demonstrated auth bypass.** `device-identity.mjs` and `device-session-auth.mjs` were marked critical yet had no block-coverage minimum, and the previous suite concentrated on valid signatures/replay/tamper paths rather than malformed local JWK/origin/transcript/certificate/timestamp inputs. A direct malformed-input matrix now exercises those rejection branches. The coverage generation guard rejected the first measurement after concurrent edits changed the tree; a later stable frozen-tree run then measured `device-identity.mjs` at 100.0% functions / 83.6% blocks, `device-session-auth.mjs` at 100.0% / 97.0%, the new `relay-reconnect.mjs` at 100.0% / 85.7%, and `managed-job-transient-recovery.mjs` at 100.0% / 100.0%. Their release gates are therefore raised to 95/75, 100/90, 100/80, and 100/90 respectively rather than retaining null or zero placeholders. Changing the threshold file created a new verification generation, and that tightened frozen-tree coverage run also passed with the same four measured ratios; the new nonzero gates are therefore proven on their own final source generation rather than inherited from the exploratory measurement.
|
|
86
|
+
|
|
87
|
+
**Documentation/package review did not delete history merely to reduce artifact size.** `docs/AUDIT.md` is large, but `package-test` deliberately requires the engineering/security audit record in the consumer tarball, and the 2026-08-26 beta.139 review already documented that auditability choice. Historical version-specific statements remain as then-true evidence; current normative documents are updated instead. Published-prerelease installation now also tells operators to reload the unpacked Chromium extension before treating browser behavior as soak evidence, because package/service activation cannot itself refresh the extension loaded in an existing profile.
|
|
88
|
+
|
|
89
|
+
**Release consequence.** These changes modify packaged runtime behavior, release tooling, dependencies, public `list_jobs` recovery semantics, and shipped documentation. The tree advances to beta.147 and hosted tool schema generation 16. Beta.146 formal soak is invalidated by the blocking recovery defect. Beta.147 requires a fresh frozen full verification, exact candidate preparation/install-only proof, guarded activation, activated-package OAuth canary, live durable-helper saturation/recovery and relay-flap observation where practical, acceptance, PR/exact-head provider gates, GitHub prerelease publication, and then a separately owner-authorized npm publication. Only registry-verified beta.147 installation/activation can start the new seven-day major-prerelease soak.
|
|
90
|
+
|
|
3
91
|
## 2026-08-27 beta.146 live start-job result-boundary follow-up
|
|
4
92
|
|
|
5
93
|
**Beta.145 is rejected by live behavior even though its underlying durable work remained healthy.** The exact candidate passed the 131-task frozen plan, candidate/install-only validation, guarded Worker/login-daemon activation, and the activated-package OAuth canary. The canary's initiating hosted `start_job` nevertheless returned public `internal_error`. Reusing its idempotency key with a deliberately different plan produced the expected conflict containing the already-bound managed-job ID; reading that exact ID proved the original canary job had completed successfully. A second harmless zero-exit hosted `start_job` independently reproduced the same public `internal_error`, while `exec_command` continued to use the shared two-second settlement path successfully. This isolates the regression to ordinary managed-job result construction rather than Worker/OAuth availability, the managed-job runner, process-carrier settlement, relay transport, or resource admission.
|
package/docs/CLIENTS.md
CHANGED
|
@@ -172,7 +172,7 @@ Machine Bridge itself does not block files because their names look sensitive. I
|
|
|
172
172
|
|
|
173
173
|
Expected file-operation failures arrive as ordinary MCP tool-error results, not JSON-RPC transport failures. Clients should branch first on `structuredContent.error.code`, then optionally on the bounded `details.reason`. For example, `conflict/already_exists`, `conflict/hash_mismatch`, `conflict/text_ambiguous`, and `conflict/context_not_found` require a fresh read and reconciliation; `not_found/text_not_found` means the requested edit fragment is absent; `invalid_request` means the request or patch syntax must change. Do not log or display tool arguments to reconstruct diagnostics: public error details intentionally omit paths, file content, edit fragments, and compared hashes.
|
|
174
174
|
|
|
175
|
-
Remote configurable browser/application foreground tools default to 20 seconds, compound `computer_observe`/`computer_act` default to 30 seconds, and all configurable foreground tools advertise a 45-second maximum. Those values bound daemon execution; the Worker records its settlement deadline five seconds later. Remote `exec_command`, `run_process`, and `run_local_command` use a separate durable 10-second acceptance contract with independently bounded detached execution. Hosted `read_process` supports paced same-response follow-up: the actual output/exit blocking wait is at most one second, while a repeated would-block request inside the fifteen-second cooldown remains inside that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. A live result reports `status_polling_mode=paced_followup` plus `next_blocking_poll_after_ms`; callers may continue reading when the current task needs output or terminal state, but must not busy-loop. Admission and transport latency may consume part of an individual request interval, and none of these values guarantees host receipt. They are not evidence for a guessed whole-assistant-turn deadline: clients must not stop merely because some number of wall-clock minutes has elapsed. Missing or role-hidden tools, non-object arguments, and requests above current limits fail at the shared Worker schema boundary before daemon dispatch; schema failures include `side_effects_started=false`. SSE-capable current requests receive the same pre-dispatch validation as JSON responses and never allocate a recovery stream. Do not treat this as a retry invitation for the same oversized mutation, and do not attempt to evade a host refusal by renaming, encoding, or switching to another arbitrary execution tool. Durable acceptance does not force a hosted-turn handoff. When the current task needs terminal state, a known active job may be followed through bounded same-response `read_job` calls while calls continue to be accepted; relay reads report `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Hosted active `read_job` defaults to 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` requests an immediate checkpoint, while
|
|
175
|
+
Remote configurable browser/application foreground tools default to 20 seconds, compound `computer_observe`/`computer_act` default to 30 seconds, and all configurable foreground tools advertise a 45-second maximum. Those values bound daemon execution; the Worker records its settlement deadline five seconds later. Remote `exec_command`, `run_process`, and `run_local_command` use a separate durable 10-second acceptance contract with independently bounded detached execution. Hosted `read_process` supports paced same-response follow-up: the actual output/exit blocking wait is at most one second, while a repeated would-block request inside the fifteen-second cooldown remains inside that same MCP call until output/exit or the cooldown boundary instead of returning a rapid running checkpoint. A live result reports `status_polling_mode=paced_followup` plus `next_blocking_poll_after_ms`; callers may continue reading when the current task needs output or terminal state, but must not busy-loop. Admission and transport latency may consume part of an individual request interval, and none of these values guarantees host receipt. They are not evidence for a guessed whole-assistant-turn deadline: clients must not stop merely because some number of wall-clock minutes has elapsed. Missing or role-hidden tools, non-object arguments, and requests above current limits fail at the shared Worker schema boundary before daemon dispatch; schema failures include `side_effects_started=false`. SSE-capable current requests receive the same pre-dispatch validation as JSON responses and never allocate a recovery stream. Do not treat this as a retry invitation for the same oversized mutation, and do not attempt to evade a host refusal by renaming, encoding, or switching to another arbitrary execution tool. Durable acceptance does not force a hosted-turn handoff. When the current task needs terminal state, a known active job may be followed through bounded same-response `read_job` calls while calls continue to be accepted; relay reads report `status_polling_mode=bounded_followup` and `host_turn_handoff_recommended=false`. Hosted active `read_job` defaults to 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` requests an immediate checkpoint, while public hosted `wait_ms` is capped at 60 seconds. The default is deliberately 40 seconds rather than equal to that maximum: live acceptance must prove that the target host carries the omitted-parameter default without terminating the tool call, and longer jobs continue through another paced read of the same `job_id` rather than a longer single HTTP request. Per-call survival does not establish aggregate assistant-response lifetime; a host may still impose a real boundary after many individually successful reads. Do not replace server-side pacing with a rapid host-side checkpoint loop. Handoff is justified only after an actual host/tool boundary is observed, external input or authorization is required, or the user explicitly requested a checkpoint. After a real host boundary, resume the same durable `job_id` rather than resubmitting the operation. For a coherent non-interactive workflow with several local commands, prefer a repository umbrella command or one multi-step `start_job` rather than a chain of one-step process carriers; this is a host-event-density control, not a task-duration limit. A valid job ID that is no longer retained returns typed `not_found`; this is missing recovery evidence, not proof that its side effect never executed. Completed one-step process carriers use lower-priority terminal retention than explicit managed jobs, so removable helper history is reclaimed first within the shared 512-state durable store; `list_jobs` is independently bounded to 50 returned records per response and remains an inventory operation rather than a substitute polling mechanism. The same pacing principle applies to read-only status/diagnostic surfaces such as `server_info` and `diagnose_runtime`: use them to capture relevant evidence rather than switching surfaces merely to wait on the same background state. `server/discover` and `tools/list` both advertise `ttlMs=0`; discovery instructions therefore refresh alongside the tool contract, every host-visible tool description carries `Tool schema generation N`, and `server_info.tool_delivery` exposes the current generation/version plus `discovery_ttl_ms=0` and `tool_list_ttl_ms=0` while reporting that the server cannot know which generation an external host has cached. `host_turn_deadline_observable=false` means Machine Bridge also cannot predict when the external host will terminate an assistant turn; `managed_jobs_detached_from_mcp_response=true` means that boundary does not itself cancel an already accepted durable job. A release that changes tool semantics therefore verifies the live Machine Bridge runtime/discovery contract and harmless changed-generation invocation behavior after activation. When ChatGPT workspace governance freezes approved actions, use the Workspace Action control snapshot after automation performs the supported refresh/review path as the product-level publication evidence. Opaque host-internal cache inspection is intentionally excluded from release acceptance and must not trigger reconnect, refresh, recreation, or republication. For durable workflows that must survive a later host boundary:
|
|
176
176
|
|
|
177
177
|
1. register credentials locally as resource aliases so their values never enter MCP arguments;
|
|
178
178
|
2. choose a fresh `idempotency_key` and submit a complete policy-authorized `start_job` plan before the workflow depends on later cleanup calls; hosted generation-8 `start_job` requires that key before dispatch so an ambiguous acceptance response can be retried with the same logical arguments instead of creating a second job; batch coherent non-interactive local commands into that managed plan (or a repository umbrella command) instead of creating a one-step tool event for every small probe; `stage_job` is only a non-running draft, while an explicit local operator may use `machine-mcp job submit PLAN.json`;
|