machine-bridge-mcp 1.2.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +11 -6
  4. package/browser-extension/manifest.json +2 -2
  5. package/docs/ARCHITECTURE.md +23 -11
  6. package/docs/AUDIT.md +37 -3
  7. package/docs/CLIENTS.md +2 -2
  8. package/docs/ENGINEERING.md +2 -2
  9. package/docs/GETTING_STARTED.md +1 -1
  10. package/docs/LOCAL_AUTHORIZATION.md +111 -0
  11. package/docs/LOGGING.md +7 -5
  12. package/docs/MULTI_ACCOUNT.md +5 -5
  13. package/docs/OPERATIONS.md +33 -7
  14. package/docs/OVERVIEW.md +12 -8
  15. package/docs/PROJECT_STANDARDS.md +1 -1
  16. package/docs/RELEASING.md +4 -4
  17. package/docs/TESTING.md +9 -2
  18. package/docs/THREAT_MODEL.md +7 -6
  19. package/docs/TOOL_REFERENCE.md +4 -4
  20. package/docs/UPGRADING.md +10 -6
  21. package/package.json +8 -2
  22. package/scripts/check-plan.mjs +5 -0
  23. package/scripts/check-runner.mjs +75 -0
  24. package/scripts/coverage-check.mjs +1 -1
  25. package/scripts/github-backlog.mjs +116 -0
  26. package/scripts/github-push.mjs +4 -0
  27. package/scripts/local-release-acceptance.mjs +2 -2
  28. package/scripts/release-acceptance.mjs +36 -17
  29. package/scripts/run-checks.mjs +11 -21
  30. package/src/local/account-admin.mjs +28 -3
  31. package/src/local/bounded-output.mjs +20 -3
  32. package/src/local/cli-approval.mjs +117 -0
  33. package/src/local/cli-options.mjs +8 -2
  34. package/src/local/cli.mjs +13 -2
  35. package/src/local/device-identity.mjs +108 -0
  36. package/src/local/errors.mjs +5 -1
  37. package/src/local/execution-limits.mjs +6 -1
  38. package/src/local/operation-authorization.mjs +366 -0
  39. package/src/local/operation-risk.mjs +221 -0
  40. package/src/local/operation-state-lock.mjs +92 -0
  41. package/src/local/process-execution.mjs +94 -14
  42. package/src/local/process-output-stream.mjs +82 -0
  43. package/src/local/process-result-projection.mjs +25 -0
  44. package/src/local/process-sessions.mjs +37 -51
  45. package/src/local/relay-connection.mjs +12 -5
  46. package/src/local/runtime-relay.mjs +13 -5
  47. package/src/local/runtime.mjs +14 -7
  48. package/src/local/secure-file.mjs +24 -4
  49. package/src/local/state.mjs +9 -3
  50. package/src/local/tool-executor.mjs +4 -2
  51. package/src/local/tools.mjs +4 -9
  52. package/src/local/worker-deployment.mjs +4 -3
  53. package/src/local/worker-secret-file.mjs +2 -1
  54. package/src/shared/admin-auth.d.mts +10 -0
  55. package/src/shared/admin-auth.mjs +36 -0
  56. package/src/shared/daemon-auth.d.mts +20 -0
  57. package/src/shared/daemon-auth.mjs +55 -0
  58. package/src/shared/result-projection.d.mts +6 -0
  59. package/src/shared/result-projection.json +4 -0
  60. package/src/shared/result-projection.mjs +50 -0
  61. package/src/shared/server-metadata.json +1 -0
  62. package/src/shared/tool-catalog.json +4 -4
  63. package/src/worker/account-admin.ts +73 -4
  64. package/src/worker/daemon-auth.ts +205 -0
  65. package/src/worker/daemon-sockets.ts +15 -2
  66. package/src/worker/errors.ts +18 -4
  67. package/src/worker/index.ts +59 -10
  68. package/src/worker/mcp-jsonrpc.ts +4 -2
  69. package/src/worker/nonce-store.ts +79 -0
  70. package/src/worker/oauth-controller.ts +11 -6
  71. package/src/worker/oauth-refresh-families.ts +172 -0
  72. package/src/worker/oauth-state.ts +48 -3
  73. package/src/worker/oauth-tokens.ts +49 -40
@@ -15,7 +15,7 @@ Use separate OS accounts, containers, VMs, state roots, Workers, and workspaces
15
15
  | `reviewer` | `review` | Read-only workspace, Git, image, resource, and job inspection |
16
16
  | `editor` | `edit` | Reviewer access plus deterministic file mutation |
17
17
  | `operator` | `agent` | Editor access plus workspace-confined direct process execution and sessions |
18
- | `owner` | `full` | Complete bridge authority, including shell, unrestricted paths, browser, applications, resources, jobs, and account administration |
18
+ | `owner` | `full` | Complete capability ceiling; high-impact remote transactions additionally consume a local account/client-bound lease |
19
19
 
20
20
  The effective tool set is the intersection of:
21
21
 
@@ -23,7 +23,7 @@ The effective tool set is the intersection of:
23
23
  2. the policy advertised by the connected local daemon;
24
24
  3. the tools actually available from that daemon.
25
25
 
26
- The Worker filters `tools/list` and rejects unauthorized calls before relay. Every accepted relay call also carries `account_id`, `account_version`, and `role`; the local runtime validates the role again before dispatch. The shared policy contract remains the single source of capability semantics.
26
+ For execution, a fourth local layer distinguishes trusted owner automation from delegated access. An authenticated owner may use the daemon policy ceiling directly. A high-impact call from a reviewer, editor, or operator must also match an active capability lease bound to its `account_id` and OAuth `client_id`. This transaction layer does not remove tools from canonical `full`; it preserves least privilege for delegated credentials without interrupting the owner workflow. The Worker filters `tools/list` and rejects unauthorized calls before relay. Every accepted relay call carries `account_id`, `account_version`, `client_id`, and `role`; the local runtime validates them again before dispatch. See [LOCAL_AUTHORIZATION.md](LOCAL_AUTHORIZATION.md).
27
27
 
