machine-bridge-mcp 3.0.0-beta.158 → 3.0.0-beta.165
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 +43 -0
- package/README.md +5 -1
- package/SECURITY.md +2 -0
- package/browser-extension/manifest.json +1 -1
- package/docs/ARCHITECTURE.md +6 -4
- package/docs/AUDIT.md +94 -0
- package/docs/MULTI_ACCOUNT.md +2 -0
- package/docs/OPERATIONS.md +5 -3
- package/docs/PRIVACY.md +4 -0
- package/docs/PROJECT_STANDARDS.md +5 -1
- package/docs/TESTING.md +8 -4
- package/docs/TOOL_REFERENCE.md +9 -2
- package/package.json +1 -1
- package/scripts/consumer-package-security.mjs +1 -1
- package/scripts/generate-worker-types.mjs +211 -9
- package/scripts/start-release-candidate.mjs +0 -1
- package/src/local/browser-bridge.mjs +3 -2
- package/src/local/browser-broker-server.mjs +7 -3
- package/src/local/browser-pairing-launch.mjs +2 -2
- package/src/local/cli-local-admin.mjs +18 -8
- package/src/local/cli.mjs +10 -8
- package/src/local/daemon-http-relay-connection.mjs +3 -1
- package/src/local/daemon-http-relay-request.mjs +4 -3
- package/src/local/daemon-process.mjs +20 -9
- package/src/local/full-access-test.mjs +10 -5
- package/src/local/managed-job-acceptance.mjs +1 -0
- package/src/local/managed-job-active-child.mjs +8 -7
- package/src/local/managed-job-plan.mjs +8 -1
- package/src/local/managed-job-projection.mjs +4 -1
- package/src/local/managed-job-retention-policy.mjs +11 -2
- package/src/local/managed-job-retention.mjs +4 -2
- package/src/local/managed-job-transient-recovery-capacity.mjs +141 -0
- package/src/local/managed-jobs.mjs +15 -7
- package/src/local/process-identity.mjs +12 -7
- package/src/local/process-tree-force-settlement.mjs +1 -3
- package/src/local/remote-configuration.mjs +2 -2
- package/src/local/resource-admission.mjs +3 -9
- package/src/local/resource-host-sample-file.mjs +2 -18
- package/src/local/resource-legacy-staging-recovery.mjs +71 -0
- package/src/local/resource-probe-command.mjs +7 -2
- package/src/local/resource-staging-recovery.mjs +11 -3
- package/src/local/resource-state-file.mjs +22 -0
- package/src/local/shell.mjs +22 -8
- package/src/local/worker-health.mjs +14 -2
- package/src/shared/relay-contract.json +1 -0
- package/src/shared/server-metadata.json +4 -4
- package/src/shared/tool-catalog.json +9 -2
- package/src/worker/index.ts +22 -22
- package/src/worker/managed-job-hosted-authority.ts +19 -1
- package/src/worker/managed-job-hosted-schema.ts +4 -2
- package/src/worker/mcp-controller.ts +4 -4
- package/src/worker/mcp-job-monitor-claims.ts +94 -0
- package/src/worker/mcp-job-monitor-status.ts +28 -0
- package/src/worker/mcp-job-monitor-store.ts +149 -0
- package/src/worker/mcp-job-monitor-tools.ts +89 -0
- package/src/worker/mcp-job-monitor-ui.ts +129 -0
- package/src/worker/tool-catalog.ts +7 -1
- package/src/worker/worker-mcp-config.ts +6 -1
- package/src/worker/worker-tool-authority.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.165 - 2026-09-06
|
|
4
|
+
|
|
5
|
+
- Supersede beta.164 before GitHub prerelease after exact-head Windows CI reproduced a hard-timeout settlement regression inherited from beta.163. The Windows `terminateProcessTreeAndWait()` barrier again treats `taskkill /T /F` terminal `close(0)` as the bounded whole-tree settlement proof documented and provider-tested in beta.138, instead of starting an additional CIM process-enumeration sweep after `taskkill` has already succeeded. The five-second confirmation window, fail-closed `termination_settled=false`/`EUNSETTLED` path, and unchanged resistant-descendant publication regression remain intact.
|
|
6
|
+
- Keep the Worker-types rollback regression cross-platform without introducing a CodeQL file-system race. Tests compare restored bytes and mode bits from one opened file descriptor, so POSIX still enforces the requested `0640` mode while Windows verifies the mode actually exposed by that filesystem; content and mode are no longer read through separate pathname operations.
|
|
7
|
+
- Advance package/runtime identity to `3.0.0-beta.165`. Hosted tool schema generation remains 26 because this repair changes no MCP tool name, schema, visibility, description, or result shape. The beta.164 candidate and acceptance are historical evidence for different packaged bytes and cannot authorize beta.165.
|
|
8
|
+
|
|
9
|
+
## 3.0.0-beta.164 - 2026-09-04
|
|
10
|
+
|
|
11
|
+
- Move long-task continuity off the external assistant-turn lifetime without weakening beta.163 ordinary-job semantics. `start_job` now accepts the narrow optional `continuation_mode=task_supervisor` declaration only when the durable job's own reviewed plan already encapsulates all remaining noninteractive task work. Ordinary claimed Job Monitors still keep `host_turn_handoff_recommended=false`; only an active persisted task supervisor plus a proven current View claim may report `host_turn_handoff_recommended=true`, because the supervisor job already owns the remaining execution lifecycle. The Job Monitor remains status-only and gains no execution or capability authority. Machine Bridge can validate the persisted continuation declaration, active state, and current View claim, but it cannot infer conversation-level task completeness; the `task_supervisor` completeness declaration remains an owner/caller semantic assertion.
|
|
12
|
+
- Make the 24-hour private recovery promise for relay-origin one-step helpers hard under capacity pressure rather than merely higher priority. A terminal helper with `transient_recovery_pending=true` and a valid 24-hour `finished_at` window is excluded from capacity eviction; if all 512 retained slots are non-evictable, new admission fails with the existing typed retryable `limit_exceeded` backpressure instead of deleting promised recovery evidence. Producing a terminal hosted response clears the private pending marker and returns the result to the existing thirty-minute/newest-16 delivery reserve; expiry beyond 24 hours also makes the pending terminal evictable.
|
|
13
|
+
- Close the cross-principal availability boundary created by combining that hard recovery promise with delegated durable one-step process tools. `operator` accounts still cannot create explicit persistent plans, but their remote `exec_command` / `run_process` / `run_local_command` calls use `transient_process` managed-job carriers in the same profile-wide 512-state store. New relay non-owner carriers are therefore limited to 16 unresolved pending-recovery promises per account ID plus account version across all OAuth clients/refresh families, under the existing root capacity lock before status publication or runner launch. Existing valid same-key persisted replay remains recoverable at quota; a statusless deterministic directory is only incomplete publication and must re-enter quota admission. Delivery/24-hour expiry releases the account slot, owner/local work remains on the global capacity contract, already accepted 24-hour promises remain hard-protected, and no separate mutable quota ledger or host-visible account inventory is added.
|
|
14
|
+
- Close an independent ResourceCoordinator generation race exposed by repeated frozen full verification. Lease ownership updates atomically replace the persisted resource-state file, so a concurrent secure reader can legitimately observe `MBM_IDENTITY_CHANGED` between descriptor open and path revalidation. One shared resource-state JSON reader now retries only that legal generation-change class for at most four observations, while permission, symlink, hard-link, malformed-content, and persistent identity failures remain fail-closed. Host-sample and coordinator reads reuse the same helper, and `resource-admission.mjs` stays below its existing architecture ceiling instead of raising the limit.
|
|
15
|
+
- Advance package identity to `3.0.0-beta.164` and hosted tool schema generation to 26 because both the `start_job` input contract and managed-job continuation result semantics change. Generation-25/beta.163 audit observations remain historical evidence and are not rewritten.
|
|
16
|
+
|
|
17
|
+
## 3.0.0-beta.163 - 2026-09-02
|
|
18
|
+
|
|
19
|
+
- Correct the task-level continuation semantics of the MCP Apps Job Monitor. A claimed View owns only status polling for one durable job; it does not know whether the initiating assistant still has code changes, verification, release steps, or other work that depends on that job. Claimed active reads therefore keep `host_turn_handoff_recommended=false` while reporting `status_polling_mode=ui_monitor` and `follow_up_read_required=false`. Hosted tool guidance and repository automation standards now forbid mounting the monitor merely to shorten a response when remaining same-response work needs the job's terminal result; those cases must keep the original `job_id` + `recovery_key` and continue bounded server-paced `read_job` follow-up. This closes the failure mode where successful UI polling was incorrectly treated as ownership of the entire assistant task and caused premature conversational handoff. Because the result semantics and hosted tool descriptions change again within the still-unpublished beta.163 candidate, hosted tool schema generation advances from 24 to 25.
|
|
20
|
+
- Separate MCP Apps monitor presentation and background status traffic from the model-visible tool stream after live ChatGPT evidence exposed both as user-visible noise. The exact `Durable job monitor opened.` text shown in the host's tool-status area was traced to Machine Bridge's own `openai/toolInvocation/invoked` metadata rather than an unexplained host message, and a live render probe showed the mounted View could begin a `read_job` before the model's confirmation read. `render_job_monitor` no longer publishes custom invoking/invoked status labels and returns an empty ordinary text projection; model-visible `read_job` is again model-only. The existing one model-side render/claim confirmation remains because live beta.162 also proved that a current View can successfully claim MCP Apps continuation.
|
|
21
|
+
- Move post-claim View polling onto app-only `read_job_monitor`. The Worker verifies the original principal-bound `recovery_key`, treats `ui_monitor_id` only as correlation, strips both values before delegating to one fixed 40-second daemon `read_job`, and exposes no new job inventory or direct network path. App polling is additionally bound to the latest claimed monitor instance: issuing a replacement monitor for the same job/principal retires older instances, while each accepted app read refreshes the current monitor's five-minute handoff-freshness lease. Issued/active/claimed monitor metadata is now transactionally persisted in the BridgeRoom Durable Object rather than an isolate-local `WeakMap`, so a Worker isolate replacement between `render_job_monitor` and the View's `claim_job_monitor` no longer erases the activated render instance and produces `managed-job monitor render instance is not active`; the bounded store contains only job/principal/monitor/state/expiry metadata and never the recovery capability or job result. Expiry of the freshness lease stops it from serving as model-side handoff proof, but does not strand a still-latest View after host suspension or machine sleep; the same View may resume app polling with the original recovery capability unless a newer monitor replaced it. The recovery capability remains the data-read authority; the monitor ID only gates which View may continue automatic polling. A replacement may race with one already-dispatched old read, but no subsequent old-View read is admitted. The View gives `ui/initialize` and `claim_job_monitor` separate 30-second response watchdogs and automatically retries a missing/transport-failed or explicitly retryable claim response at most five times with 1/2/4/8/15-second backoff; claim replay is safe because an already-claimed exact monitor remains claimable and refreshes its bounded handoff freshness. Each app-origin status call separately has a 60-second response watchdog and automatically retries missing/transport-failed or explicitly retryable status responses for at most six attempts using 1/2/4/8/15/30-second backoff. Exhausted or non-retryable failures pause instead of looping; no retry can resubmit the managed job. This targets the separately reproduced client/connector-to-Worker request-failure layer where `mcp_network_error` occurred while daemon-to-Worker relay continuity remained healthy; it does not claim to make an externally terminated ChatGPT assistant turn or final-message delivery observable. Package/runtime identity advances to beta.163 and hosted tool schema generation advances to 24 because tool visibility and the new app-only tool are host-visible schema changes.
|
|
22
|
+
- Harden the monitor as an optional continuation layer rather than a second result channel. `read_job_monitor` now returns only a bounded lifecycle-status projection and discards step output, command text, paths, nested job results, and recovery/control capabilities before the View sees them. The View binds only its initial complete render input, ignores ambient tool-result notifications as state, uses fixed user-facing failure text, and clears the in-memory recovery capability plus pending response watchdogs on teardown. Durable Object monitor-storage failures after a managed job has already been accepted or read no longer reclassify that core result: issuance degrades to an ordinary non-UI continuation, claim probing degrades to “no proven handoff”, and authority-revocation cleanup remains auxiliary with bounded error-class telemetry only. Persisted monitor state also rejects duplicate monitor IDs/sequences and impossible sequence generations.
|
|
23
|
+
- Keep the language-neutral resource coordinator live across the Workflow Bundle staging-name transition discovered during beta.163 verification. Current lease/wait publication still accepts only Machine Bridge's strict `.<target>.<pid>.<16hex>.tmp` grammar. A separate migration reader recognizes the retired Workflow Bundle lease-only `.lease_<id>.json.<8char>.tmp` replacement artifact only when the corresponding published lease is a valid provisional record and PID/process-start evidence proves its publisher reclaimable; live, missing, malformed, mismatched, or extra-link state remains fail-closed. This prevents a killed older Workflow Bundle validator from leaving one zero-byte staging file that makes all later local resource admission unavailable, without broadening the current staging grammar.
|
|
24
|
+
|
|
25
|
+
## 3.0.0-beta.162 - 2026-09-02
|
|
26
|
+
|
|
27
|
+
- Treat the continuing relay interruptions as two evidenced transport classes instead of another host-schema or heartbeat-only failure. One observed outage followed an approximately eighteen-second local event-loop stall and correctly rebuilt a stale WebSocket, while separate abrupt `1006` episodes occurred after recent inbound traffic and correlated with macOS network-extension/SystemConfiguration path re-election. That correlation is evidence of a local network-epoch change, not proof that a particular tunnel, proxy, edge, or upstream provider is faulty. The existing relay already starts exact-generation signed HTTPS takeover immediately on real WebSocket loss, so beta.162 does not add another watchdog or weaken the WebSocket liveness policy.
|
|
28
|
+
- Bound only the pre-ready exact-generation HTTPS takeover request to three seconds while retaining the ordinary seven-second signed-HTTP request deadline, twelve-second liveness window, one-second ready cadence, five-second standby cadence, bounded one/two/four/five-second failure backoff, and 750 ms minimum request-start interval. A stale network epoch can therefore consume at most three seconds on the first takeover request before retry, leaving enough of the Worker's existing fifteen-second new-call recovery window for the first failure backoff plus the two request exchanges required to reach verified readiness. A deterministic regression holds the first takeover request until its full deadline, then requires the replacement channel to become verified-ready inside that same fifteen-second budget. Package/runtime identity advances to beta.162; hosted tool schema generation remains 23 because no MCP tool name, schema, visibility, or result shape changes.
|
|
29
|
+
|
|
30
|
+
## 3.0.0-beta.161 - 2026-09-02
|
|
31
|
+
|
|
32
|
+
- Replace beta.160's render-result fallback with a planner-independent MCP Apps handoff. A prior fresh Work canary had already proved the normal MCP App render/claim path can mount and hand off when the render result is exposed. A separate fresh Work canary then explicitly forbade `structuredContent`, required parsing `ui_monitor_id` only from `render_job_monitor` ordinary text, and ended `TEXT_ID_NOT_AVAILABLE`; that narrower test proves ordinary render-tool text is not a reliable planner-visible fallback, without assuming whether that particular View mount completed. An active MCP Apps-capable `start_job` now pre-issues the fresh non-secret `ui_monitor_id` in its ordinary data result, where the model already receives `job_id` and recovery capabilities. The model passes that exact ID into `render_job_monitor(job_id,recovery_key,ui_monitor_id)` and immediately reuses the same start-result ID for the single confirmation `read_job`; no planner decision depends on any render-tool result projection.
|
|
33
|
+
- Preserve the render-instance safety boundary with an explicit Worker-only lifecycle instead of making pre-issued IDs immediately claimable. A start result creates a principal/job-bound monitor record in `issued` state; `claim_job_monitor` rejects it until `render_job_monitor` verifies the original recovery capability and activates that exact ID. The View receives `job_id`, `recovery_key`, and `ui_monitor_id` from render tool input, then its app-only claim advances the active record to `claimed`. Exact principal/job/ID matching, five-minute bounded retention, authority revocation, capability stripping before daemon dispatch, and the rule that `recovery_key` is the sole read authority are unchanged. Because the render input schema and active start-result contract change, package/runtime identity advances to beta.161 and hosted tool schema generation advances from 22 to 23. beta.160 is superseded before acceptance; it remains useful evidence that neither structured nor text render-result projection is reliable planner input.
|
|
34
|
+
|
|
35
|
+
## 3.0.0-beta.160 - 2026-09-01
|
|
36
|
+
|
|
37
|
+
- Fix the live ChatGPT MCP Apps handoff blocker found after beta.159 activation. Workspace Action refresh successfully published `render_job_monitor`/`claim_job_monitor`, fresh chats mounted the Job Monitor iframe, and the View independently updated to terminal state, but the initiating model did not reliably receive the render tool's `structuredContent.ui_monitor_id`. A controlled 180-second canary therefore reported no `ui_monitor_claimed`, stayed in `bounded_followup`, and ended `CLAIM_NOT_CONFIRMED` even though the View itself was mounted. `render_job_monitor` now mirrors the same fresh non-secret `ui_monitor_id` into its ordinary text content as a planner-compatibility fallback while retaining `structuredContent` as the canonical result. The principal-bound `recovery_key` remains the sole read authority and is never copied into text; claim issuance, View permissions, TTL, and daemon boundaries are unchanged. Regression coverage binds the same ID across structured/text projections at both the Worker domain and real MCP `tools/call` boundary and rejects recovery/control capability leakage. beta.159 had already received a local acceptance record after the first successful fresh-chat canary; the stricter repeat later invalidated that reliability conclusion before any public promotion. This is packaged behavior, so beta.160 supersedes beta.159 for further promotion and package/runtime identity advances to beta.160; hosted tool schema generation remains 22 because tool names, inputs, outputs, visibility, and descriptions are unchanged.
|
|
38
|
+
|
|
39
|
+
## 3.0.0-beta.159 - 2026-09-01
|
|
40
|
+
|
|
41
|
+
- Treat the newly reproduced ChatGPT `message reply timed out` incident as an outer host-turn delivery failure with a Machine Bridge-controlled amplification path, not as another relay outage. Contemporaneous beta.158 evidence kept the same launchd daemon alive with no restart, no matching relay outage, no system sleep, Green resource admission, and zero Worker tool failure/cancellation/timeout for the incident window. At the same time, the active durable workflow was still being followed through successive individually successful 40-second hosted `read_job` long-polls. That composition can keep one assistant response attached to already-durable work until the external host ends the response, even though every MCP call succeeds. Machine Bridge still cannot observe the host's aggregate turn deadline or final-message receipt, so beta.159 does not guess a fixed number of reads/minutes or lengthen the already host-tested per-call wait.
|
|
42
|
+
- Add an MCP Apps managed-job monitor as a capability-gated continuation path for hosted `start_job`, while following the host-recommended data-tool/render-tool split. `start_job` remains the execution/data tool and carries no UI template. On an MCP Apps-capable request, an active result reports `ui_monitor_candidate=true` plus `ui_monitor_render_tool=render_job_monitor` but keeps model-side `follow_up_read_required=true`. The model calls `render_job_monitor(job_id,recovery_key)` once; that Worker-only read-authority check mounts the static `text/html;profile=mcp-app` resource and returns a fresh 128-bit `ui_monitor_id`. The sandboxed View receives the render input/result, completes `ui/initialize`, requires `hostCapabilities.serverTools`, and calls the app-only `claim_job_monitor(job_id,recovery_key,ui_monitor_id)`. The model's next `read_job` passes the same `ui_monitor_id`; only an exact current claim then projects `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=true`, and `follow_up_read_required=false`. The View thereafter owns the existing capability-bound `read_job(...,wait_ms=40000)` continuation. The random render-instance ID is not read authority, but it prevents a short-lived claim from an older widget instance from transferring a later turn. Unsupported hosts, unmounted Views, Views without app-origin server-tool proxying, and already-terminal jobs retain the beta.158 bounded same-response behavior.
|
|
43
|
+
- Preserve the existing hosted authorization boundary while adding UI delivery. `start_job` stays model-visible without a UI template, `render_job_monitor` is model-visible and is the only tool that mounts the Job Monitor resource, `read_job` is model+app visible, and `claim_job_monitor` is app-only. Render, claim, and read all rely on the accepted principal-bound recovery capability; render/claim verify it entirely in the Worker, while `read_job` still verifies and strips both `recovery_key` and the Worker-only `ui_monitor_id` before daemon dispatch. The View receives no bare-ID inventory or direct network path, renders a redacted result, and does not automatically emit a follow-up chat message. Hosted resources/capabilities, the two Worker-only monitor tools, the optional `read_job.ui_monitor_id`, and active managed-job result semantics are public schema changes, so package/runtime identity advances to beta.159 and hosted tool schema generation advances from 21 to 22. beta.158 remains the immutable published/activated artifact and its existing soak evidence cannot authorize beta.159 promotion.
|
|
44
|
+
- Keep process-tree cleanup safety and coverage evidence separate after the first beta.159 full-verification attempt exposed a test-harness coupling. The standalone `managed-jobs:test` passed, while the nested coverage rerun inherited `NODE_V8_COVERAGE` into the detached managed-job runner; under the resulting scheduler pressure, the intentionally fail-closed POSIX ownership probe could not prove the anti-`SIGTERM` descendant still belonged to the original process group, correctly skipped unsafe `SIGKILL`, and the integration fixture then timed out waiting for that descendant to exit. Production ownership proof, the three-second bounded process snapshot contract, and fail-closed PID/PGID-reuse protection remain unchanged. The real process-tree timeout fixture now uses its own manager/job root with nested runner coverage disabled, while the parent test and all other managed-job runners remain instrumented; standalone managed-job integration and the complete critical coverage gate both pass with `managed-job-runner.mjs` still above its required coverage threshold.
|
|
45
|
+
|
|
3
46
|
## 3.0.0-beta.158 - 2026-08-31
|
|
4
47
|
|
|
5
48
|
- Fix a resource-coordinator self-identity race exposed by the exact-main beta.157 source-release gate. Waiter/lease stale-pruning and staging-publisher recovery now treat the running coordinator's own `process.pid` as authoritative through `currentProcessStartTimeMs()` instead of allowing an asynchronous all-process snapshot to override that identity. A generation-skewed external snapshot can therefore no longer delete the acquire call's own waiter and turn an otherwise admitted Green-state request into `fairness_wait`, reclaim a provisional lease owned by the still-running coordinator, or delete a still-live atomic waiter/lease staging file. Foreign PIDs continue to use sampled process-start evidence, so proven PID reuse remains reclaimable and fail-closed liveness semantics are unchanged.
|
package/README.md
CHANGED
|
@@ -195,7 +195,11 @@ For stateful GUI trajectories, owner/full callers can use the higher-level `comp
|
|
|
195
195
|
|
|
196
196
|
## Durable work and local resources
|
|
197
197
|
|
|
198
|
-
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. Hosted durable acceptance returns `job_id`, `recovery_key`, and `control_key`: preserve them together, use the read capability for `read_job` and `depends_on`, and use the control capability for `cancel_job`; a bare job ID is not remote recovery authority. 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` plus its preserved `recovery_key`. 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. The daemon/local `list_jobs.jobs` inventory keeps a 50-record durable-first primary window and `recent_process_recovery` remains capped at 16 additional authority-visible handles for local administration. The hosted Worker projection deliberately removes those job IDs/names/handles and exposes aggregate retained/capacity/activity state only, so one hosted conversation cannot discover another workflow's durable handle through shared owner inventory. Hosted recovery therefore depends on the capability returned by the original acceptance; legacy or lost-capability jobs require local CLI/stdio administration. 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.
|
|
198
|
+
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. Hosted durable acceptance returns `job_id`, `recovery_key`, and `control_key`: preserve them together, use the read capability for `read_job` and `depends_on`, and use the control capability for `cancel_job`; a bare job ID is not remote recovery authority. On an MCP Apps-capable host, an active `start_job` may report `ui_monitor_candidate=true`, `ui_monitor_render_tool=render_job_monitor`, and a fresh random `ui_monitor_id`, but it remains a data/execution tool and carries no UI template. **Job polling ownership is not task ownership:** if remaining same-response work needs that job's terminal result, do not render the monitor merely to shorten the response; preserve `job_id` + `recovery_key` and continue bounded server-paced `read_job` calls. Render the monitor only when the current task can deliberately transfer that job's status polling without needing its terminal result for remaining same-response work. When intentionally rendering, preserve the start-result ID, call `render_job_monitor` once with the exact accepted `job_id` + `recovery_key` + `ui_monitor_id`, and do not depend on the render result being planner-visible. Then issue one model-visible `read_job` with the same three values. A matching claimed View on an ordinary job reports `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=false`, and `follow_up_read_required=false`; those fields suppress duplicate model polling for that job only and do not require ending unrelated or still-actionable assistant work. The sole host-turn exception is an explicitly persisted `continuation_mode=task_supervisor`: only when that durable job's own reviewed plan already encapsulates all remaining noninteractive task work may a later active claimed read report `host_turn_handoff_recommended=true`. Machine Bridge can validate the persisted continuation declaration, active job state, and current monitor claim, but it cannot infer conversation-level task completeness; that completeness remains an owner/caller semantic assertion. That handoff is owned by the durable supervisor, not by the Job Monitor, and grants the View no execution authority. The monitor then uses app-only `read_job_monitor` for fixed 40-second status waits instead of emitting background model-visible `read_job` events. Each app-origin status call has a 60-second response watchdog; missing/transport-failed and explicitly retryable status responses receive at most six automatic retries with 1/2/4/8/15/30-second backoff. Exhausted or non-retryable failures pause for inspection, and no retry resubmits the durable job. An older View claim cannot transfer a newer turn because its monitor ID differs. Unsupported hosts, or hosts that can render MCP Apps but cannot proxy app-origin server tools, retain the existing contract: 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` plus its preserved `recovery_key`. 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 is non-evictable during the fixed 24-hour undelivered-result grace because Machine Bridge explicitly told the caller to follow that durable handle. If the 512-state store is full and every retained slot is non-evictable, the existing typed retryable `limit_exceeded` backpressure rejects the incoming job instead of deleting promised recovery evidence. 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 shorter thirty-minute bounded newest-16 transient delivery reserve because only outer response-delivery uncertainty remains; an undelivered marker older than 24 hours likewise stops hard protection and becomes ordinary eviction material. For relay non-owner one-step process carriers, acceptance additionally caps unresolved pending recovery at 16 records per account ID plus account version across all OAuth clients and refresh families; changing client/family does not reset that fairness bound. The check is serialized by the same root capacity transaction before new status publication or process launch and uses retained job state rather than a separate counter. A securely valid persisted same-key job can still be recovered at quota because it adds no reservation, but an empty/statusless deterministic directory is only an incomplete publication and must pass quota again. Hosted terminal delivery or the existing 24-hour terminal expiry releases the account slot. Owner/local work is not subject to this account quota, and the hosted inventory does not expose raw account-quota counts or principal identifiers. The daemon/local `list_jobs.jobs` inventory keeps a 50-record durable-first primary window and `recent_process_recovery` remains capped at 16 additional authority-visible handles for local administration. The hosted Worker projection deliberately removes those job IDs/names/handles and exposes aggregate retained/capacity/activity state only, so one hosted conversation cannot discover another workflow's durable handle through shared owner inventory. Hosted recovery therefore depends on the capability returned by the original acceptance; legacy or lost-capability jobs require local CLI/stdio administration. 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.
|
|
199
|
+
|
|
200
|
+
The repository-tracked `workflow-bundle.json` is the project-native lifecycle authority for local readiness. Its `project-native:npm:check:fast` and `project-native:npm:check:full` stages must remain `sandbox: required`; a machine-local or untracked lifecycle file must not silently lower high-risk containment. Release architecture checks enforce both Git tracking and those required Seatbelt modes, so the job-local continuation contract above is validated under the same repository-owned verification authority used for the candidate.
|
|
201
|
+
|
|
202
|
+
The Job Monitor is deliberately a status surface, not a duplicate result viewer. Its app-only reads expose only bounded lifecycle state; command output, command text, paths, nested step results, and recovery/control capabilities remain on the normal managed-job result path. The `recovery_key` is present in the render input only because it is the View's read authority and is cleared from View memory on teardown. If optional monitor coordination storage is unavailable after `start_job` or `read_job` has already succeeded, Machine Bridge keeps the durable job result authoritative and falls back to normal model-side continuation rather than reporting a new job failure.
|
|
199
203
|
|
|
200
204
|
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.
|
|
201
205
|
|
package/SECURITY.md
CHANGED
|
@@ -227,6 +227,8 @@ Managed jobs are durability, not sandboxing. Only `owner` may create remote pers
|
|
|
227
227
|
|
|
228
228
|
Hosted managed-job recovery adds a separate Worker capability boundary. `start_job`, `stage_job`, and durable one-step process acceptance return deterministic HMAC-SHA256 `recovery_key` and `control_key` values bound to account version, OAuth client, refresh family, role, job ID, and purpose. Hosted `read_job` and dependency references require the read capability; hosted `cancel_job` requires the control capability. The Worker verifies them before daemon dispatch and strips them from daemon arguments. Capability values are derived with domain separation from the existing Worker OAuth identity key, are not persisted in local job state, are not mirrored into MCP parameter headers, and are covered by ordinary log/privacy redaction. Rotating the OAuth identity key invalidates outstanding hosted capabilities. Hosted `list_jobs` returns aggregate state only, while local CLI/stdio remains the global administration surface. This prevents Machine Bridge inventory from turning one hosted conversation into another conversation's job-discovery authority, but it cannot identify host-side conversation misrouting if the host itself supplies the other conversation's still-valid capability.
|
|
229
229
|
|
|
230
|
+
Delegated `operator` accounts still cannot call `start_job` or `stage_job`, but remote `exec_command`, `run_process`, and `run_local_command` use internal `transient_process` managed-job carriers so an accepted one-step operation remains recoverable after a host boundary. Those carriers share the profile-wide 512-state job store. To keep the 24-hour hard undelivered-result promise from becoming a cross-principal capacity claim, one relay non-owner account version may hold at most 16 unresolved `transient_recovery_pending` carriers across all of its OAuth clients and refresh families. The fairness key is account ID plus account version; changing client or refresh-family identity does not reset it. The check runs inside the existing root `capacity.lock` before new durable status publication or runner launch and derives its count from persisted job truth rather than a second quota ledger. A securely readable valid persisted same-key job remains recoverable at quota because replay adds no reservation; deterministic-directory existence by itself is not replay, and a statusless partial publication must pass the account quota again. Present but unreadable, malformed, or integrity-invalid retained status fails closed. Already accepted pending results remain non-evictable for their full 24-hour grace, while hosted terminal delivery or terminal grace expiry releases the account quota. Owner/local work remains governed only by the existing global capacity contract. Quota errors expose only a bounded maximum and coarse capacity scope, not account/client/family/job identifiers, paths, command text, output, or recovery capabilities.
|
|
231
|
+
|
|
230
232
|
Job directories must remain canonical real children of the owner-only job root. Cancellation markers are atomically replaced owner-only timestamp files; unreadable, malformed, symlinked, or hard-linked markers fail closed instead of being treated as no cancellation.
|
|
231
233
|
|
|
232
234
|
`stage_job` is non-executing and cannot be promoted by a terminal approval command. `machine-mcp job approve` was removed. Execution requires trusted `start_job` authority or an explicit local `machine-mcp job submit PLAN.json` action.
|
|
@@ -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.165",
|
|
34
34
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxryYkpZhq8+VAQLHcGS9BAHQcyKX8RHGIpIwvtIVRU/rcOcE0bNdnM0aZJ/h6xWQsGDHlhvjT2+1aJaAn/9k8473BRWajzVXld961CdHYVFVHoce2hHiSJ0xydWrHMMZhAm0mN0UzjEpgZ0tMw209efcZHIvSwuxhteZMRy4kyiVjwFlOf5oXFCxRuCJnPj3AK9CmCf4XgEBuPIJ0TZmjGHOOdBvJmbCNnAWXYEo5/mf7MfCGhV4IJ1hNuhpoNQfOFKMUcw9/v/IpT62XpfXdGYTfGYCmCjC+gntK1spbkr2P4/2+sYMQtLpse71mpSNGXfcf3abU55Vpn+gncSxRQIDAQAB"
|
|
35
35
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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. 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
|
|
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 while its hosted response still requires follow-up, retention makes it non-evictable under capacity pruning for a fixed 24-hour undelivered-result grace. `managed-job-transient-recovery-capacity.mjs` adds the complementary admission-side fairness boundary for relay non-owner carriers: while the existing root `capacity.lock` serializes the create transaction, the helper derives at most 16 unresolved pending promises for one account ID plus account version directly from retained status, aggregating all OAuth clients and refresh families and adding no second quota ledger. Active pending states count regardless of age; terminal pending states count only through the same 24-hour hard grace. Owner/local work bypasses this account quota and still uses the global 512-state capacity contract. A securely readable valid persisted status for the same deterministic idempotent job may be replayed at quota because it adds no reservation. A deterministic directory with no `status.json` is recognized as incomplete publication rather than replay or an account reservation, so its retry re-enters quota admission; present-but-unreadable, malformed, wrong-identity, or terminal-evidence-invalid status fails closed. 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 shorter thirty-minute 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: pending undelivered transient terminals inside 24 hours are excluded from the removable set, so ordinary removable terminal history may be reclaimed first; if all 512 retained states are active, staged, unreadable, dependency-pinned, retired blockers, or hard-protected undelivered terminals, the existing typed retryable `limit_exceeded` capacity path rejects another job instead of deleting recovery evidence. Terminal delivery clears the pending marker and returns that helper to the newest-16/thirty-minute delivered reserve, while a pending terminal older than 24 hours becomes ordinary removable history. `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
|
|
|
@@ -137,7 +137,7 @@ Public `/healthz`, `/`, discovery metadata, CORS preflight, and unknown-path 404
|
|
|
137
137
|
|
|
138
138
|
MCP `2026-07-28` requests are independent. The Worker validates Origin, authenticates the request, checks body metadata and mirrored HTTP headers, intersects the tool with the account-visible catalog, and validates raw arguments before dispatch. OAuth token plus JSON-RPC request ID is deliberately not a global request key: two requests sharing one token may reuse the same ID concurrently. A streamed call remains owned by its initiating response stream and the ordinary bounded pending-call index. Public abort, response-body cancellation, or failed keepalive delivery sends one random internal stream capability that removes the matching pending call and requests daemon cancellation. Public requests cannot supply that capability because internal headers are stripped, and the control request forwards neither Authorization nor DPoP.
|
|
139
139
|
|
|
140
|
-
Daemon reconnect continuity remains separate from MCP delivery semantics. WebSocket is preferred, but a daemon that loses verified WSS readiness may establish the signed HTTPS fallback with the same ephemeral root-certified P-256 identity and daemon instance. Planned process shutdown is explicitly separated from that accidental transport-loss path: a ready local runtime sends one bounded `daemon_draining` control message before closing the relay, the Worker settles pending calls still owned by that daemon using recovery metadata already attached to each call, and `daemon_draining_ack` lets shutdown proceed without waiting for a socket failure to classify the event. `read_job` carries a read-only same-`job_id` recovery descriptor, while mutations remain fail-closed if their execution settlement is ambiguous. The handshake does not reuse daemon process identity and therefore cannot duplicate execution across old/new daemons. Each fallback POST signs the fixed route/origin/server/version, a 30-second replay nonce/timestamp, and the exact body hash. Both directions carry bounded contiguous transport sequences; an unacknowledged envelope retains its sequence across a response loss, duplicates are discarded before business handling, and sequence gaps fail closed. HTTPS handover is candidate → probing → verified-ready. An explicit same-instance takeover is allowed to retire a Worker-side zombie WSS only after the signed new-session request passes candidate preconditions; a malformed/stale request cannot knock down a healthy incumbent. The daemon reconciles `resume_calls`, processes `ready_ack`, commits local readiness, emits sequenced `https_ready`, and only then emits `resume_calls_ack`; the Worker does not expose the fallback to ordinary dispatch before `https_ready`. This order makes a non-empty `resume_calls_ack.missing_ids` a proof both that the same daemon never owned those call IDs and that the replacement channel is locally ready. While the initiating MCP response still owns settlement and at least one second of the original execution budget remains, the Worker may transparently retransmit exactly that proven-undelivered call with the same call ID, arguments, authority, and a reduced timeout bounded by the original absolute deadline. If redelivery cannot be accepted or too little budget remains, the old safe retryable `unavailable` settlement is retained. Calls present in the active-call or unacknowledged-result ledger, calls from a different daemon instance, and any mutation with ambiguous/post-dispatch settlement are never automatically replayed. This transport continuity never creates MCP session/replay state. A new call arriving with no verified daemon channel has a separate pre-dispatch recovery path: `daemon-ready-waiters.ts` waits at most fifteen seconds and never longer than that call's own execution budget; measured recovery time is deducted by `daemon-recovery-budget.ts` before dispatch while preserving the separate five-second Worker settlement margin. Waiter authority and shared-capacity reservation remain live until the handover reaches its final no-`await` release point: WebSocket readiness first completes old-channel cleanup and runtime-alarm settlement, HTTPS readiness first completes its final alarm settlement, and only then does `notifyReadyDaemon` release the batch. A socket that is already marked ready while waiters still exist is treated as handover-in-progress, so fresh calls join the same waiter set instead of leapfrogging its reserved capacity; waiter timeout returns retryable `unavailable` rather than bypassing a missing final handover notification. Once released, the already-ready path performs capacity check → pending registration → first daemon send synchronously with no intervening `await`.
|
|
140
|
+
Daemon reconnect continuity remains separate from MCP delivery semantics. WebSocket is preferred, but a daemon that loses verified WSS readiness may establish the signed HTTPS fallback with the same ephemeral root-certified P-256 identity and daemon instance. Planned process shutdown is explicitly separated from that accidental transport-loss path: a ready local runtime sends one bounded `daemon_draining` control message before closing the relay, the Worker settles pending calls still owned by that daemon using recovery metadata already attached to each call, and `daemon_draining_ack` lets shutdown proceed without waiting for a socket failure to classify the event. `read_job` carries a read-only same-`job_id` recovery descriptor, while mutations remain fail-closed if their execution settlement is ambiguous. The handshake does not reuse daemon process identity and therefore cannot duplicate execution across old/new daemons. Each fallback POST signs the fixed route/origin/server/version, a 30-second replay nonce/timestamp, and the exact body hash. Both directions carry bounded contiguous transport sequences; an unacknowledged envelope retains its sequence across a response loss, duplicates are discarded before business handling, and sequence gaps fail closed. HTTPS handover is candidate → probing → verified-ready. An explicit same-instance takeover is allowed to retire a Worker-side zombie WSS only after the signed new-session request passes candidate preconditions; a malformed/stale request cannot knock down a healthy incumbent. The daemon reconciles `resume_calls`, processes `ready_ack`, commits local readiness, emits sequenced `https_ready`, and only then emits `resume_calls_ack`; the Worker does not expose the fallback to ordinary dispatch before `https_ready`. This order makes a non-empty `resume_calls_ack.missing_ids` a proof both that the same daemon never owned those call IDs and that the replacement channel is locally ready. While the initiating MCP response still owns settlement and at least one second of the original execution budget remains, the Worker may transparently retransmit exactly that proven-undelivered call with the same call ID, arguments, authority, and a reduced timeout bounded by the original absolute deadline. If redelivery cannot be accepted or too little budget remains, the old safe retryable `unavailable` settlement is retained. Calls present in the active-call or unacknowledged-result ledger, calls from a different daemon instance, and any mutation with ambiguous/post-dispatch settlement are never automatically replayed. This transport continuity never creates MCP session/replay state. A new call arriving with no verified daemon channel has a separate pre-dispatch recovery path: `daemon-ready-waiters.ts` waits at most fifteen seconds and never longer than that call's own execution budget; measured recovery time is deducted by `daemon-recovery-budget.ts` before dispatch while preserving the separate five-second Worker settlement margin. Waiter authority and shared-capacity reservation remain live until the handover reaches its final no-`await` release point: WebSocket readiness first completes old-channel cleanup and runtime-alarm settlement, HTTPS readiness first completes its final alarm settlement, and only then does `notifyReadyDaemon` release the batch. A socket that is already marked ready while waiters still exist is treated as handover-in-progress, so fresh calls join the same waiter set instead of leapfrogging its reserved capacity; waiter timeout returns retryable `unavailable` rather than bypassing a missing final handover notification. Once released, the already-ready path performs capacity check → pending registration → first daemon send synchronously with no intervening `await`. Ordinary standby/ready signed-HTTP exchanges retain the seven-second request deadline, while an exact-generation takeover uses a three-second request deadline until verified readiness. One fully timed-out stale takeover request, the first one-second failure backoff, and two further fully bounded takeover exchanges therefore fit inside the existing fifteen-second new-call recovery window without enlarging the hosted foreground envelope. Owner diagnostics report WebSocket and HTTPS fallback readiness separately. Late results after request cancellation/timeout are acknowledged only so local recovery storage can release them; results from a superseded owner are rejected. `relay-call-recovery.mjs` retains completed results in memory until acknowledgement, but that ownership is not allowed to grow outside the call-capacity model: `relay-recovery-admission.mjs` combines active relay tools with unacknowledged result count under the same 16-total / 14-ordinary / 2-reserved-control algebra. Retained results count conservatively as ordinary ownership, so another ordinary call is rejected before execution once ordinary recovery ownership is full while `diagnose_runtime`/`list_roots` retain reserved capacity. The retained-result store is encapsulated behind `RelayResultRetention`: normal ownership has a separate 16-entry hard ceiling, plus exactly one non-admission emergency slot reserved for an already-executed result that reaches retention only after that invariant has unexpectedly been violated. The emergency slot preserves acknowledgement/reconnect ownership instead of sending an executed result untracked; it does not enlarge admission capacity, and a second overflow is not sent. `relay-result-retention.mjs` also records a monotonic first-retained timestamp and prunes any result still unacknowledged after the 315-second maximum Worker settlement lifetime on the next live relay pulse; reconnect failure keeps its separately shorter grace cleanup. Diagnostics project only aggregate counts. A hibernated WebSocket attachment missing current liveness fields fails closed rather than being interpreted through an old schema.
|
|
141
141
|
|
|
142
142
|
`runtime-alarm.ts` and `runtime-alarm-storage.ts` own transient request deadlines plus both daemon transports' readiness/liveness alarm projection. `daemon-sockets.ts` owns WebSocket role transitions, `daemon-http-channel.ts` owns fallback transport sequencing/readiness, and `daemon-socket-attachment.ts` remains the bounded policy/tool/instance metadata shape shared by transport projections. `daemon-last-observation.ts` owns one in-memory, privacy-bounded last-verified-channel observation and `daemon-registry.ts` wires it across both transports so `server_info.daemon.previous_connection` remains useful while both are offline; that observation contains only transport/timestamps/sanitized relay diagnostics and is never authority, routing, or dispatch state. `runtime-relay-shutdown-drain.mjs` owns the local bounded pre-close drain/ack exchange; `daemon-planned-drain.ts` owns Worker-side planned-drain settlement, while `daemon-ready-messages.ts` remains the common ready-channel dispatcher for terminal result, resume acknowledgement, authority-revocation acknowledgement, and planned-drain dispatch. `mcp-jsonrpc.ts` owns JSON-RPC shape validation and current result/error/tool-result projection. `websocket-protocol.ts` remains WebSocket-specific send/close/rejection plumbing. `OAuthController` owns OAuth-store pruning, registration throttling, authorization submission, account-admin routing, token exchange, access-token verification, and the serialization queue for OAuth mutations. Worker-internal TypeScript imports use explicit `.ts` specifiers and JSON import attributes, so the same modules are directly executable under the pinned Node runtime and bundled by Wrangler.
|
|
143
143
|
|
|
@@ -258,7 +258,9 @@ The default `full` profile passes the complete parent environment. Isolated envi
|
|
|
258
258
|
|
|
259
259
|
`execution-limits.mjs` is the shared source for local tool-call concurrency, one-shot process timeout/stdin/output limits, and process-session count/stdin/output/retention limits. Remote-owner `diagnose_runtime.runtime.execution_guardrails` reports those enforced limits, while local stdio `server_info.runtime.execution_guardrails` exposes the same contract together with explicit `not-enforced` values for CPU quota, memory quota, and network isolation. Public one-shot commands inline at most 32 KiB per stream. When either stream exceeds that preview, the runtime keeps up to 1 MiB per stream in a closed in-memory process session for thirty minutes and returns an `output_session_id`; `read_process` then reads monotonic byte-offset pages. Relay-origin process reads use paced same-response follow-up. `process-session-read.mjs` owns read/wait/output orchestration while `process-session-remote-poll.mjs` owns only hosted projection/cooldown policy, leaving `process-sessions.mjs` focused on session lifecycle and ownership. The async read helper returns its internal plan/output snapshot before hosted projection is finalized; after the await the manager re-checks cancellation, then completes projection synchronously, so cancellation at the Promise boundary is not lost and a child exit there cannot produce `running=false` with stale cooldown metadata. The Worker and daemon cap the actual output/exit blocking wait at one second; a live session that has just consumed a remote blocking read enters a fifteen-second would-block cooldown. A repeated would-block request inside that cooldown is paced inside the same MCP call until output/exit or the cooldown boundary instead of returning an immediate running checkpoint, and the Worker execution budget covers that server-side delay. When `wait_for_exit=true`, ordinary output notifications remain readable but do not release the cooldown stage; only process exit or the monotonic cooldown deadline advances the call to its bounded one-second exit-wait stage. Every live relay-origin read identifies `status_polling_mode=paced_followup` and keeps `host_turn_handoff_recommended=false`; terminal reads identify `status_polling_mode=terminal`. The cooldown fields are explicitly blocking-only (`blocking_poll_throttled`, `next_blocking_poll_after_ms`) so a zero-wait status read is not misrepresented as globally rate-limited. Same-response follow-up is allowed when the task needs additional output or terminal state, but callers must not busy-loop and should respect the reported cooldown. Elapsed wall-clock time is not a host-deadline signal: callers continue bounded follow-up while calls are accepted and hand progress back only after an actual host/tool boundary is observed, external input/authorization is required, or the user explicitly requested a checkpoint. This state is per in-memory process session, does not alter local stdio/CLI wait behavior, and is intentionally a hosted pacing bound rather than durable scheduling. The oldest exited session is evicted before an active session is refused, so continuation retention is explicitly best effort rather than durable. The continuation stores command basename and cwd metadata but not argv or shell text. It is memory-only and disappears on runtime stop or daemon replacement.
|
|
260
260
|
|
|
261
|
-
Resource admission is a separate cooperative boundary, not an OS quota. `resource-foreground-wait.mjs` owns the ordinary process-start wait budget: a one-shot foreground call defaults to 20% of its execution timeout with a two-second floor, ten-second ceiling, and never more than the complete execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait, while relay-origin `start_process` performs one admission attempt without queueing by default so known host pressure is returned before the request-owned response budget is consumed. An explicit configured override remains authoritative for controlled diagnostics/tests. `LocalRuntime` leaves this default unconfigured in production so the services actually apply it, while explicit overrides are validated and capped at thirty minutes. These admission waits occur before process spawn and do not enlarge an outer relay deadline; cancellation propagates through the same coordinator wait. Detached managed-job steps use the shared durable-delivery admission ceiling instead: the runner may wait up to thirty minutes before spawn, child execution timeout starts only after admission succeeds, and persisted job status reports `current_phase=resource_admission` during that pre-spawn interval. Hosted managed-job status supports bounded autonomous follow-up: `managed-job-hosted-status.mjs` owns the relay-only projection, so relay-origin `read_job` reports `status_polling_mode=bounded_followup` plus `host_turn_handoff_recommended=false` for active jobs while terminal reads report `status_polling_mode=terminal`; local job reads retain their prior shape. Worker tool guidance allows a known active job to be followed again in the same assistant response when terminal state is required, while prohibiting busy loops, repeated `list_jobs` substitution, and speculative handoff based only on elapsed wall-clock time. `managed-job-read-wait.mjs` keeps that autonomy out of the host spin loop: the runtime `read_job` handler performs a relay-only 40-second default long-poll with five-second lightweight status-only probes, monotonic deadline accounting, and cancellation checks. Terminal settlement returns on the next bounded probe. Nonterminal status/phase/dependency progress is coalesced until at least the 30-second hosted progress floor (or the caller's explicitly shorter wait), and `current_step` alone is excluded from the hosted wake signature; local explicit waits retain step-progress wake behavior. The public hosted maximum is 60 seconds; the 40-second default remains intentionally separate so ordinary continuation leaves additional host/Worker settlement headroom, while longer jobs continue through another server-paced read of the same capability-bound job rather than one overlong host request. Full runner/recovery reconciliation is bounded to thirty-second intervals inside the advertised wait rather than executing on every lightweight probe. The monotonic wait deadline starts before the initial full read, so initial reconciliation consumes the same per-call budget, and an unchanged timeout returns the latest secure persisted progress state without adding a second heavyweight reconcile after the deadline. Coherent non-interactive command sequences should use a repository umbrella command or a multi-step managed job so local command fan-out does not become host-visible tool-event fan-out; this density control does not shorten the managed task or its six-hour step ceiling. Hosted reads first verify a healthy runner with async process-start identity sampling; `managed-job-hosted-reconcile.mjs` owns that relay-only liveness/recovery decision, and only a non-current/ambiguous runner enters the existing recovery reconciliation path, so a normal long-running job no longer launches synchronous `ps` from the relay event loop every reconcile interval. `ManagedJobManager.read()` remains the local synchronous persistence/ownership projection while relay reads use `readHosted()`; `readProgress()` deliberately omits runner reconciliation and terminal-result projection during unchanged waits. Relay-origin `list_jobs` remains an `inventory` surface and does not recommend handoff, but its hosted projection is aggregate-only: it omits job IDs, names, and `recent_process_recovery` handles. Hosted acceptance returns deterministic HMAC-derived `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and capability purpose. The Worker validates these credentials before daemon dispatch, strips them before constructing the daemon request, requires `recovery_key` for `read_job` and every `depends_on` reference, and requires `control_key` for `cancel_job`. The capabilities are derived from the existing Worker OAuth identity key and are never persisted with local job state; rotating that identity key invalidates outstanding hosted capabilities. Local CLI/stdio administration intentionally retains global owner visibility. This boundary prevents Machine Bridge's own hosted global inventory from causing cross-conversation job discovery, but it cannot distinguish two host conversations if the host itself incorrectly supplies the other conversation's still-valid capability. Ordinary managed-job result/status projection remains separate from this hosted pacing policy. Owner diagnostics separately expose the Boolean `waiters.drain_active`, computed by the same fairness selection state machine, so Green host/resource pressure is not confused with an aged protected waiter intentionally reserving a drain window. A fixed CPU request that exceeds the machine's best-case priority-specific launch window is structurally incapable of becoming admissible while the configured CPU headroom remains in force; it therefore returns non-retryable `cpu_request_exceeds_launch_window` before entering the retry sleep loop instead of masquerading as transient `cpu_pressure_window`. Explicit worker counts/argv are never rewritten and the headroom is not relaxed; elastic/unbounded requests retain their pressure-aware fitting behavior. `resource-command-profile.mjs` classifies known light operations, bounded adaptive unknowns, and known CPU/I/O/mixed build families. `resource-script-classification.mjs` classifies only an actually executed shell/Node/Python script operand or package-manager script name, and `resource-shell-analysis.mjs` owns conservative shell token/segment parsing. This lets direct orchestration roots reserve startup capacity before descendant fan-out without letting an unrelated argument such as a test filename or release note impersonate a heavy script. The arbitrary-process zero-resource allowlist is intentionally identity-bound: `resource-light-command.mjs` accepts only a small set of standard absolute executables for constant/output, process-table, uptime, and sleep probes. Bare PATH-resolved names and every arbitrary shell invocation remain adaptive even when their apparent command is cheap, because executable resolution, startup configuration, repository configuration, path behavior, options, helpers, or input size can change the actual work. Caller-controlled Git, filesystem metadata/query commands, lookup helpers, recursive/search/file-stream processors, `find`, application launchers, and script interpreters therefore never receive zero-resource admission from basename alone. Implementation-owned Git/diagnostic probes use their separate fixed-argv/internal boundary and do not depend on this allowlist. `resource-admission.mjs` persists per-user owner-only leases and waiters outside workspace state; ownership is bound to PID plus process-start identity and, where supported, the isolated process group. Releasing the caller-side lease does not delete a bound POSIX reservation while that isolated process group still exists: the persisted lease remains until ordinary pruning observes the group gone, so a detached descendant cannot become unaccounted merely because its direct caller returned. Independent nested process roots retain their own full durable leases for crash recovery, but live accounting does not blindly add an orchestration envelope and every child reservation. `resource-process-ancestry.mjs` samples the live parent graph through `resource-process-ancestry-cache.mjs`; the async cache coalesces concurrent requests and retains a completed snapshot for one second so admission retries do not repeatedly enumerate the complete process table. If ancestry cannot be sampled, accounting falls back to conservative full summation. `resource-lease-accounting.mjs` forms an ephemeral lease forest whose effective vector is the component-wise maximum of a node's own envelope and the sum of its direct lease children. A pending nested request is charged only for the additional vector it contributes to that forest. Same-key contention is exempted only for the request's actual ancestor lease chain; siblings and unrelated roots still serialize.
|
|
261
|
+
Resource admission is a separate cooperative boundary, not an OS quota. `resource-foreground-wait.mjs` owns the ordinary process-start wait budget: a one-shot foreground call defaults to 20% of its execution timeout with a two-second floor, ten-second ceiling, and never more than the complete execution timeout. Owner-local process-session startup defaults to a ten-second cooperative wait, while relay-origin `start_process` performs one admission attempt without queueing by default so known host pressure is returned before the request-owned response budget is consumed. An explicit configured override remains authoritative for controlled diagnostics/tests. `LocalRuntime` leaves this default unconfigured in production so the services actually apply it, while explicit overrides are validated and capped at thirty minutes. These admission waits occur before process spawn and do not enlarge an outer relay deadline; cancellation propagates through the same coordinator wait. Detached managed-job steps use the shared durable-delivery admission ceiling instead: the runner may wait up to thirty minutes before spawn, child execution timeout starts only after admission succeeds, and persisted job status reports `current_phase=resource_admission` during that pre-spawn interval. Hosted managed-job continuation has two paths, and the MCP Apps path follows an explicit data-tool/render-tool split. When a request advertises the standard MCP Apps `text/html;profile=mcp-app` capability, an active `start_job` remains data-only and reports `ui_monitor_candidate=true`, `ui_monitor_claim_required=true`, `ui_monitor_render_tool=render_job_monitor`, and a fresh 128-bit `ui_monitor_id`; model-side `follow_up_read_required` remains true. Issuance is latest-wins for the same job/principal: a new monitor record retires older issued/active/claimed records for that authority, so a stale View cannot keep opening new automatic status reads after a later render instance takes over. The bounded issued/active/claimed registry is transactionally stored in the BridgeRoom Durable Object rather than isolate-local memory. Its schema contains only job/principal/monitor/state/expiry/ordering metadata, never a `recovery_key` or job result; therefore a Worker isolate replacement between `render_job_monitor` and the View's claim can reconstruct the exact activated record without weakening authorization. The monitor is optional because job polling ownership is not task ownership. If the current assistant still has task-local work that depends on this job's terminal result, it does not render the monitor merely to shorten the response; it preserves `job_id` + `recovery_key` and continues bounded server-paced `read_job` calls. Only when the current task can deliberately transfer this job's status polling without needing its terminal result for remaining same-response work does the model preserve the new ID and call `render_job_monitor(job_id,recovery_key,ui_monitor_id)` once. That Worker-only read-authority check is the sole tool carrying the Job Monitor resource metadata and activates only the exact pre-issued monitor record; it does not generate a replacement ID, and the model does not depend on either structured or text output from the render tool. The sandboxed View receives `job_id`, `recovery_key`, and `ui_monitor_id` from the render tool input, bounds `ui/initialize` to a 30-second response watchdog, requires `hostCapabilities.serverTools`, and calls the app-only `claim_job_monitor(job_id,recovery_key,ui_monitor_id)`. The claim validates the existing principal-bound read capability and requires an activated monitor record before storing short-lived claimed polling evidence; the recovery capability and result are not stored, and the claim never reaches the daemon. The model's next `read_job` passes the same `ui_monitor_id` already obtained from `start_job`, which the Worker strips before daemon dispatch. Only a claim whose five-minute freshness window is still current projects `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, and model-side `follow_up_read_required=false`. For an ordinary job it keeps `host_turn_handoff_recommended=false` and suppresses duplicate model-side polling for this job only. For an explicitly persisted `continuation_mode=task_supervisor` whose reviewed durable plan already encapsulates every remaining noninteractive task action, the same active claimed projection may set `host_turn_handoff_recommended=true`; Machine Bridge can validate that finite declaration, its persisted active state, and the current claim but cannot infer conversation-level completeness, which remains an owner/caller semantic assertion; execution ownership remains in the durable supervisor job, not the View, and no new capability authority is created; the View uses app-only `read_job_monitor`, which verifies the same principal-bound recovery capability and also requires that monitor ID to remain the latest claimed View before reducing the request to a fixed 40-second daemon `read_job` with no recovery/correlation values crossing that boundary. Each accepted app read refreshes the five-minute handoff-freshness window. If the host suspends the View or the machine sleeps past that interval, the expired freshness no longer proves model-side handoff, but the still-latest claimed record remains bounded in durable Worker state and the same View may resume app polling with its recovery capability; a newer issuance deletes the old record and therefore blocks resumed polling from the replaced View. The `recovery_key` remains the data-read authority; `ui_monitor_id` is only continuation-instance gating. A replacement can race with one old read already dispatched to the daemon, but no subsequent read from that old View is admitted. Each app-origin status call has a 60-second client response watchdog. A missing/transport-failed or explicitly retryable app-read response is retried in the View for at most six attempts with bounded 1/2/4/8/15/30-second backoff; exhausting those attempts or receiving a non-retryable error pauses instead of looping, and no retry path resubmits the managed job. Claimed records remain bounded by the existing 128-record durable monitor registry, latest-wins replacement, oldest-record eviction, and authority revocation. The View still has no direct network domains or weaker job lookup. Unsupported hosts, and UI hosts without proven `serverTools`, retain bounded autonomous model follow-up: `managed-job-hosted-status.mjs` owns the relay-only projection, so relay-origin `read_job` reports `status_polling_mode=bounded_followup` plus `host_turn_handoff_recommended=false` for active jobs while terminal reads report `status_polling_mode=terminal`; local job reads retain their prior shape. Worker tool guidance allows a known active job to be followed again in the same assistant response when terminal state is required, while prohibiting busy loops, repeated `list_jobs` substitution, and speculative handoff based only on elapsed wall-clock time. `managed-job-read-wait.mjs` keeps that autonomy out of the host spin loop: the runtime `read_job` handler performs a relay-only 40-second default long-poll with five-second lightweight status-only probes, monotonic deadline accounting, and cancellation checks. Terminal settlement returns on the next bounded probe. Nonterminal status/phase/dependency progress is coalesced until at least the 30-second hosted progress floor (or the caller's explicitly shorter wait), and `current_step` alone is excluded from the hosted wake signature; local explicit waits retain step-progress wake behavior. The public hosted maximum is 60 seconds; the 40-second default remains intentionally separate so ordinary continuation leaves additional host/Worker settlement headroom, while longer jobs continue through another server-paced read of the same capability-bound job rather than one overlong host request. Full runner/recovery reconciliation is bounded to thirty-second intervals inside the advertised wait rather than executing on every lightweight probe. The monotonic wait deadline starts before the initial full read, so initial reconciliation consumes the same per-call budget, and an unchanged timeout returns the latest secure persisted progress state without adding a second heavyweight reconcile after the deadline. Coherent non-interactive command sequences should use a repository umbrella command or a multi-step managed job so local command fan-out does not become host-visible tool-event fan-out; this density control does not shorten the managed task or its six-hour step ceiling. Hosted reads first verify a healthy runner with async process-start identity sampling; `managed-job-hosted-reconcile.mjs` owns that relay-only liveness/recovery decision, and only a non-current/ambiguous runner enters the existing recovery reconciliation path, so a normal long-running job no longer launches synchronous `ps` from the relay event loop every reconcile interval. `ManagedJobManager.read()` remains the local synchronous persistence/ownership projection while relay reads use `readHosted()`; `readProgress()` deliberately omits runner reconciliation and terminal-result projection during unchanged waits. Relay-origin `list_jobs` remains an `inventory` surface and does not recommend handoff, but its hosted projection is aggregate-only: it omits job IDs, names, and `recent_process_recovery` handles. Hosted acceptance returns deterministic HMAC-derived `recovery_key` and `control_key` capabilities bound to account version, OAuth client, refresh family, role, job ID, and capability purpose. The Worker validates these credentials before daemon dispatch, strips them before constructing the daemon request, requires `recovery_key` for `read_job` and every `depends_on` reference, and requires `control_key` for `cancel_job`. The capabilities are derived from the existing Worker OAuth identity key and are never persisted with local job state; rotating that identity key invalidates outstanding hosted capabilities. Local CLI/stdio administration intentionally retains global owner visibility. This boundary prevents Machine Bridge's own hosted global inventory from causing cross-conversation job discovery, but it cannot distinguish two host conversations if the host itself incorrectly supplies the other conversation's still-valid capability. Ordinary managed-job result/status projection remains separate from this hosted pacing policy. Owner diagnostics separately expose the Boolean `waiters.drain_active`, computed by the same fairness selection state machine, so Green host/resource pressure is not confused with an aged protected waiter intentionally reserving a drain window. A fixed CPU request that exceeds the machine's best-case priority-specific launch window is structurally incapable of becoming admissible while the configured CPU headroom remains in force; it therefore returns non-retryable `cpu_request_exceeds_launch_window` before entering the retry sleep loop instead of masquerading as transient `cpu_pressure_window`. Explicit worker counts/argv are never rewritten and the headroom is not relaxed; elastic/unbounded requests retain their pressure-aware fitting behavior. `resource-command-profile.mjs` classifies known light operations, bounded adaptive unknowns, and known CPU/I/O/mixed build families. `resource-script-classification.mjs` classifies only an actually executed shell/Node/Python script operand or package-manager script name, and `resource-shell-analysis.mjs` owns conservative shell token/segment parsing. This lets direct orchestration roots reserve startup capacity before descendant fan-out without letting an unrelated argument such as a test filename or release note impersonate a heavy script. The arbitrary-process zero-resource allowlist is intentionally identity-bound: `resource-light-command.mjs` accepts only a small set of standard absolute executables for constant/output, process-table, uptime, and sleep probes. Bare PATH-resolved names and every arbitrary shell invocation remain adaptive even when their apparent command is cheap, because executable resolution, startup configuration, repository configuration, path behavior, options, helpers, or input size can change the actual work. Caller-controlled Git, filesystem metadata/query commands, lookup helpers, recursive/search/file-stream processors, `find`, application launchers, and script interpreters therefore never receive zero-resource admission from basename alone. Implementation-owned Git/diagnostic probes use their separate fixed-argv/internal boundary and do not depend on this allowlist. `resource-admission.mjs` persists per-user owner-only leases and waiters outside workspace state; ownership is bound to PID plus process-start identity and, where supported, the isolated process group. Releasing the caller-side lease does not delete a bound POSIX reservation while that isolated process group still exists: the persisted lease remains until ordinary pruning observes the group gone, so a detached descendant cannot become unaccounted merely because its direct caller returned. Independent nested process roots retain their own full durable leases for crash recovery, but live accounting does not blindly add an orchestration envelope and every child reservation. `resource-process-ancestry.mjs` samples the live parent graph through `resource-process-ancestry-cache.mjs`; the async cache coalesces concurrent requests and retains a completed snapshot for one second so admission retries do not repeatedly enumerate the complete process table. If ancestry cannot be sampled, accounting falls back to conservative full summation. `resource-lease-accounting.mjs` forms an ephemeral lease forest whose effective vector is the component-wise maximum of a node's own envelope and the sum of its direct lease children. A pending nested request is charged only for the additional vector it contributes to that forest. Same-key contention is exempted only for the request's actual ancestor lease chain; siblings and unrelated roots still serialize.
|
|
262
|
+
|
|
263
|
+
The monitor continuation path has an explicit data-minimization boundary after daemon dispatch. The daemon still executes the ordinary capability-checked `read_job`, but the Worker immediately converts that result into a fixed lifecycle-status allowlist before returning from app-only `read_job_monitor`; stdout/stderr, argv/command text, paths, nested step/result objects, capabilities, and unknown fields do not enter the View. The View's initial render input is the sole source of `job_id`, `ui_monitor_id`, and `recovery_key`; later ambient tool-result notifications cannot retarget it, and teardown erases that in-memory authority plus outstanding response timers. Monitor Durable Object storage is continuation metadata rather than business-result authority: an issuance or claim-probe storage failure after a durable result exists falls back to non-UI/model continuation, while monitor cleanup failure during authority revocation is logged by bounded operation/error class and does not block the revocation itself. The persisted registry additionally validates unique monitor IDs, unique ordering sequences, and claim sequences no newer than the registry generation.
|
|
262
264
|
|
|
263
265
|
Live host-pressure observation is deliberately asynchronous-only. `resource-probe-command.mjs` owns bounded `execFile` child-probe transport and intentionally exposes no synchronous `spawnSync` path; `resource-host-darwin.mjs` runs Darwin memory, VM, disk, and thermal probes concurrently, `resource-process-ancestry.mjs` samples the parent graph asynchronously, and `resource-host-snapshot.mjs` composes those results without blocking the daemon event loop on `ps`, `memory_pressure`, `vm_stat`, `iostat`, or `pmset`. The previous sync host/Darwin/process-parent samplers and sync ancestry cache are removed rather than retained as test-only APIs that a future runtime path could accidentally call. Process-start identity used when binding a spawned lease is also sampled asynchronously before the coordinator file lock is taken. Lease/waiter pruning uses a separate one-second cached async process-start snapshot collected outside the transaction lock; while holding the lock it performs only PID liveness plus in-memory generation comparison. The currently executing coordinator is authoritative for its own generation and uses `currentProcessStartTimeMs()` when a persisted waiter/lease owner or atomic staging publisher PID equals `process.pid`; the external all-process snapshot remains generation authority only for foreign owners. Missing foreign snapshot evidence fails closed for a live PID, while a proven foreign generation mismatch remains reclaimable before isolated process-group liveness can preserve the lease. This prevents a still-running coordinator from self-pruning its own waiter or provisional lease, or deleting its own in-flight waiter/lease staging file, because of an external process-table generation discrepancy. Staging from an older same-PID generation remains reclaimable because its file timestamp predates the current process-start identity. The expired beta.104 transaction-owner directory migration path is no longer executable; unsupported legacy transaction-lock directories are retained unchanged and fail closed so stale migration code cannot become a destructive recovery surface. `resource-host-cache.mjs` treats a same-project general host snapshot as fresh for 500 milliseconds while retaining successful I/O evidence for at most five seconds; a stale general sample can therefore refresh cheap CPU/memory/load data without waiting for another one-second `iostat` interval, but an older throughput peak is not copied into a current quick sample. Timestamps represent sample completion rather than sample start. `resource-admission-policy.mjs` evaluates effective CPU, memory, I/O, disk-reserve, startup-window, and host-pressure values without imposing global serialism. macOS `iostat -Id` exposes transfer and throughput activity, not device utilization/saturation; fresh high IOPS/MB-per-second evidence therefore tightens I/O capacity as Yellow pressure but cannot by itself mark the host Red. Red remains reserved for direct critical evidence such as thermal/memory/disk-headroom failure or a severe load backlog corroborated by current CPU/I/O evidence. Linux additionally uses `MemAvailable` and optional PSI avg10 observations through `resource-host-linux.mjs`; sustained `psi_io_full_avg10 >= 60` is direct critical I/O-stall evidence and therefore Red, while lower I/O PSI remains a Yellow capacity-throttling signal. The soft free-disk floor is `min(80 GiB, max(8 GiB, 15% of volume))`; the post-reservation hard floor is `min(50 GiB, max(5 GiB, 10% of volume))`. Disk-only hard-red pressure has one narrow self-recovery exception: only an internally classified direct standard absolute deletion executable with the exact small `disk-reclaim` envelope may enter using Yellow capacity limits, and only when `disk_free_headroom_critical` is the sole critical reason. PATH-resolved or shell-composed deletion cannot claim that class, and thermal, memory, PSI, CPU/load, or other Red evidence still blocks it. Diagnostics expose observed busy CPU next to reserved CPU and their ratio as a mismatch signal, not as process attribution. `resource-admission-diagnostics.mjs` owns the privacy-safe snapshot/check projection, while `resource-admission-diagnostic-error.mjs` separately classifies bounded transaction/staging-lock contention as retryable coordinator-busy snapshot unavailability instead of inflating the diagnostics module or mislabeling contention as process execution failure. `resource-waiters.mjs` normally skips an older request that does not fit, but a sufficiently aged rank-zero waiter blocked by coordinator-owned project/CPU/I/O/memory capacity can enter a protected drain phase if it is structurally feasible after current leases disappear. Fixed implementation-owned probes bypass this coordinator so diagnostics/recovery remain usable during resource pressure.
|
|
264
266
|
|
|
@@ -266,7 +268,7 @@ The same protocol is intentionally language-neutral: compatible workflow control
|
|
|
266
268
|
|
|
267
269
|
`resource-transaction-lock.mjs` publishes current `transaction.lock` ownership as one complete-before-visible owner-state regular file using the same exclusive-file primitive as other owner-state locks. The record carries a fixed `resource-coordinator` purpose, random ownership token, PID, and process-start identity; release and stale-file reclamation remove only a record whose token/purpose and secure-file identity still match. The beta.104 directory reader, owner-publication staging recovery, quarantine/restore path, and their migration-only process-identity branches have been removed because beta.104 is no longer inside the supported immediately-preceding/live transition. Encountering the obsolete `transaction.lock/owner.json` directory shape now fails closed without modifying it and directs the operator to preserve the state and use a supported upgrade transition or rollback snapshot. Current regular-file claims still wait for a live owner, reclaim only a proven stale matching owner, reject persistent multiple hard links, and keep transaction waiting operation-bounded: admission uses only the remaining caller admission budget, capped at 30 seconds, while spawned-process lease bind/release ownership settlement may wait up to 30 seconds and managed-job background admission retains its longer outer wait.
|
|
268
270
|
|
|
269
|
-
`resource-staging-recovery.mjs` is the narrow exception to the lease/wait directories' final-name-only rule: it understands only the exact temporary naming emitted by the coordinator's current lease/wait atomic-file helpers. Lease/waiter recovery handles a committed two-hard-link alias or a dead-publisher single-link uncommitted replacement after identity/link-count rechecks. A staging inode owned by a still-current publisher is classified as `MBM_RESOURCE_STAGING_BUSY`: `ResourceCoordinator.withLock()` releases the transaction generation and performs at most four five-millisecond retries so an ordinary concurrent complete-before-visible publication can settle, while a staging owner that remains live after that fixed budget still fails closed. Process admission maps the exhausted busy classification to the same retryable resource-unavailable surface as transaction-lock contention instead of exposing an internal error. No transaction-owner staging grammar remains
|
|
271
|
+
`resource-staging-recovery.mjs` is the narrow exception to the lease/wait directories' final-name-only rule: it understands only the exact temporary naming emitted by the coordinator's current lease/wait atomic-file helpers. Lease/waiter recovery handles a committed two-hard-link alias or a dead-publisher single-link uncommitted replacement after identity/link-count rechecks. A staging inode owned by a still-current publisher is classified as `MBM_RESOURCE_STAGING_BUSY`: `ResourceCoordinator.withLock()` releases the transaction generation and performs at most four five-millisecond retries so an ordinary concurrent complete-before-visible publication can settle, while a staging owner that remains live after that fixed budget still fails closed. Process admission maps the exhausted busy classification to the same retryable resource-unavailable surface as transaction-lock contention instead of exposing an internal error. `resource-legacy-staging-recovery.mjs` is a separate migration-only reader for the pre-alignment Workflow Bundle lease replacement name `.lease_<32hex>.json.<8char>.tmp`; it removes that retired artifact only when the published lease proves a reclaimable provisional publisher, and rejects live or unprovable ownership. Keeping this grammar outside the current recovery module prevents migration compatibility from becoming a second accepted publication protocol. No transaction-owner staging grammar remains after the beta.104 transition reader expired.
|
|
270
272
|
|
|
271
273
|
`resource-build-root.mjs` assigns stable per-project build caches only for toolchains with documented controls. Cargo receives `CARGO_TARGET_DIR`; direct SwiftPM receives `--scratch-path`; direct Xcode receives `-derivedDataPath`. The build-cache partition uses the same canonical anonymous project hash as coordinator diagnostics, so symlink and platform path aliases cannot create duplicate cache identities. Compiler job limits are injected only when the caller did not already choose one; they are fan-out hints and startup reservations, not an OS-enforced ceiling on descendants. Shell text is not rewritten to smuggle path flags: a conservative literal `cd` chain may refine the resource project root even when the later executable is generic, so sibling repositories do not inherit a false common-parent contention key. Dynamic, quoted, or otherwise ambiguous directory changes are left unguessed and fall back to the last provable base. `resource-process-priority.mjs` additionally wraps POSIX background heavy roots in `nice +5`; this is work-conserving scheduling preference rather than a quota, so background work may still use idle CPU while yielding more readily to interactive and control-plane work. Ordinary and interactive roots are not reniced.
|
|
272
274
|
|
package/docs/AUDIT.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Security and privacy audit notes
|
|
2
2
|
|
|
3
|
+
## beta.165 Windows release-gate correction — 2026-09-06
|
|
4
|
+
|
|
5
|
+
**Observed provider evidence invalidated beta.164 before source publication.** PR #126's exact `68ed189dbad00d1adcb34c36b765b8db12673407` head reached a fresh Windows platform run and failed `publish-npm:test` after about 5.4 seconds because the publication hard timeout returned unconfirmed tree settlement rather than the required explicit `ETIMEDOUT`/`SIGKILL` result. Historical comparison was then restricted to causal evidence: PR #124's exact `b8fa7c382f3a13c1d54e56bedecd7142db31828f` Windows job failed the same unchanged assertion after about 5.3 seconds. The later `ca97669` edit renamed PowerShell variables but did not remove the failure mechanism. These are distinct from local macOS verification and from the already activated beta.164 candidate; neither can substitute for the required hosted Windows acceptance gate.
|
|
6
|
+
|
|
7
|
+
**The causal regression was an extra post-success proof step, not a demonstrated need for a longer publication deadline.** Beta.138 had already defined the Windows hard-settlement contract as launching `taskkill.exe /PID <pid> /T /F` and waiting for that helper's own terminal `close(0)` inside the bounded five-second barrier, with spawn failure, nonzero exit, or timeout remaining fail-closed. Its unchanged resistant-descendant publication test is the end-to-end provider proof that descendants cannot continue ordinary execution after the caller reports settlement. `b8fa7c3` later inserted a PowerShell/CIM descendant-enumeration sweep only after successful `taskkill` completion, but that implementation and its coupled deterministic test never obtained a green hosted Windows publication regression. beta.165 removes only that extra sweep and restores the audited beta.138 barrier; it does not increase the five-second budget, weaken `EUNSETTLED`, or alter the real resistant-descendant test.
|
|
8
|
+
|
|
9
|
+
**A separate beta.164 CI portability repair exposed a CodeQL test defect rather than a production finding.** Windows reports different permission-mode semantics after `chmod(0640)`, so the Worker-types rollback test correctly stopped hard-coding one POSIX mode value. Its first repair then read file content and mode through separate pathname operations; fresh exact-head CodeQL rejected seven such call sites as `js/file-system-race`. beta.165 keeps the cross-platform assertion but snapshots bytes and metadata through one opened file descriptor, so the test verifies one filesystem object generation without adding a SARIF exception. The existing accepted production shell-boundary finding is unchanged.
|
|
10
|
+
|
|
11
|
+
**Release consequence.** `src/local/process-tree-force-settlement.mjs` ships in the npm package, so this is a packaged-byte change after beta.164 activation and acceptance. Repository policy therefore forbids reusing beta.164 evidence: beta.165 removes `release-acceptance/v3.0.0-beta.164.json` from candidate source and requires a new frozen full verification, exact candidate, install-only preflight, persistent activation, activated-package OAuth canary, live Machine Bridge verification, and beta.165 acceptance before another push. Hosted tool schema generation remains 26 because no hosted MCP contract changes.
|
|
12
|
+
|
|
13
|
+
## beta.164 continuity lifecycle correction — 2026-09-04
|
|
14
|
+
|
|
15
|
+
The repeated interruption class was traced past the beta.163 job-local monitor fix: an ordinary managed job can be durable while the model still depends on its terminal result for later planning, implementation, verification, or review, so forcing that complete workflow to live inside one external assistant response remains structurally fragile. beta.164 adds a narrow persisted `continuation_mode=task_supervisor` authority for the distinct case where the durable job's own reviewed plan already encapsulates all remaining noninteractive task work. Ordinary jobs retain generation-25 semantics and a claimed View keeps `host_turn_handoff_recommended=false`; only an active task supervisor plus a proven current View claim may recommend host-turn handoff. The View remains status-only; the supervisor job, not the monitor, owns execution. Machine Bridge can prove the finite persisted declaration, active state, and current View claim, but cannot infer whether the durable plan semantically covers every remaining conversation-level action; that completeness remains an owner/caller assertion and must not be reinterpreted as a server proof.
|
|
16
|
+
|
|
17
|
+
A second live failure showed that a relay-origin helper terminal result could still be lost after the existing thirty-minute private pending-recovery grace when the shared 512-state store was saturated. The first beta.164 repair extended the pending window to 24 hours but independent review found that the implementation still treated those records as merely higher-priority eviction candidates: a store containing only promised pending terminals could still remove the oldest one and recreate `not_found`. The corrected policy excludes a valid `transient_recovery_pending=true` terminal from capacity eviction for the full 24-hour window. If all 512 retained states are non-evictable, admission returns the existing typed retryable `limit_exceeded` backpressure instead of deleting promised recovery evidence. Producing a terminal hosted response clears the marker and returns the helper to the existing thirty-minute/newest-16 post-delivery reserve; expiry beyond 24 hours likewise returns it to removable history.
|
|
18
|
+
|
|
19
|
+
A post-implementation security re-review then found a separate cross-principal availability interaction that the green functional/full suites had not exercised. Explicit persistent plans remain owner-only, but delegated `operator` process calls are intentionally wrapped in durable `transient_process` jobs for recovery, and every account shares the same profile-wide 512-state job root. Once undelivered terminal helpers became hard non-evictable for 24 hours, one restricted account could accumulate many accepted recovery promises and deny durable-job admission to other principals. A per-client limit would not close the boundary because one account may authorize multiple OAuth clients and refresh families. beta.164 therefore adds an admission-side ceiling of 16 unresolved relay non-owner pending-recovery carriers per account ID plus account version, aggregated across client/family identity and serialized by the existing root `capacity.lock` before new status publication or runner launch. The count is reconstructed from persisted job truth rather than stored in a second ledger. Active pending state counts immediately; terminal pending state counts only through the existing 24-hour hard grace. Delivery-marker clearing or terminal grace expiry releases the account slot, while every already accepted promise remains hard-protected for the advertised period. Owner/local work keeps the pre-existing global capacity semantics.
|
|
20
|
+
|
|
21
|
+
The same review challenged idempotent crash recovery rather than equating directory existence with a committed reservation. A securely readable valid persisted same-key job is quota-exempt at 16/16 because replay adds no reservation. A deterministic directory left behind before `status.json` publication is only incomplete create state: it carries no account recovery promise, does not consume that account quota, and a later retry must pass quota again. Present but unreadable, malformed, wrong-identity, or incoherent terminal evidence instead fails classification closed. Quota rejection is privacy-bounded to the numeric maximum and coarse `account_pending_transient_recovery` scope; no account/client/family/job identifier, path, argv/output, or recovery/control capability is projected. This finding is also a reminder that a green full suite is not sufficient security evidence when a new retention guarantee composes with an unchanged delegated-authority path; the final review surface must include both sides of that composition.
|
|
22
|
+
|
|
23
|
+
A repeated frozen full verification exposed a separate ResourceCoordinator race rather than a managed-job continuation defect. `bindLease()` atomically replaces persisted lease ownership while a concurrent secure reader verifies path identity; the legal sequence “open old generation, replace pathname, then revalidate” can therefore produce `MBM_IDENTITY_CHANGED`. ResourceCoordinator had no bounded generation retry even though managed-job state and host-sample readers already used that security-compatible pattern. beta.164 centralizes the rule in one small resource-state JSON reader: retry only `MBM_IDENTITY_CHANGED` for at most four observations, keep optional `ENOENT` semantics explicit, and fail immediately for permission, symlink, multiple-hard-link, malformed-content, or other errors; persistent identity churn still fails on the fourth observation. Host-sample and coordinator paths share the helper, and the main resource-admission module is reduced rather than having its architecture ceiling raised.
|
|
24
|
+
|
|
25
|
+
Release lineage is reset at this boundary. The beta.163 acceptance record was re-recorded after `ca97669` changed packaged files, but repository release policy requires a new prerelease identity whenever a post-acceptance repair changes packaged bytes. beta.164 therefore removes `release-acceptance/v3.0.0-beta.163.json` from the candidate source tree and does not reuse or re-record beta.163 acceptance as authority for the new bytes. Historical beta.163 observations remain historical evidence only; beta.164 requires fresh frozen verification, candidate preparation and activation, candidate-bound live evidence, and its own acceptance record before source publication.
|
|
26
|
+
|
|
27
|
+
Package identity advances to beta.164 and hosted tool schema generation to 26. Historical beta.163/generation-25 observations below remain unchanged.
|
|
28
|
+
|
|
29
|
+
## 2026-09-04 beta.163 task-level continuation correction
|
|
30
|
+
|
|
31
|
+
**Repeated short assistant runs were caused by a Machine Bridge-owned semantic overreach, not by an observed time or context-budget exhaustion.** A claimed MCP Apps View legitimately proved that it could continue polling one durable managed job, but `projectManagedJobMonitorHandoff()` projected that job-local fact as `host_turn_handoff_recommended=true`. Hosted `start_job` / `read_job` / `render_job_monitor` descriptions and the repository automation contract then instructed the initiating model to finish its whole assistant response once that flag appeared. The Worker cannot know whether the assistant still has code edits, verification, release work, or other task-local actions that depend on the job's terminal result, so job polling authority was incorrectly being treated as task ownership. This explains why an assistant could stop after only a short amount of productive work even though Machine Bridge calls were still accepted and neither a real host boundary nor an explicit user checkpoint had occurred.
|
|
32
|
+
|
|
33
|
+
**The corrected invariant is job-local and mechanically enforced: job polling ownership is not task ownership.** A claimed monitor may project `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, and `follow_up_read_required=false`, but it now keeps `host_turn_handoff_recommended=false`. Tool guidance and repository standards prohibit rendering the monitor merely to shorten a response when remaining same-response work still needs that job's terminal result; in that case the model retains the accepted `job_id` + principal-bound `recovery_key` and continues bounded server-paced `read_job` follow-up while calls remain available. A monitor is eligible only when the current task can deliberately transfer that job's status polling without needing its terminal result for remaining same-response work. A successful claim suppresses duplicate model polling for that job only and never instructs the assistant to abandon unrelated or still-actionable work.
|
|
34
|
+
|
|
35
|
+
**The same investigation separated two Workflow Bundle control-plane failures from beta.163 product behavior.** First, a real outer Workflow Bundle Seatbelt plus inner `preferred` Seatbelt reproduced `sandbox-exec: sandbox_apply: Operation not permitted` before the inner command started; `preferred` now recognizes only that exact harmless preflight failure as unavailable nested containment while `required` remains fail-closed. Second, documentation smoke wrapped `validate-profile` and `verify` in a 300-second outer subprocess deadline even though their controller-owned inner work can independently spend up to 300 seconds in pre-spawn resource admission and then consume its command execution budget. The documentation harness now builds the runner wheel offline with `--no-build-isolation --no-index`, installs only that local wheel into the fresh venv, and gives controller phases a 1500-second carrier budget. The resulting real documentation smoke completed all 16 commands with `report_ready=true`; the planning regression also completed 10/10 once it actually reached execution. These observations are control-plane evidence, not a reason to weaken beta.163 sandbox or resource policy.
|
|
36
|
+
|
|
37
|
+
**Release consequence.** The task-continuation result semantics and host-visible tool descriptions change again within the still-unpublished beta.163 candidate, so hosted tool schema generation advances from 24 to 25. Historical beta.159/beta.162 audit statements that observed `host_turn_handoff_recommended=true` remain unchanged because they record what those candidates actually emitted; generation 25 supersedes that interpretation for the current candidate. Fresh frozen verification and the normal candidate/activation/live-publication gates are required before any readiness claim.
|
|
38
|
+
|
|
39
|
+
## 2026-09-02 beta.163 final local verification and interruption classification
|
|
40
|
+
|
|
41
|
+
**The first current-candidate full gate did not fail a project test; its one-step execution carrier hit its own 600-second ceiling.** `npm run check:full` completed tasks 1–127 of 131 successfully and had started `install:test` at task 128 when the managed one-step helper reached about 604 seconds and was terminated with SIGTERM / `error_class=timeout`. Treating that terminal helper record as a test failure would conflate carrier lifetime with the child verification result.
|
|
42
|
+
|
|
43
|
+
**The same candidate was then verified through a long-lived durable managed job with a 21,600-second step budget.** That run completed all 131/131 full-verification tasks with exit code 0 and reported `full verification plan passed in 681.2s`; notable long stages included `managed-jobs:test` at about 179.7 seconds, `coverage:test` at about 273.4 seconds, `worker:integration-test` at about 75.0 seconds, and the previously interrupted `install:test` at about 28.4 seconds. An outer MCP/host connection interruption did not cancel the durable job; a later capability-bound `read_job` recovered its terminal success.
|
|
44
|
+
|
|
45
|
+
**Operational interpretation.** A hosted/relay/tool-response interruption or one-step carrier timeout is not evidence that an already accepted durable job failed, and it must not trigger blind resubmission of the underlying mutation. Recover the known durable job through its retained capability and distinguish transport/carrier status from job terminal status. This evidence is local candidate verification only; it does not prove Worker publication, hosted-product receipt, release activation, soak, or production health.
|
|
46
|
+
|
|
47
|
+
This quick audit-only follow-up changes no runtime source or tests. Its verification belongs to this fresh workflow epoch rather than retroactively rewriting either archived predecessor epoch.
|
|
48
|
+
|
|
49
|
+
## 2026-09-02 beta.163 MCP Apps monitor event discipline and outer-read recovery
|
|
50
|
+
|
|
51
|
+
**The latest owner-visible tool-status anomaly is generated by Machine Bridge metadata, not evidence of a new daemon or Worker fault.** The ChatGPT status surface displayed the literal English text `Durable job monitor opened.` beside an otherwise ordinary tool call. Source tracing found that exact string in `render_job_monitor`'s `openai/toolInvocation/invoked` metadata together with the corresponding `Opening durable job monitor…` invoking label. A live beta.162 monitor probe also showed another presentation coupling: after `render_job_monitor` mounted the View, `server_info` observed an active daemon `read_job` before the initiating model had issued its own confirmation read, proving that app-origin background polling reused the same model-visible tool surface. A separate live canary later returned `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, and `host_turn_handoff_recommended=true`, so the evidence does not support removing the MCP Apps handoff or inventing another render mechanism; the defect is the status/event boundary around an otherwise working handoff.
|
|
52
|
+
|
|
53
|
+
**The remaining interruption evidence also falsifies another relay-only repair.** During the beta.162 release workflow, a hosted `read_job` failed to the planner as `mcp_network_error: Connection failed`. Immediate local diagnostics still reached the daemon, whose relay remained ready with `outage_count=2` and the same most-recent 1006 interval ending at 05:35:30Z; no new daemon-to-Worker outage had occurred. This reproduces the beta.151 finding that an outer client/connector-to-Worker request can disappear while the durable job and daemon relay remain healthy. The native MCP stream already emits an immediate `: connected` comment and the outer response proxy emits five-second SSE heartbeats, so another keepalive would not target the demonstrated mechanism. Machine Bridge still cannot observe ChatGPT's final assistant-turn termination or final rendered-message receipt, and this change does not pretend otherwise.
|
|
54
|
+
|
|
55
|
+
**Beta.163 separates the presentation and continuation paths instead of broadening recovery authority.** `render_job_monitor` keeps the proven data-tool/render-tool split but removes its custom `openai/toolInvocation/invoking` / `openai/toolInvocation/invoked` labels and emits no ordinary-text status prose. `read_job` returns to model-only visibility and remains the one model-side confirmation call that can observe the matching View claim. Once that claim transfers continuation, the View calls a new app-only `read_job_monitor`: the Worker validates the same principal-bound `recovery_key`, validates only the monitor-ID shape as non-authority correlation, removes both values, and delegates exactly one fixed 40-second `read_job` to the daemon. App polling therefore no longer shares the model-visible `read_job` tool surface. `claim_job_monitor` has a separate 30-second response watchdog; a missing/transport-failed or explicitly retryable claim response is retried at most five times with 1/2/4/8/15-second backoff, and replay is safe because the same already-claimed exact monitor can be claimed again without daemon execution. Each app-origin status call has a 60-second response watchdog. A missing/transport-failed or explicitly retryable app-read response is retried at most six times with 1/2/4/8/15/30-second backoff; exhaustion and non-retryable errors pause. The recovery key remains the sole read authority, no bare-ID inventory is added, no monitor claim is persisted as authorization, no daemon mutation is replayed, and no retry can resubmit the managed job.
|
|
56
|
+
|
|
57
|
+
**Independent beta.163 review found two secondary boundaries that the initial implementation did not make strict enough.** First, app-only `read_job_monitor` originally returned the underlying `read_job` result after authorization, which made the monitor a second disclosure surface for step output, command text, paths, and other fields that status continuation does not require. It now applies a Worker-side allowlist before the result reaches the View; the render-input `recovery_key` remains necessary in app memory as read authority but is never part of that status projection, DOM output, persistent monitor state, logs, or daemon arguments, and teardown clears it with pending response timers. The View also no longer consumes ambient `ui/notifications/tool-result` messages as state, so unrelated host tool traffic cannot retarget the monitor, and user-visible failures use fixed text rather than reflecting host/runtime exception wrappers. Second, the Durable Object monitor store sits after accepted managed-job/read results on several paths. Storage failure there is therefore a continuation-coordination failure, not new evidence about whether the durable business operation succeeded: optional issuance falls back to normal model continuation, claim probing falls back to no handoff, and monitor-record cleanup cannot block an otherwise valid authority revocation. Bounded logs retain only the monitor operation class and sanitized error class. Persistent state validation additionally rejects duplicate monitor IDs/order sequences and impossible sequence generations. These changes reduce disclosure and unknown-outcome risk without weakening the principal-bound recovery capability.
|
|
58
|
+
|
|
59
|
+
**Release consequence.** This changes packaged Worker/UI bytes and the hosted tool contract. Package/runtime identity therefore advances to `3.0.0-beta.163` and hosted tool schema generation advances from 23 to 24. beta.162 remains valid only for its exact takeover-recovery bytes and cannot authorize beta.163. Fresh frozen verification, candidate/install-only proof, guarded activation, candidate-bound OAuth canary, current hosted schema publication, a live MCP Apps claim/continuation canary, and exact-head provider gates are required before beta.163 source publication. npm publication remains the sole separate current-task owner authorization boundary.
|
|
60
|
+
|
|
61
|
+
## 2026-09-02 beta.162 relay takeover recovery budget
|
|
62
|
+
|
|
63
|
+
**Continuing interruptions falsified the narrower conclusion that the remaining reports were only stale-host-session or outer-turn effects.** The live beta.161 daemon retained additional completed relay outages after the earlier host-schema diagnosis. One `relay_transport_timeout` episode coincided with an approximately eighteen-second local event-loop stall and therefore remains a local scheduling/starvation class. Separate abrupt `connection_interrupted`/`1006` episodes followed recent inbound traffic rather than a long heartbeat silence. Privacy-bounded macOS logs around those abrupt episodes also showed NetworkExtension/SystemConfiguration DNS/path invalidation and primary-path re-election. That is strong evidence that a network epoch can change underneath an established WebSocket, but it does not identify a particular tunnel product, proxy node, Cloudflare edge, ISP, DNS server, or other upstream component as the physical root cause.
|
|
64
|
+
|
|
65
|
+
**The relay already reacted immediately to established WebSocket loss, so another disconnect trigger is not the missing mechanism.** `ResilientRelayConnection` already promotes signed HTTPS fallback to exact-WebSocket-generation takeover with zero activation delay on a real close, and the Worker already gives a newly arriving daemon-backed call up to fifteen seconds to wait for some verified daemon channel. Existing in-flight calls also retain the separate same-instance reconnect ownership path. The remaining deterministic amplification was inside the fallback timing budget: every signed HTTP exchange used the ordinary seven-second request deadline. If the first takeover request was attached to the just-invalidated network epoch and consumed that full deadline, the first one-second failure backoff plus the two further exchanges needed for probing and verified readiness could no longer be guaranteed to fit inside the fifteen-second new-call recovery window. The old architecture gate merely checked `7s × 2 <= 15s`, which did not model the failed first request at all.
|
|
66
|
+
|
|
67
|
+
**Beta.162 narrows only the vulnerable takeover phase.** An exact-generation HTTPS takeover uses a three-second request deadline until that fallback channel is verified-ready; ordinary standby and ready HTTP traffic retains the seven-second deadline. The existing 750 ms request-start floor, steady-state polling cadence, retry backoff, authentication, generation targeting, sequence/acknowledgement rules, and Worker rate limits are unchanged. The resulting worst-case budget for one fully timed-out stale takeover request, the first failure backoff, and two further fully bounded takeover exchanges is ten seconds, leaving five seconds of margin inside the existing fifteen-second new-call recovery ceiling. A deterministic scheduler regression reproduces the original causal condition by holding the first takeover request until its entire timeout, then requires the same exact-generation takeover to finish verified readiness before the recovery ceiling. Package/runtime identity advances to `3.0.0-beta.162`; hosted tool schema generation remains 23 because the MCP catalog contract is unchanged. The change requires fresh frozen verification and live candidate transport evidence before acceptance; beta.161 remains immutable historical evidence and cannot authorize beta.162 promotion.
|
|
68
|
+
|
|
69
|
+
## 2026-09-02 beta.161 planner-independent monitor correlation
|
|
70
|
+
|
|
71
|
+
**The beta.160 live host test falsified the assumption that ordinary tool text is a reliable fallback for an MCP Apps render result.** The exact beta.160 runtime had already demonstrated a normal fresh Work canary where `structuredContent.ui_monitor_id` was visible, the App mounted, and the handoff passed. A separate fresh Work canary then explicitly forbade use of `structuredContent` and required the initiating model to parse `ui_monitor_id` only from `render_job_monitor` ordinary text. The model returned `TEXT_ID_NOT_AVAILABLE`. That narrower test proves the ordinary text projection is not reliable planner input; by itself it does not establish whether that particular render invocation mounted the View. Taken together, the two canaries show that successful MCP Apps support does not make render-result projections a stable prerequisite for planner continuation. beta.160 is not accepted; its text mirror does not remove the planner dependency.
|
|
72
|
+
|
|
73
|
+
**Beta.161 moves correlation issuance to the ordinary data-tool result and preserves render gating as a separate state transition.** An MCP Apps-capable active `start_job` now returns a fresh random `ui_monitor_id` alongside the existing job and recovery data. The Worker stores only a short-lived principal/job-bound record in `issued` state. That ID is correlation evidence, not read authority, and an app-only `claim_job_monitor` attempt must fail while the record remains merely issued. `render_job_monitor` now requires the exact `job_id`, `recovery_key`, and pre-issued `ui_monitor_id`; after verifying the principal-bound read capability it activates that exact record and mounts the static monitor resource. The View receives all three values through the render tool input notification, so it does not need the render result to claim. The model likewise reuses the ID it already obtained from `start_job` for its one confirmation `read_job`, so no handoff decision depends on host projection of a UI tool result.
|
|
74
|
+
|
|
75
|
+
**The authority and stale-instance boundaries remain fail-closed.** `recovery_key` remains the sole read capability and is stripped before daemon dispatch; the monitor ID alone authorizes nothing. Monitor state remains Worker-scope/principal/job/ID bound, has the existing five-minute bounded lifetime, participates in authority revocation, and cannot be claimed before render activation. Replaying or forging another ID therefore cannot transfer continuation, while a successfully mounted View must still prove app-origin `serverTools` access by claiming the same activated record before a model read can project `ui_monitor_claimed=true`. The changed render input and active-start result are host-visible schema behavior, so beta.161 advances tool schema generation from 22 to 23 and requires a governed Workspace Action refresh plus a fresh live canary that deliberately ignores the render result and uses only the ID from `start_job` before acceptance.
|
|
76
|
+
|
|
77
|
+
## 2026-09-01 beta.160 ChatGPT render-result planner compatibility
|
|
78
|
+
|
|
79
|
+
**The beta.159 live host canary falsified a narrower assumption than MCP Apps support itself.** After the exact beta.159 candidate was activated, the governed ChatGPT Workspace Action refresh completed and its approved action list contained both `render_job_monitor` and `claim_job_monitor`; `server_info` independently reported beta.159, 56 effective tools, and schema generation 22. Fresh plugin-bound chats then mounted the Job Monitor sandbox and the View independently followed the durable job to terminal state. A second controlled canary used a 180-second sleep and explicitly required exactly one default active `read_job` after render. The initiating model instead spent 2m18s investigating the render result and finally reported `ui_monitor_claimed` absent, `status_polling_mode=bounded_followup`, `host_turn_handoff_recommended=false`, and `CLAIM_NOT_CONFIRMED`, while the already-mounted widget later displayed the terminal job. This rules out stale Workspace publication and failure to mount the View, but it does not establish that ChatGPT's planner reliably exposes a UI render tool's `structuredContent` back to the initiating model.
|
|
80
|
+
|
|
81
|
+
**Beta.160 keeps the security boundary unchanged and adds only a non-secret text projection fallback.** `render_job_monitor` already issues a fresh random `ui_monitor_id` after verifying the principal-bound recovery capability; that ID is correlation evidence, not read authority. The render result now uses the same generated ID in both canonical `structuredContent.ui_monitor_id` and ordinary model-visible text so a host that mounts the Apps result but withholds its structured projection from planner context can still make the required next `read_job(job_id,recovery_key,ui_monitor_id)`. Neither `recovery_key` nor `control_key` is mirrored into text, the View still needs the original recovery capability to claim/read, an unissued or stale monitor ID still cannot claim, and exact claim matching remains required before model-side continuation is disabled. Worker-domain and HTTP/MCP integration regressions bind the two projections to the identical monitor ID and reject capability prefixes in the text result. Because no tool name, schema, visibility, resource URI, or description changes, hosted schema generation remains 22 while package/runtime identity advances to beta.160. beta.159 had already been locally accepted after the first successful fresh-chat canary, but the later strict repeat invalidated that acceptance as sufficient evidence for reliable handoff before any public promotion. beta.160 therefore requires fresh frozen verification, exact candidate activation, the packaged OAuth canary, and repeated fresh-chat active-claim canaries before its own acceptance.
|
|
82
|
+
|
|
83
|
+
## 2026-09-01 beta.159 hosted reply-turn timeout and MCP Apps continuation remediation
|
|
84
|
+
|
|
85
|
+
**The latest `message reply timed out` report is not supported by evidence of a contemporaneous Machine Bridge relay, daemon, sleep, or resource failure.** At the report boundary the beta.158 login daemon remained the same live process with one service run and no termination signal. Its most recent completed relay outage had recovered more than an hour earlier; the incident interval contained no daemon warning/error record, no matching macOS sleep/event-loop pause, and Green resource admission with no waiter. The current Worker isolate reported successful HTTP requests and zero tool-call failure, cancellation, or timeout, while durable continuity's most recent request abort predated the incident by hours. Machine Bridge cannot see the ChatGPT host's aggregate assistant-turn deadline or final rendered-message receipt, so the exact private host timeout remains outside server observability. Those negative controls nevertheless falsify another daemon/relay/reconnect explanation for this occurrence.
|
|
86
|
+
|
|
87
|
+
**A controlled amplification path remains inside the hosted orchestration contract.** The same workflow was following one already-durable managed job through repeated `read_job` calls. Recent daemon audit state showed thirteen `read_job` calls in fifteen minutes with no failure, and the Worker continued to settle individual calls normally. Each default hosted read is deliberately capped at forty seconds because earlier live testing rejected a five-minute single request while accepting forty/sixty-second calls. However, per-call survival does not prove that an unbounded sequence of successful calls can occupy one assistant response forever. Historical beta.104 and beta.113 evidence already demonstrated both ends of this constraint: high-density status loops can monopolize a turn, while a paced sequence of roughly 125 forty-second reads can still eventually meet a real outer host boundary. A new fixed read-count or elapsed-minute handoff would merely guess that private deadline and would reintroduce the opposite beta.104 regression where the user had to send `continue` as a scheduler tick.
|
|
88
|
+
|
|
89
|
+
**A later beta.159 verification interruption is separate transport evidence, not retroactive proof for the original host-turn timeout.** During the first full-verification attempt the relay recorded two later `connection_interrupted`/1006 outages, 2026-09-01 10:05:11.458Z–10:05:32.971Z and 10:13:27.373Z–10:13:56.723Z. The latter also produced a hosted call failure classified as `original_call_deadline_expired_during_reconnect`; reconnect attempts were delayed in `proxy_connecting`, and the signed HTTPS fallback independently retained `connection_timeout`. At that time the local verification host was under extreme scheduler pressure (about 258 runnable processes and load1 around 453) while the nested coverage run was failing its real process-tree fixture. A later post-suite sample showed four runnable processes and load averages 5.23/10.74/11.85 after the exact escaped fixture process was identity-checked and removed. That temporal correlation is useful environment evidence but does not identify the proxy, VPN/TUN, scheduler pressure, or another network layer as the outage cause. It therefore does not justify a new relay watchdog or changing the separately evidenced host-turn remediation.
|
|
90
|
+
|
|
91
|
+
**Beta.159 moves long-job waiting out of the model turn only after the current mounted MCP Apps View proves it can actually proxy server tools.** The Worker exposes one static `ui://machine-bridge/managed-job-monitor-v1.html` resource using `text/html;profile=mcp-app` and negotiates `io.modelcontextprotocol/ui`, but `start_job` remains the execution/data tool and carries no UI template. An exact UI MIME match on an active start produces only `ui_monitor_candidate=true`, `ui_monitor_claim_required=true`, and `ui_monitor_render_tool=render_job_monitor`; model-side `follow_up_read_required` stays true. The model calls `render_job_monitor(job_id,recovery_key)` once. That Worker-only read-authority check is the sole tool linked to the View and returns a fresh 128-bit `ui_monitor_id`. The View receives the render input/result, completes the MCP Apps 2026-01-26 initialization handshake, requires `hostCapabilities.serverTools`, and calls the app-only `claim_job_monitor(job_id,recovery_key,ui_monitor_id)`. The Worker stores only bounded short-lived principal/job/monitor-instance handoff evidence—never the result or recovery key and never a daemon request. The model's next `read_job` passes the same monitor ID; the Worker strips it together with the recovery capability before daemon dispatch and only an exact current claim emits `ui_monitor_claimed=true`, `status_polling_mode=ui_monitor`, `host_turn_handoff_recommended=true`, and model-side `follow_up_read_required=false`. There is no app-only job inventory, bare-ID lookup, copied terminal-result database, or direct View network access. `start_job` is model-visible without UI metadata, `render_job_monitor` is model-visible and mounts UI, `claim_job_monitor` is app-only, and `read_job` is model+app visible. Rendered terminal JSON removes recovery/control capabilities even though the View retains its read capability in memory for continuation; a stale claim from another View cannot transfer the current turn because its random monitor ID differs.
|
|
92
|
+
|
|
93
|
+
**This is progressive enhancement rather than a claim that every MCP host can now deliver background completion.** The first live canary of the earlier direct-attachment prototype falsified one important assumption: the live host advertised the MCP Apps MIME capability and `start_job` returned a monitor candidate, but a complete 131-task full-verification run never produced a View claim. That evidence does not show why the View failed to mount, but it is enough to reject `start_job`-attached UI as release proof. The implementation therefore follows the documented data-tool/render-tool pattern instead: `start_job` returns data, `render_job_monitor` is the explicit render tool, and only its random monitor instance can be claimed. The current MCP Tasks extension would still be the cleaner protocol-level durable completion abstraction once the target host supports it, but beta.159 does not depend on speculative Tasks support. The Job Monitor also does not automatically call `ui/message` / `sendFollowUpMessage`; current host behavior does not establish that unsolicited completion messaging is a reliable correctness path. The release target is narrower and testable: after the updated Worker/schema is actually activated and the governed host action snapshot is refreshed when required, ChatGPT must invoke `render_job_monitor`, mount the View, observe the matching claim on the next `read_job`, let the initiating assistant response settle, and let the View independently reach/display terminal state without a user `continue`. Until that live canary passes, static/full verification is necessary but not sufficient acceptance evidence.
|
|
94
|
+
|
|
95
|
+
**Release consequence.** The Worker capabilities, resource protocol, hosted tool metadata, active `start_job` result projection, tests, and documentation are packaged/public behavior. Package/runtime identity advances to `3.0.0-beta.159` and hosted tool schema generation advances from 21 to 22. beta.158 remains immutable on npm/GitHub and continues to be historical evidence for the incident; its soak clock cannot authorize stable promotion after this blocking continuity repair. beta.159 requires fresh frozen verification, candidate activation, an MCP Apps live continuation canary, owner acceptance, reviewed merge/provider gates, separate exact-version npm publication authorization, published activation, and a fresh major-prerelease soak interval.
|
|
96
|
+
|
|
3
97
|
## 2026-08-31 beta.158 resource-coordinator self-identity release blocker
|
|
4
98
|
|
|
5
99
|
**The exact-main beta.157 source-release gate failed before any tag, GitHub Release, or npm publication mutation.** The canonical wrapper passed the first 122/131 frozen full-verification tasks, then `[123/131] full-access:test` failed on its first direct process with retryable `resource_admission`, `pressure_state=green`, and `admission_reason=fairness_wait`. That fixture already used a unique private coordinator root and a synthetic healthy-host sampler. A later standalone `full-access:test` passed, ruling out a deterministic full-profile/process capability defect but not making the release failure safe to ignore. Explicit remote reconciliation found no `v3.0.0-beta.157` tag and no GitHub Release, so the failed wrapper left no immutable source-publication object.
|
package/docs/MULTI_ACCOUNT.md
CHANGED
|
@@ -130,6 +130,8 @@ For delegated (non-owner) principals, a different account, client, or token fami
|
|
|
130
130
|
|
|
131
131
|
Hosted managed-job recovery adds a stricter Worker boundary even for owner accounts: detailed `list_jobs` handles are not remotely enumerable, and `read_job`, `cancel_job`, and `depends_on` require purpose-scoped capabilities bound to the accepting account version, OAuth client, refresh family, role, and job. This prevents Machine Bridge's own global owner inventory from becoming cross-conversation recovery authority. It does not claim to distinguish two host conversations if the host itself incorrectly forwards the other conversation's valid capability.
|
|
132
132
|
|
|
133
|
+
Explicit persistent plan creation (`stage_job` / `start_job`) remains owner-only. Delegated `operator` process tools nevertheless use internal durable `transient_process` carriers so accepted one-step work can be recovered without granting plan authority. All accounts share the same profile job root and its global 512-state ceiling, so relay non-owner pending recovery is additionally bounded to 16 unresolved carriers per account ID plus account version. The account-version key deliberately spans OAuth clients and refresh families: one account may authorize several clients, and changing either client or family cannot reset the quota. Object read/control authority remains more narrowly bound to the original account/client/family capability. The account quota is checked under the existing root capacity lock from persisted job state before a genuinely new carrier publishes status or launches; it has no separate mutable ledger. A securely persisted valid same-key replay is still allowed at quota because it creates no new reservation. A deterministic directory with no persisted status is an incomplete transaction, not replay and not an account reservation; its retry must pass quota again. Hosted terminal delivery clears the pending marker and releases the account slot, as does terminal expiry beyond the existing 24-hour hard recovery grace. Owner/local submissions remain outside this delegated-account quota.
|
|
134
|
+
|
|
133
135
|
## Administrative boundary
|
|
134
136
|
|
|
135
137
|
Account and client administration is not exposed as an MCP tool and does not use a long-lived administration secret.
|