28
28
  Authenticated `server_info` deliberately reports both layers. `authorization.effective_policy` and `authorization.effective_tools` are authoritative for the current account. The nested `daemon.policy` and `daemon.tools` fields are only the local capability ceiling before account-role filtering; a `full` daemon does not make an `editor` account full. Remote `project_overview` uses the effective values at top-level `policy` and `tools` and preserves the daemon values as `daemonPolicy` and `daemonTools`.
29
29
 
@@ -79,15 +79,15 @@ An authorization code records:
79
79
  - scope and protected resource;
80
80
  - expiration.
81
81
 
82
- An access-token record contains the same account binding plus the deployment-wide token version. Token values are stored only as SHA-256 lookup keys. Account passwords are CLI-generated 256-bit tokens. The Worker stores independent salted HMAC-SHA-256 verifiers and rejects arbitrary human-chosen passwords; the token entropy, rather than a CPU-intensive dictionary-hardening loop, provides offline-guessing resistance within the Worker CPU budget.
82
+ An access-token record contains the same account binding plus the deployment-wide token version and refresh-family identity. Token values are stored only as SHA-256 lookup keys. Access tokens last fifteen minutes. Refresh tokens rotate on every use, have a fourteen-day idle limit and thirty-day family limit, and leave a bounded consumed-token marker; replay of a consumed token revokes every remaining refresh and access token in that family. Account passwords are CLI-generated 256-bit tokens. The Worker stores independent salted HMAC-SHA-256 verifiers and rejects arbitrary human-chosen passwords; the token entropy, rather than a CPU-intensive dictionary-hardening loop, provides offline-guessing resistance within the Worker CPU budget.
83
83
 
84
84
  At each authenticated request, the Worker verifies that the account still exists, is active, and has the same version and role recorded in the token. A password rotation, role change, suspension, or removal increments or removes that account state and invalidates only its codes and tokens.
85
85
 
86
- `machine-mcp rotate-secrets` is intentionally broader. It rotates the account-administration secret, daemon secret, and deployment-wide token version, invalidating every account token and requiring all clients to authorize again.
86
+ `machine-mcp rotate-secrets` is intentionally broader. It rotates the account-administration HMAC key, daemon device identity, and deployment-wide token version, invalidating every account token, requiring all clients to authorize again, and requiring the matching Worker/daemon deployment to converge.
87
87
 
88
88
  ## Administrative boundary
89
89
 
90
- Account administration is not exposed as an MCP tool. It uses an owner-only local secret to call private Worker administration endpoints from the CLI. The administration secret is stored only in owner-protected local state and Cloudflare Worker secrets; it is never printed by `status`, sent through MCP, or used as an account password.
90
+ Account administration is not exposed as an MCP tool. It uses an owner-only local HMAC key to sign each CLI request over method, path, body SHA-256, timestamp, and random nonce. The key is stored only in owner-protected local state and Cloudflare Worker secrets and is never transmitted as a network bearer. The Worker consumes each nonce once through bounded Durable Object transaction state and rejects replay or malformed state. The key is never printed by `status`, sent through MCP, or used as an account password.
91
91
 
92
92
  The first start of a new deployment creates an `owner` account automatically and prints its generated password once. Subsequent starts do not display account passwords.
93
93
 
@@ -248,8 +248,9 @@ Defense-in-depth limits include:
248
248
  - text writes and patch envelopes: 5 MiB;
249
249
  - images: 4 MiB before base64 encoding;
250
250
  - shell/argv envelope: 64 KiB;
251
- - captured one-shot output: 512 KiB per stream by default;
252
- - process-session retained output: 1 MiB per stream, with lossless base64 fallback for non-UTF-8 slices;
251
+ - public one-shot output preview: 32 KiB per stream; larger output returns an `output_session_id` and remains readable through `read_process`;
252
+ - internal bounded subprocess capture: 512 KiB per stream by default;
253
+ - running or completed process-session retained output: 1 MiB per stream for up to 30 minutes, best effort subject to the eight-session capacity, with monotonic offsets and lossless base64 fallback for non-UTF-8 slices;
253
254
  - process sessions: 8 retained per runtime;
254
255
  - process stdin write: 64 KiB per call;
255
256
  - local simultaneous tool calls: 16;
@@ -292,15 +293,40 @@ machine-mcp --workspace /path/to/project --profile agent
292
293
 
293
294
  A remote policy change is saved locally, propagated in the daemon handshake, and loaded by autostart from owner-only state.
294
295
 
296
+ ### Remote authority and optional delegated-account leases
297
+
298
+ The saved profile defines the capability ceiling. An authenticated owner account may use that ceiling directly and is never required to approve a pending ID in a terminal. Delegated reviewer, editor, and operator accounts still require bounded leases for consequential remote effects. Inspect their pending requests and active leases with:
299
+
300
+ ```sh
301
+ machine-mcp --workspace /path/to/project approval list
302
+ ```
303
+
304
+ Approve the requested scope for ordinary work, or explicitly open a temporary full automation window:
305
+
306
+ ```sh
307
+ machine-mcp --workspace /path/to/project approval approve APPROVAL_ID --duration 1h
308
+ machine-mcp --workspace /path/to/project approval approve APPROVAL_ID --full
309
+ ```
310
+
311
+ Revoke one lease or all leases:
312
+
313
+ ```sh
314
+ machine-mcp --workspace /path/to/project approval revoke LEASE_ID
315
+ machine-mcp --workspace /path/to/project approval clear
316
+ ```
317
+
318
+ The pending and lease files are owner-only and contain identity bindings, scope, timestamps, and target digests rather than command or content text. Full details are in [LOCAL_AUTHORIZATION.md](LOCAL_AUTHORIZATION.md).
319
+
295
320
  ## Incident response
296
321
 
297
322
  After suspected credential or client compromise:
298
323
 
299
324
  1. stop foreground and autostart daemons;
300
- 2. run `machine-mcp rotate-secrets`;
301
- 3. restart without broad flags and redeploy;
302
- 4. inspect Cloudflare account access, Worker configuration, local state/resource permissions, process-lock owners, managed-job results, and service logs;
303
- 5. cancel active managed jobs and remove compromised resource aliases;
304
- 6. remove the Worker and local state if continued remote access is unnecessary.
325
+ 2. run `machine-mcp approval clear` for every affected workspace;
326
+ 3. disable or rotate the affected account, or run `machine-mcp rotate-secrets` for deployment-wide revocation;
327
+ 4. restart without broad flags and redeploy;
328
+ 5. inspect Cloudflare account access, Worker configuration, local state/resource permissions, process-lock owners, managed-job results, and service logs;
329
+ 6. cancel active managed jobs and remove compromised resource aliases;
330
+ 7. remove the Worker and local state if continued remote access is unnecessary.
305
331
 
306
332
  The detailed 0.12.0 audit record and residual operational limits are in [AUDIT.md](AUDIT.md).
package/docs/OVERVIEW.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  Machine Bridge is one local authority surface with two MCP transports. The architecture is organized around three independent questions:
4
4
 
5
- 1. **Who may request an operation?** Remote OAuth account identity and role, or the local process identity that launched stdio.
5
+ 1. **Who may request an operation?** Remote OAuth account/client identity and role, or the local process identity that launched stdio.
6
6
  2. **Which tools are exposed?** The shared tool catalog, policy profile, and account-role intersection.
7
- 3. **What can an exposed tool actually do?** The local runtime, workspace/path rules, operating-system user authority, and platform security controls.
7
+ 3. **Is this remote effect currently authorized?** Direct authenticated-owner authority within the daemon ceiling, automatic delegated workspace-safe behavior, or an account/client-bound delegated capability lease.
8
+ 4. **What can an authorized tool actually do?** The local runtime, workspace/path rules, operating-system user authority, and platform security controls.
8
9
 
9
10
  Transport authentication does not create an operating-system sandbox. The local daemon executes with the authority of its OS user.
10
11
 
@@ -14,13 +15,14 @@ Transport authentication does not create an operating-system sandbox. The local
14
15
  flowchart LR
15
16
  HC[Hosted MCP client] -->|HTTPS + OAuth 2.1 / PKCE| W[Cloudflare Worker]
16
17
  W --> DO[Durable Object room]
17
- DO -->|Authenticated outbound WebSocket| R[LocalRuntime]
18
+ DO -->|P-256 device-authenticated WebSocket| R[LocalRuntime]
18
19
 
19
20
  LC[Local MCP client] -->|stdio| S[stdio adapter]
20
21
  S --> R
21
22
 
22
23
  R --> PG[PolicyGate + account access]
23
- PG --> TE[ToolExecutor]
24
+ PG --> OA[OperationAuthorizer]
25
+ OA --> TE[ToolExecutor]
24
26
  TE --> FS[Workspace file service]
25
27
  TE --> PS[Process services]
26
28
  TE --> MJ[Managed jobs]
@@ -40,9 +42,10 @@ flowchart LR
40
42
 
41
43
  1. The Worker validates OAuth client, token, account state, role, resource binding, and MCP session state.
42
44
  2. The Worker filters advertised tools by account role and the daemon-reported capability ceiling.
43
- 3. The Durable Object relays a bounded tool envelope over the authenticated daemon socket.
45
+ 3. The Durable Object relays a bounded tool envelope carrying account and OAuth client identity over the device-authenticated daemon socket.
44
46
  4. The local runtime revalidates account authorization, policy, call lifecycle, timeout, and cancellation.
45
- 5. The selected local service executes with the daemon OS user's authority.
47
+ 5. The local transaction gate directly permits authenticated owner work within the daemon ceiling; delegated accounts receive automatic workspace-safe behavior or require a matching bounded capability lease for a high-impact effect.
48
+ 6. The selected local service executes with the daemon OS user's authority.
46
49
 
47
50
  The Worker never receives ambient filesystem or process authority. It receives only explicit request/result messages.
48
51
 
@@ -65,6 +68,7 @@ Local and Worker code consume these contracts. Generated references and drift te
65
68
  `LocalRuntime` is an orchestrator, not a low-level implementation module. It composes:
66
69
 
67
70
  - policy and account authorization;
71
+ - authenticated-owner direct authority plus delegated effect classification and local capability leases;
68
72
  - call registration, timeout, cancellation, and observability;
69
73
  - workspace and path services;
70
74
  - direct and shell process execution;
@@ -78,9 +82,9 @@ Low-level responsibilities remain in focused modules. Architecture tests enforce
78
82
 
79
83
  ## State and lifecycle
80
84
 
81
- Each canonical workspace has independent profile state, Worker identity, credentials, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
85
+ Each canonical workspace has independent profile state, Worker identity, credentials, local capability leases, locks, service metadata, and managed jobs. State mutations use owner-only files where supported, bounded reads, atomic replacement, and process-identity-aware locks.
82
86
 
83
- The relay distinguishes connectivity, authentication, readiness probing, and active service. A candidate daemon must complete an end-to-end probe before replacing an incumbent. Disconnect cancels relay-owned calls and terminates associated local processes.
87
+ The relay distinguishes signed preflight, challenge authentication, readiness probing, and active service. A candidate daemon must prove possession of the enrolled P-256 key and complete an end-to-end probe before replacing an incumbent. Disconnect cancels relay-owned calls and terminates associated local processes.
84
88
 
85
89
  Managed jobs use a separate durable lifecycle. Their plans are integrity-bound, runners are process-identity checked, transitions are lock-protected, terminal plans are scrubbed, and recovery is bounded.
86
90
 
@@ -20,7 +20,7 @@ Direct pushes to `main`, force pushes, and branch deletion are blocked by reposi
20
20
 
21
21
  ### Completion ownership and local acceptance
22
22
 
23
- Repository automation owns implementation, local validation, observed candidate verification, acceptance recording, and pull-request completion. For every npm-package change, automation generates the exact candidate tarball with `npm run release:candidate` and stops before the first GitHub push. The repository owner explicitly authorizes the live candidate Worker update and runs `npm run release:candidate:start -- --allow-worker-deploy` on the maintainer machine and leaves the candidate running. The coding agent verifies the deployed Worker version/hash, remote health, relay readiness, connected local version, and representative functionality through Machine Bridge, then records a passing decision with `npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp <version> CANDIDATE ON THE OWNER MACHINE AND IT WORKS"`. Automated checks or an unobserved process are not sufficient evidence.
23
+ Repository automation owns implementation, local validation, observed candidate verification, acceptance recording, and pull-request completion. For every npm-package change, automation generates the exact candidate tarball with `npm run release:candidate` and stops before the first GitHub push. The repository owner explicitly authorizes the live candidate Worker update in the active conversation. The coding agent starts the exact candidate through Machine Bridge with `npm run release:candidate:start -- --allow-worker-deploy`, verifies the deployed Worker version/hash, remote health, relay readiness, connected local version, and representative functionality, then records a passing decision with `npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp <version> CANDIDATE ON THE OWNER MACHINE AND IT WORKS"`. No owner terminal approval command is part of the standard workflow. Automated checks or an unobserved process are not sufficient evidence.
24
24
 
25
25
  The tracked `release-acceptance/v<version>.json` record binds the observed result to the npm tarball SHA-1 and SHA-512 integrity value. The record is excluded from the package, so a content-preserving squash merge does not invalidate it; any source, executable script, package metadata, or packaged-documentation change produces a different package hash and requires a regenerated candidate and another observed live verification. `npm run github:push`, pull-request CI, and `npm run release` verify the record. Raw direct pushes of release-relevant branches are prohibited.
26
26
 
package/docs/RELEASING.md CHANGED
@@ -40,13 +40,13 @@ A repository-only change under paths such as `.github/` may be merged without a
40
40
 
41
41
  ## Interactive local candidate acceptance
42
42
 
43
- The repository owner starts the exact `.release-candidate/*.tgz` artifact on the maintainer machine with:
43
+ The repository owner explicitly authorizes the exact `.release-candidate/*.tgz` artifact and any configured same-name Worker update in the active conversation. The coding agent then runs the candidate startup helper through Machine Bridge:
44
44
 
45
45
  ```sh
46
46
  npm run release:candidate:start -- --allow-worker-deploy
47
47
  ```
48
48
 
49
- This command verifies the pending tarball hashes, installs it into the ignored `.release-candidate/runtime/` prefix without replacing the normal global installation, explicitly authorizes startup to update the configured same-name Worker when its version or deployment hash differs, and starts the installed candidate in the foreground. This is an in-place live candidate deployment, not an isolated staging Worker. The owner leaves that process running. The coding agent then connects through Machine Bridge and verifies at minimum:
49
+ The helper verifies the pending tarball hashes, installs it into the ignored `.release-candidate/runtime/` prefix without replacing the normal global installation, updates the configured same-name Worker when its version or deployment hash differs, and starts the installed candidate in the foreground. This is an in-place live candidate deployment, not an isolated staging Worker. The owner does not need to execute a terminal authorization command. The coding agent then verifies at minimum:
50
50
 
51
51
  - the remote Worker reports the candidate version and the persisted deployment hash matches the candidate Worker bundle;
52
52
  - the remote health route succeeds without redirect;
@@ -63,7 +63,7 @@ npm run release:accept -- --confirm "I VERIFIED machine-bridge-mcp 1.2.9 CANDIDA
63
63
 
64
64
  This creates `release-acceptance/v<version>.json` containing package identity, npm tarball hashes, a portable Git-content digest, timestamp, result, and a fixed observed-verification marker. The portable digest is computed with a temporary Git index containing `HEAD` plus the complete working tree; the real staging area is not modified. The record does not store a personal name, machine path, command output, credential, or user content. It is intentionally excluded from the npm package, so adding it does not change the tested tarball.
65
65
 
66
- The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. Automated tests alone, a prepared tarball, a process the agent did not observe, or an ambiguous owner response do not authorize acceptance. Version 1.2.8 owner-recorded acceptance remains supported as a historical marker; version 1.2.9 and later require the owner-started, agent-observed workflow.
66
+ The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. Automated tests alone, a prepared tarball, a process the agent did not observe, or an ambiguous owner response do not authorize acceptance. Version 1.2.8 owner-recorded acceptance and the 1.2.9 owner-started marker remain supported as historical formats; version 2.0.0 and later require explicit owner authorization plus agent-operated, agent-observed verification.
67
67
 
68
68
  ## Push and review
69
69
 
@@ -73,7 +73,7 @@ Commit the candidate changes and acceptance record, then push the branch only th
73
73
  npm run github:push
74
74
  ```
75
75
 
76
- The command requires a clean non-`main` branch, verifies that the acceptance record is tracked, rebuilds the npm package, compares both hashes, and only then executes a non-force push of the current branch. Direct pushes to `main` remain prohibited. Any packaged-file change after acceptance invalidates the hash and blocks the next push until the owner retests a regenerated candidate.
76
+ The command requires a clean non-`main` branch, fetches `origin/main`, paginates all open GitHub issues and pull requests, and refuses to push while an unrelated PR remains open or an issue is not covered by a standard closing keyword in `origin/main..HEAD`. An already-open PR for the current branch is allowed so it can be updated. The command then verifies that the acceptance record is tracked, rebuilds the npm package, compares both hashes, and only then executes a non-force push of the current branch. Direct pushes to `main` remain prohibited. Any packaged-file change after acceptance invalidates the hash and blocks the next push until the owner retests a regenerated candidate.
77
77
 
78
78
  Open a pull request, satisfy the required checks, and squash-merge. Pull-request CI repeats the acceptance verification. A content-preserving squash changes the Git commit but not the package bytes, so the accepted hash remains valid.
79
79
 
package/docs/TESTING.md CHANGED
@@ -12,12 +12,16 @@ npm run check # complete suite; equivalent to check:full
12
12
 
13
13
  The explicit task lists live in `scripts/check-plan.mjs`. The fast plan retains static analysis, policy, architecture, contracts, and focused unit behavior. The platform plan adds process, service, browser/application, state, managed-job, cryptographic, and real-machine behavior tests needed on macOS and Windows. The full-only plan adds coverage, browser-broker integration, package/install, stdio, Worker/OAuth, and real-browser navigation suites. `npm run check` remains the authoritative complete gate.
14
14
 
15
+ Successful child-task stdout/stderr is intentionally suppressed so a long green plan does not overwhelm an MCP response or hide the final status behind host truncation. Progress and timing remain visible. A failed task returns bounded head/tail diagnostics for both streams. Set `MBM_CHECK_VERBOSE=1` only when an operator explicitly needs live child output; verbose mode can be large and should be run through a process session when used remotely.
16
+
15
17
  The repository requires Node.js 26 and npm 12. `.node-version`, `.nvmrc`, `packageManager`, `devEngines`, and strict engine checks keep local and CI execution on the same baseline.
16
18
 
17
19
  The suite includes:
18
20
 
19
21
  - package-impact classification derived from the npm package manifest, allowing repository-only GitHub workflow maintenance without weakening version requirements for shipped content;
20
- - interactive candidate acceptance hashing, including owner-authorized in-place Worker deployment plus owner-started candidate verification, exact tarball SHA-1/SHA-512 matching, acceptance-record exclusion from the package, legacy 1.2.8 marker compatibility, and invalidation after any packaged-byte change;
22
+ - interactive candidate acceptance hashing, including owner-authorized in-place Worker deployment plus agent-operated candidate verification, exact tarball SHA-1/SHA-512 matching, descriptor-first no-follow reads, path-identity/symlink regressions, acceptance-record exclusion from the package, legacy 1.2.8 marker compatibility, and invalidation after any packaged-byte change;
23
+ - one-shot process output preview/continuation, completed-session paging, bounded nonzero-exit details, UTF-8-safe head/tail diagnostics, compact local/Worker MCP text mirrors, and quiet-success/bounded-failure verification-runner behavior;
24
+ - GitHub backlog enforcement that paginates all open issues and pull requests, permits only the current branch PR, and requires standard closing keywords for every open issue before a guarded push;
21
25
  - release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
22
26
  - release-state diagnostics distinguishing missing local/remote version tags from tags that point to the wrong commit, plus a release-CI gate that rejects missing, pending, failed, pull-request-only, stale, or wrong-commit runs;
23
27
  - generated Cloudflare Worker types under ignored `.wrangler/` state and strict TypeScript checking, including unused-local and unused-parameter rejection; packaging rejects generated declarations;
@@ -66,7 +70,10 @@ The suite includes:
66
70
  - independently injected service CLI status/install/start/stop/uninstall/remove paths, including provider failure, no selected workspace, no deployed Worker, aliases, and default output/exit adapters;
67
71
  - CLI parsing, policy profiles, and client configuration boundaries;
68
72
  - live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
69
- - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, access/refresh rotation, stale refresh replay rejection, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
73
+ - P-256 daemon identity generation, signed WebSocket preflight, one-time transactional nonce consumption, challenge transcript binding, wrong-key/tamper/expiry/replay rejection, and prevention of unauthenticated candidate churn;
74
+ - local operation-risk classification and catalog-wide review coverage; authenticated-owner direct execution; account/client-bound compound capability leases for delegated accounts; ordinary workspace automation; external-sensitive path composition; sensitive/persistence writes; symbolic-link ancestor and patch-move destination canonicalization; browser-session/application-control plus data-export composition; process/job continuation; scoped/full approval; cross-process daemon/CLI mutation serialization; expiry/revocation; owner-only persistence; and malformed-record rejection;
75
+ - signed account-administration requests with origin/method/path/body/time/nonce binding, transactional one-time nonce consumption, legacy bearer rejection, nonce replay rejection, and malformed nonce-state fail-closed behavior;
76
+ - live local Worker OAuth registration, the unauthenticated `resource_metadata` challenge, protected-resource and authorization-server discovery, Streamable transport metadata, consent, URL-constructed `303` callbacks including the ChatGPT and hosted Claude redirect URIs with encoded state, PKCE, `offline_access`, form-encoded authorization-code and refresh-token exchanges, fifteen-minute access tokens, refresh-family idle/absolute limits, bounded consumed-token/revoked-family replay state, record-level schema validation, access/refresh rotation, stale refresh replay rejection with whole-family access/refresh revocation, account-version refresh revocation, authorization-code replay rejection, pending-registration throttling that excludes already authorized DCR clients, exact built-in ChatGPT/Grok browser origins, additive custom origins, unrelated-origin preflight rejection, no CORS response sharing for unrelated or opaque origins, opaque-origin authorization-form routing, exact per-request redirect-origin CSP with narrowly scoped Microsoft regional-consent and final Copilot Studio handoff exceptions, accessible credential-error rendering, protocol negotiation, HMAC-bound MCP session issuance, two-session same-id concurrency, sessionless same-id independence, session-scoped cancellation isolation, same-session duplicate rejection, daemon-backed session bootstrap, dynamic tool advertisement, rich content, candidate/probing/ready transitions, invalid readiness-result rejection, incumbent preservation until verified handover, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure. The metadata/refresh contract is the path used by Claude DCR and Copilot Studio Dynamic discovery. The same integration runs an `editor` account against a canonical `full` daemon and proves that `server_info` and remote `project_overview` report effective `edit` authority while retaining the full daemon ceiling only in explicitly scoped fields.
70
77
  - local runtime proof that one blocked tool handler does not serialize an independent handler, plus relay fault injection proving an undeliverable terminal result interrupts the ambiguous socket and enters reconnect backoff.
71
78
  - a real headless-Chrome OAuth navigation regression with four cases: `form-action 'self'` blocks the first cross-origin callback, allowing only the registered callback blocks the regional redirect, allowing the registered and regional callbacks blocks the final Copilot Studio redirect, and the complete policy preserves `code` and `state` through all three cross-origin hops. Linux CI fails if Chrome is unavailable; other environments skip only this browser executable check while retaining the Worker CSP assertions.
72
79
 
@@ -14,7 +14,7 @@ The main protected assets are:
14
14
 
15
15
  - workspace and local-user-accessible files;
16
16
  - process execution authority and inherited environment values;
17
- - daemon secret, account administration secret, account password verifiers, OAuth authorization codes, access tokens, and refresh tokens;
17
+ - daemon device private key, account-administration HMAC key, account password verifiers, OAuth authorization codes, access tokens, refresh tokens, and local capability leases;
18
18
  - Worker and Durable Object routing state;
19
19
  - owner-only profile, lock, resource, service, and managed-job state;
20
20
  - registered local resource contents and paths;
@@ -30,7 +30,7 @@ The hosted client and its prompts, tools, extensions, and retrieved content are
30
30
 
31
31
  ### Worker to local daemon
32
32
 
33
- The Worker is a relay and authorization layer, not a source of local authority. The daemon accepts only an authenticated, version-compatible relay session. End-to-end readiness requires a local probe result through the active session. Every remote tool call carries bounded account authority that is rechecked locally.
33
+ The Worker is a relay and authorization layer, not a source of local authority. The daemon accepts only a version-compatible relay that proves possession of the enrolled P-256 device key in both a signed upgrade preflight and a Worker challenge. End-to-end readiness requires a local probe result through the active session. Every remote tool call carries bounded account and OAuth-client identity that is rechecked locally, followed by effect-level local transaction authorization.
34
34
 
35
35
  ### MCP host to local stdio process
36
36
 
@@ -50,7 +50,7 @@ State paths, locks, plans, resources, and service definitions are security-sensi
50
50
 
51
51
  ### Maintainer to release infrastructure
52
52
 
53
- Repository checks reduce accidental or malicious package drift, but source hosting, npm ownership, protected environments, maintainer accounts, and human review remain external trust dependencies. Repository automation cannot manufacture independent review or claim live candidate success without observing the owner-started candidate. It may record acceptance after that observed verification.
53
+ Repository checks reduce accidental or malicious package drift, but source hosting, npm ownership, protected environments, maintainer accounts, and human review remain external trust dependencies. Repository automation cannot manufacture independent review or claim live candidate success without observing the explicitly owner-authorized candidate. It may record acceptance after that observed verification.
54
54
 
55
55
  ## Attacker models
56
56
 
@@ -71,7 +71,8 @@ The implementation aims to preserve these invariants:
71
71
 
72
72
  - deny unknown, malformed, stale, duplicated, unauthorized, or over-limit requests;
73
73
  - intersect remote account authority with the daemon capability ceiling in both Worker and local runtime;
74
- - keep transport authentication separate from tool authorization and OS authority;
74
+ - permit authenticated owner automation directly within the daemon ceiling while binding delegated high-impact effects to local account/client-scoped, time-bounded capability leases;
75
+ - keep transport authentication, account/tool authorization, transaction authorization, and OS authority separate;
75
76
  - use direct argv execution without shell interpretation unless the explicit shell tool is authorized;
76
77
  - canonicalize confined paths and reject symlink-based write escape;
77
78
  - bound request bodies, messages, files, output, logs, state, retained results, and concurrency;
@@ -102,7 +103,7 @@ Machine Bridge does **not** claim to provide:
102
103
 
103
104
  ### Canonical `full` profile
104
105
 
105
- `full` intentionally exposes the complete catalog, unrestricted local-user paths, shell execution, parent environment, and absolute path output. A malicious authorized client can use that authority destructively. The mitigation is a narrower profile or a separate low-privilege OS boundary, not additional warning text.
106
+ `full` intentionally preserves the complete catalog, unrestricted local-user paths, shell execution, parent environment, browser/application authority, and absolute path output. Version 2.0 treats the authenticated owner role as authorization to use that ceiling without a second terminal prompt. This prioritizes uninterrupted owner automation: compromise of an active owner OAuth client can exercise the complete daemon ceiling. Delegated accounts remain lease-bound for higher-impact effects. A malicious delegated client holding both valid OAuth credentials and an active matching lease can still act destructively within that lease. A temporary `full` lease is therefore an explicit automation window, not a sandbox or an endorsement of untrusted instructions. Use a narrower profile or a separate low-privilege OS boundary for mutually untrusted workloads.
106
107
 
107
108
  ### Application and browser automation
108
109
 
@@ -128,7 +129,7 @@ Code cannot create a second independent reviewer, npm OIDC trust relationship, p
128
129
 
129
130
  The main regression suites cover:
130
131
 
131
- - OAuth, account, policy, MCP session, daemon readiness, and relay replacement;
132
+ - OAuth, refresh-family replay revocation, signed administration requests, account, policy, device-authenticated daemon readiness, relay replacement, and local capability leases;
132
133
  - path confinement, atomic writes, state locks, service lifecycle, managed-job recovery, and destructive cleanup;
133
134
  - direct process boundaries, shell separation, process-tree termination, timeout, cancellation, and disconnect;
134
135
  - browser pairing, version/capability handshake, owner/client broker routing, sensitive-field handling, trusted input, and CSP navigation;
@@ -1455,7 +1455,7 @@ List effective direct-argv commands from project manifests and safe automatic pa
1455
1455
 
1456
1456
  **Run registered local command**
1457
1457
 
1458
- Run an effective manifest or automatic package-script command through its fixed argv, cwd, timeout ceiling, and extra-argument policy.
1458
+ Run an effective manifest or automatic package-script command through its fixed argv, cwd, timeout ceiling, and extra-argument policy. Large stdout/stderr is previewed inline and retained temporarily for paged read_process continuation.
1459
1459
 
1460
1460
  | Contract field | Value |
1461
1461
  |---|---|
@@ -1988,7 +1988,7 @@ Return bounded metadata and patch output for one revision without running reposi
1988
1988
 
1989
1989
  **Run process directly**
1990
1990
 
1991
- Execute an argv array without a command shell. This avoids shell parsing but does not sandbox the executable or code it launches.
1991
+ Execute an argv array without a command shell. This avoids shell parsing but does not sandbox the executable or code it launches. Large stdout/stderr is previewed inline and retained temporarily for paged read_process continuation.
1992
1992
 
1993
1993
  | Contract field | Value |
1994
1994
  |---|---|
@@ -2074,7 +2074,7 @@ Start a direct argv process without a shell and retain bounded stdout, stderr, a
2074
2074
 
2075
2075
  **Read process session**
2076
2076
 
2077
- Read bounded stdout and stderr deltas from a server-managed process session, optionally waiting briefly for new output.
2077
+ Read bounded stdout and stderr deltas from a running process session or a recently completed one-shot command continuation, optionally waiting briefly for new output.
2078
2078
 
2079
2079
  | Contract field | Value |
2080
2080
  |---|---|
@@ -2796,7 +2796,7 @@ Request cancellation of a detached managed job. The runner terminates the active
2796
2796
 
2797
2797
  **Execute shell command**
2798
2798
 
2799
- Execute a shell command with workspace cwd. This is not a sandbox and has the operating-system authority of the local user.
2799
+ Execute a shell command with workspace cwd. This is not a sandbox and has the operating-system authority of the local user. Large stdout/stderr is previewed inline and retained temporarily for paged read_process continuation.
2800
2800
 
2801
2801
  | Contract field | Value |
2802
2802
  |---|---|
package/docs/UPGRADING.md CHANGED
@@ -4,9 +4,11 @@
4
4
 
5
5
  Machine Bridge does not retain parallel implementations for obsolete MCP protocol dates, policy revisions, state schemas, lock formats, or browser-extension protocols. The supported path is a direct upgrade from the immediately preceding published package while its state already uses the current schema.
6
6
 
7
- Version 1.2.6 keeps local state schema version 6 and policy revision 5 unchanged. Existing 1.2.5 workspaces, named accounts, resource registrations, managed-job history, Worker identity, and browser pairing state are reused without a schema conversion. The package change hardens relay ready-context propagation and verified service-daemon takeover for recovery daemons; it does not alter stored authority or credential records.
7
+ Version 2.0.0 is a coordinated Worker/daemon security-protocol upgrade. It replaces the long-lived daemon bearer with a P-256 device identity, replaces network account-administration bearer requests with per-request HMAC signatures, shortens OAuth access tokens, introduces refresh-token families and replay revocation, and adds local capability leases for high-impact remote effects. The Worker and daemon must converge on 2.0.0 together; the removed daemon bearer protocol is not retained as a fallback.
8
8
 
9
- Version 1.2.6 still requires the Worker, local daemon, and browser extension to converge on the same package version. The 1.2.5 candidate probing ready negotiation remains mandatory; mixed components fail closed. Normal startup redeploys the versioned Worker when required and can now stop a verified same-workspace service daemon even when its process argv used only `--daemon-only` without repeating `--workspace` and `--state-dir`. Reload the unpacked extension after convergence even though this release does not change its browser protocol.
9
+ On the first 2.0.0 state load, a workspace without a device identity generates one locally, removes the legacy daemon secret, and rotates the deployment-wide OAuth token version. Existing pre-2.0 access and refresh credentials therefore fail closed and every client must authorize again. The device private JWK remains in owner-only local state; only the public JWK is deployed to the Worker. Subsequent ordinary starts preserve both the device identity and token version.
10
+
11
+ The canonical `full` profile is unchanged as a capability ceiling. Authenticated owner operations use that ceiling directly without terminal approval. High-impact operations from delegated non-owner accounts require a local account/client-bound lease, while their normal workspace-contained reads and edits and project inspection remain automatic. Because the packaged extension controls an existing browser profile, one `browser-session` lease covers profile reads and actions; registered-resource input or file upload uses the separate `data-export` scope. An operator may approve the requested scope or explicitly open a temporary `full` window for at most eight hours. Lease state is independent of the policy profile and does not migrate into OAuth credentials.
10
12
 
11
13
  Version 1.2.0 could accept prototype-shaped account roles through malformed administration input. On the first 1.2.1 or later Worker access, such an account is preserved for recovery but repaired fail-closed: its role becomes `reviewer`, it is disabled, its account version advances, and its authorization codes and tokens are removed. An operator can then assign a valid role, enable the account, and rotate its password through the normal account administration flow. A local policy record with an unknown profile label is normalized to `custom` while retaining its explicit capability fields; an invalid explicit `--profile` is rejected.
12
14
 
@@ -17,9 +19,11 @@ A state file from an older unsupported schema is rejected rather than guessed or
17
19
  1. Finish or cancel ordinary interactive process sessions. Accepted managed jobs may continue independently, but inspect them before replacing the daemon.
18
20
  2. Install the new package with the pinned npm procedure in [Getting started](GETTING_STARTED.md).
19
21
  3. Run `machine-mcp doctor`.
20
- 4. Start `machine-mcp` normally for each workspace. Startup verifies state, stops only a verified same-workspace daemon, converges the Worker deployment, and takes over using the installed version.
21
- 5. Reload the unpacked browser extension. Protocol and packaged-version equality are mandatory; an old extension cannot replace a working compatible connection.
22
- 6. Reconnect MCP clients if they retain stale tool or session metadata.
22
+ 4. Start `machine-mcp` normally in the foreground for each workspace. Startup generates the device identity if needed, rotates the old deployment token version, deploys the matching public key and 2.0.0 Worker, verifies end-to-end readiness, and only then may replace the prior service daemon.
23
+ 5. Reauthorize every remote MCP client. Pre-2.0 access and refresh tokens are intentionally invalid.
24
+ 6. Reload the unpacked browser extension. Protocol and packaged-version equality are mandatory; an old extension cannot replace a working compatible connection.
25
+ 7. Exercise one safe read and one high-impact operation. Approve the resulting scope or an explicit temporary `--full` window through the local CLI, then retry the operation.
26
+ 8. Restore or reinstall background service operation only after the foreground path is healthy.
23
27
 
24
28
  ## Upgrade safety
25
29
 
@@ -29,6 +33,6 @@ Machine Bridge never treats an unreadable or foreign-schema state file as empty
29
33
 
30
34
  ## Rollback
31
35
 
32
- Rollback is supported only when the older package understands every persisted schema and protocol already written by the newer package. Version 1.2.6 does not advance local state or policy schemas, so a complete rollback to 1.2.5 is structurally possible. The Worker, daemon, and extension must be rolled back together; restoring only one component recreates an unavailable mixed-version system. The preferred recovery is still to fix forward so incomplete ready-context dispatch and orphan recovery-daemon takeover remain corrected.
36
+ Rollback is supported only from a complete pre-upgrade backup. Version 2.0.0 changes Worker/daemon authentication, OAuth refresh state, administration authentication, and local credential material. An older package cannot use the new device protocol or refresh-family state. Rolling back package files alone therefore produces an unavailable or incorrectly authenticated mixed system. Restore the complete owner-only profile backup, prior Worker secret set/build, package version, service definition, and browser extension as one unit, or fix forward.
33
37
 
34
38
  Never roll back by copying only selected state files or changing version fields. Restore one complete verified state backup, package version, Worker build, and browser extension as a single operational unit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "1.2.10",
3
+ "version": "2.0.0",
4
4
  "description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -129,7 +129,13 @@
129
129
  "check-plan:test": "node tests/check-plan-test.mjs",
130
130
  "check:platform": "node scripts/run-checks.mjs platform",
131
131
  "release": "node scripts/github-release.mjs --publish",
132
- "release:candidate:start": "node scripts/start-release-candidate.mjs"
132
+ "release:candidate:start": "node scripts/start-release-candidate.mjs",
133
+ "check-runner:test": "node tests/check-runner-test.mjs",
134
+ "process-output:test": "node tests/process-output-continuation-test.mjs",
135
+ "github-backlog:test": "node tests/github-backlog-test.mjs",
136
+ "github:backlog": "node scripts/github-backlog.mjs",
137
+ "operation-authorization:test": "node tests/operation-authorization-test.mjs",
138
+ "device-auth:test": "node tests/device-auth-test.mjs"
133
139
  },
134
140
  "dependencies": {
135
141
  "https-proxy-agent": "9.1.0",
@@ -5,8 +5,12 @@ export const FAST_CHECK_TASKS = Object.freeze([
5
5
  "release-state:test",
6
6
  "release-ci:test",
7
7
  "network-retry:test",
8
+ "check-runner:test",
9
+ "process-output:test",
10
+ "github-backlog:test",
8
11
  "secure-file:test",
9
12
  "worker-secret-file:test",
13
+ "device-auth:test",
10
14
  "sarif-security:test",
11
15
  "shell:test",
12
16
  "architecture:test",
@@ -18,6 +22,7 @@ export const FAST_CHECK_TASKS = Object.freeze([
18
22
  "commit-message:test",
19
23
  "policy:test",
20
24
  "account:test",
25
+ "operation-authorization:test",
21
26
  "worker-oauth-controller:test",
22
27
  "policy-docs:check",
23
28
  "tool-docs:check",
@@ -0,0 +1,75 @@
1
+ import { spawn } from "node:child_process";
2
+ import { performance } from "node:perf_hooks";
3
+ import { BoundedOutput } from "../src/local/bounded-output.mjs";
4
+
5
+ const FAILURE_OUTPUT_BYTES_PER_STREAM = 64 * 1024;
6
+
7
+ export async function runVerificationPlan(options) {
8
+ const {
9
+ mode,
10
+ tasks,
11
+ npmCli,
12
+ cwd = process.cwd(),
13
+ env = process.env,
14
+ verbose = false,
15
+ stdout = process.stdout,
16
+ stderr = process.stderr,
17
+ spawnProcess = spawn,
18
+ } = options;
19
+ if (!npmCli) throw new Error("check runner must run through npm so npm_execpath is available");
20
+ const planStartedAt = performance.now();
21
+ stdout.write(`running ${mode} verification plan (${tasks.length} tasks)\n`);
22
+ for (const [index, task] of tasks.entries()) {
23
+ const taskStartedAt = performance.now();
24
+ stdout.write(`\n[${index + 1}/${tasks.length}] npm run ${task}\n`);
25
+ const result = await runTask({ task, npmCli, cwd, env, verbose, spawnProcess });
26
+ const elapsedSeconds = ((performance.now() - taskStartedAt) / 1000).toFixed(1);
27
+ if (result.error) throw result.error;
28
+ if (result.code !== 0) {
29
+ stderr.write(`verification task failed after ${elapsedSeconds}s: ${task}\n`);
30
+ emitFailureDiagnostics(result, stderr);
31
+ const error = new Error(`verification task failed: ${task}`);
32
+ error.exitCode = result.code || 1;
33
+ throw error;
34
+ }
35
+ stdout.write(`completed ${task} in ${elapsedSeconds}s\n`);
36
+ }
37
+ const totalSeconds = ((performance.now() - planStartedAt) / 1000).toFixed(1);
38
+ stdout.write(`\n${mode} verification plan passed in ${totalSeconds}s\n`);
39
+ }
40
+
41
+ function runTask({ task, npmCli, cwd, env, verbose, spawnProcess }) {
42
+ return new Promise((resolvePromise) => {
43
+ const stdout = verbose ? null : new BoundedOutput(FAILURE_OUTPUT_BYTES_PER_STREAM);
44
+ const stderr = verbose ? null : new BoundedOutput(FAILURE_OUTPUT_BYTES_PER_STREAM);
45
+ let child;
46
+ try {
47
+ child = spawnProcess(process.execPath, [npmCli, "run", "--silent", task], {
48
+ cwd,
49
+ env: { ...env, NO_COLOR: env.NO_COLOR || "1" },
50
+ stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
51
+ windowsHide: true,
52
+ shell: false,
53
+ });
54
+ } catch (error) {
55
+ resolvePromise({ code: 1, error, stdout, stderr });
56
+ return;
57
+ }
58
+ child.stdout?.on?.("data", (chunk) => stdout?.append(chunk));
59
+ child.stderr?.on?.("data", (chunk) => stderr?.append(chunk));
60
+ child.once("error", (error) => resolvePromise({ code: 1, error, stdout, stderr }));
61
+ child.once("close", (code) => resolvePromise({ code: Number.isInteger(code) ? code : 1, stdout, stderr }));
62
+ });
63
+ }
64
+
65
+ function emitFailureDiagnostics(result, output) {
66
+ const stdoutText = result.stdout?.text?.() || "";
67
+ const stderrText = result.stderr?.text?.() || "";
68
+ if (stdoutText) output.write(`\n--- task stdout ---\n${ensureNewline(stdoutText)}`);
69
+ if (stderrText) output.write(`\n--- task stderr ---\n${ensureNewline(stderrText)}`);
70
+ if (!stdoutText && !stderrText) output.write("task produced no captured output\n");
71
+ }
72
+
73
+ function ensureNewline(value) {
74
+ return value.endsWith("\n") ? value : `${value}\n`;
75
+ }
@@ -115,7 +115,7 @@ try {
115
115
  if (failures.length) throw new Error(`coverage thresholds failed:\n- ${failures.join("\n- ")}`);
116
116
  console.log("critical-module coverage thresholds passed");
117
117
  } finally {
118
- rmSync(coverageDir, { recursive: true, force: true });
118
+ rmSync(coverageDir, { recursive: true, force: true, maxRetries: 8, retryDelay: 50 });
119
119
  }
120
120
 
121
121
  function collectCoverage(directory) {