machine-bridge-mcp 1.2.6 → 1.2.8

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/docs/RELEASING.md CHANGED
@@ -2,58 +2,92 @@
2
2
 
3
3
  The release invariant is:
4
4
 
5
- - `main` points to the release commit, and the exact commit has a completed successful push-triggered `.github/workflows/ci.yml` run.
6
- - `v<package version>` points to that same commit locally and on GitHub.
7
- - A final GitHub Release exists for the tag.
8
- - The GitHub Release contains the npm tarball generated from that commit.
9
- - `package.json`, `package-lock.json`, the Worker-reported version, and `browser-extension/manifest.json` (`version`/`version_name`) agree.
10
- - Every release-relevant change since the prior version tag has a higher package version and matching CHANGELOG section.
11
- - The same reviewed change is present on GitHub and in a new npm version.
5
+ - the repository owner tested the exact npm tarball represented by `release-acceptance/v<version>.json` on the maintainer machine and explicitly recorded a passing result;
6
+ - the current package produces the same SHA-1 and SHA-512 integrity values as that accepted tarball;
7
+ - `main` points to the release commit, and that exact commit has completed successful push-triggered CI, CodeQL, Governance, and OpenSSF Scorecard runs;
8
+ - `v<package version>` points to that same commit locally and on GitHub;
9
+ - a final GitHub Release exists for the tag and contains the accepted npm tarball;
10
+ - `package.json`, `package-lock.json`, the Worker-reported version, and `browser-extension/manifest.json` (`version`/`version_name`) agree;
11
+ - every npm-package change since the prior version tag has a higher package version and matching CHANGELOG section;
12
+ - the same accepted package content is present on GitHub and in a new npm version.
12
13
 
13
- `npm publish` runs `release:check` through `prepublishOnly`, so npm publication is blocked until all GitHub state is synchronized.
14
+ A green automated suite is necessary but is not evidence that the maintainer's ordinary installation path works. The repository therefore separates automated validation from owner acceptance and binds both to the final package bytes.
14
15
 
15
- ## Default automation responsibility
16
+ ## Change classification
16
17
 
17
- For every reviewed release-relevant change, repository automation completes the source release without requiring the owner to repeat the instruction: it merges the pull request, waits for successful push-triggered CI on the exact `main` commit, creates and pushes the annotated version tag, creates or updates the final GitHub Release, uploads the generated npm tarball, and verifies that all references identify the same commit. These GitHub operations use only local `git`, `gh`, and `gh api` through Machine Bridge.
18
+ A change is **npm-package relevant** when it alters `package.json`, `package-lock.json`, or a path included by the package `files` manifest. Source, runtime scripts, browser extension files, shared contracts, shipped documentation, and package metadata therefore require a new version and local acceptance.
18
19
 
19
- This standing responsibility does not authorize npm publication, Cloudflare Worker deployment, credential changes, global package installation, or local daemon/service replacement. Those operations change a registry or live machine and require explicit authorization.
20
+ A repository-only change under paths such as `.github/` may be merged without a synthetic npm version when the package bytes are unchanged. It still requires review and all applicable GitHub checks. `scripts/release-impact-check.mjs` implements this distinction from the package manifest rather than from a duplicated path list.
20
21
 
21
- ## Prepare a version
22
+ ## Prepare the candidate locally
22
23
 
23
- 1. Set the new version without creating an automatic npm tag. The npm version hook synchronizes the Worker and packaged browser-extension versions:
24
+ 1. Set the new version without creating a tag. The npm version hook synchronizes the Worker and browser-extension versions:
24
25
 
25
26
  ```sh
26
27
  npm version <version> --no-git-tag-version
27
28
  ```
28
29
 
29
- 2. Add the matching dated `CHANGELOG.md` section.
30
- 3. Run `npm run release-impact:check`, `npm run privacy:check`, review `npm run privacy:history`, run `npm run check`, both dependency audits, `npm audit signatures`, and generate a CycloneDX `npm sbom`.
31
- 4. Inspect the complete diff and `npm pack --dry-run`, including packaged file modes and every helper referenced by package scripts, then commit and push all release changes to `main`. Wait for the push-triggered Linux/macOS/Windows and package-audit checks to complete successfully.
30
+ 2. Add the matching dated `CHANGELOG.md` section and update affected documentation and audit notes.
31
+ 3. Run the required dependency, privacy, Worker, and package checks while iterating.
32
+ 4. Inspect the complete diff.
33
+ 5. Generate the final candidate:
32
34
 
33
- A privacy/security documentation correction is not “docs only” for release purposes. It requires a replacement npm version and, when appropriate, deprecation or unpublication of the affected version.
35
+ ```sh
36
+ npm run release:candidate
37
+ ```
38
+
39
+ `release:candidate` runs the complete repository suite and then writes the exact npm tarball plus a bounded pending manifest under ignored `.release-candidate/`. Do not modify a packaged file after generating the candidate without regenerating and retesting it.
40
+
41
+ ## Repository-owner local acceptance
42
+
43
+ The repository owner—not the coding agent—must test the exact `.release-candidate/*.tgz` artifact on the maintainer machine through the normal installation and startup path relevant to the change. For a general release, this includes installation from that tarball, zero-argument startup, ordinary connection/readiness, and at least one representative user operation. Changes to browser, application automation, service startup, proxying, credentials, or platform-specific behavior require the corresponding live or platform-specific test described in `docs/TESTING.md` and `docs/OPERATIONS.md`.
44
+
45
+ After the owner confirms the candidate works, record the decision using the exact phrase printed by `release:candidate`, for example:
46
+
47
+ ```sh
48
+ npm run release:accept -- --confirm "I TESTED machine-bridge-mcp 1.2.8 LOCALLY AND IT WORKS"
49
+ ```
50
+
51
+ This creates `release-acceptance/v<version>.json` containing only package identity, hashes, timestamp, result, and a fixed owner-confirmation marker. It does not store a personal name, machine path, command output, credential, or user content. The acceptance record is intentionally excluded from the npm package, so adding it does not change the tested tarball.
34
52
 
35
- ## Publish GitHub source and release
53
+ The command repacks the current tree and refuses to record acceptance if any packaged byte changed after candidate preparation. The assertion is a process gate backed by branch ownership and review, not a cryptographic identity signature; it must never be generated by automation merely because automated tests passed.
36
54
 
37
- From a clean `main` worktree:
55
+ ## Push and review
56
+
57
+ Commit the candidate changes and acceptance record, then push the branch only through:
58
+
59
+ ```sh
60
+ npm run github:push
61
+ ```
62
+
63
+ 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.
64
+
65
+ 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.
66
+
67
+ ## Publish the GitHub source release
68
+
69
+ From a clean, fast-forwarded `main` worktree whose `HEAD` exactly equals `origin/main`:
38
70
 
39
71
  ```sh
40
72
  npm run release:publish
41
73
  ```
42
74
 
43
- The command validates the project, including repository privacy checks, fast-forwards `origin/main` when needed, then requires the latest push-triggered CI run for that exact commit to be completed successfully before it creates or verifies the annotated version tag. It then pushes the tag, builds the npm tarball, creates or updates the GitHub Release, uploads the tarball, and verifies the resulting state. Read-only GitHub operations, Git pushes, and idempotent release updates use bounded retries only for classified transient network failures; ambiguous Release creation responses are resolved by querying server state before continuing.
75
+ `release:publish` never pushes `main`. It verifies the owner acceptance, runs the complete project and version checks, requires successful exact-commit push runs for CI, CodeQL, Governance, and Scorecard, creates or verifies the annotated version tag, pushes only the version tag when absent, builds the npm tarball, creates or updates the final GitHub Release, uploads the tarball, and verifies the resulting state.
44
76
 
45
- To verify without changing anything:
77
+ To verify an already completed source release without changing anything:
46
78
 
47
79
  ```sh
48
80
  npm run release:check
49
81
  ```
50
82
 
51
- To create GitHub Release records for existing remote version tags that lack releases:
83
+ To create GitHub Release records for historical remote tags that lack releases:
52
84
 
53
85
  ```sh
54
86
  npm run release:backfill
55
87
  ```
56
88
 
89
+ Backfill is historical metadata repair. It does not retroactively claim that releases predating the 1.2.8 acceptance policy had a recorded local acceptance.
90
+
57
91
  ## Publish npm
58
92
 
59
93
  Only after `release:check` succeeds:
@@ -62,18 +96,16 @@ Only after `release:check` succeeds:
62
96
  npm publish --access public
63
97
  ```
64
98
 
65
- The npm lifecycle repeats the full project checks and the GitHub synchronization check before upload. Do not leave a code or documentation fix only on GitHub; the corresponding npm version must be published, and the operator should be explicitly reminded until registry publication is confirmed.
66
-
67
- ## Authentication requirements
99
+ `prepublishOnly` repeats the complete checks and GitHub synchronization check. npm publication remains a deliberate release-operator action and is not authorized by an ordinary source change.
68
100
 
69
- - Git push access to `origin`.
70
- - An authenticated GitHub CLI session with repository release permission.
71
- - An npm account that owns the package or has maintainer permission.
101
+ ## Authentication and external controls
72
102
 
73
- GitHub Actions is a required publication boundary. The authenticated GitHub CLI must be able to read Actions runs, and a missing, pending, cancelled, skipped, or failed exact-commit push run blocks both `release:publish` and `release:check`. This prevents a machine-specific local pass from publishing a version that fails another supported platform.
103
+ Required local access:
74
104
 
75
- ## Registry publication hardening
105
+ - Git push access to `origin` for the accepted branch and version tag;
106
+ - an authenticated GitHub CLI session with pull-request and release permission;
107
+ - an npm account that owns the package or has maintainer permission for the separate registry publication step.
76
108
 
77
- The preferred target is npm trusted publishing from a narrowly scoped GitHub Actions release workflow using OIDC and a protected GitHub environment. This removes the need for a long-lived npm publication token and allows npm to generate provenance automatically. Enabling it requires an explicit package-owner configuration in npm and a reviewed change to the release workflow; repository code alone cannot complete that trust relationship.
109
+ GitHub Actions remains a required cross-platform boundary. Missing, pending, cancelled, skipped, failed, pull-request-only, stale, or wrong-commit runs do not satisfy release publication. Local owner acceptance and successful GitHub checks are complementary evidence; neither substitutes for the other.
78
110
 
79
- Until trusted publishing is configured, publication remains a deliberate local operator step after `release:check`. Do not add an npm token to repository or environment files, workflow YAML, logs, or local project notes. Restrict any fallback token to the shortest practical lifetime and minimum package scope.
111
+ The preferred registry target remains npm trusted publishing from a narrowly scoped GitHub Actions workflow using OIDC and a protected GitHub environment. Enabling it requires package-owner configuration in npm and a reviewed workflow change. Until then, never place an npm token in repository files, workflow YAML, logs, local acceptance records, or project notes.
package/docs/TESTING.md CHANGED
@@ -12,6 +12,8 @@ The repository requires Node.js 26 and npm 12. `.node-version`, `.nvmrc`, `packa
12
12
 
13
13
  The suite includes:
14
14
 
15
+ - package-impact classification derived from the npm package manifest, allowing repository-only GitHub workflow maintenance without weakening version requirements for shipped content;
16
+ - repository-owner local acceptance hashing, including exact tarball SHA-1/SHA-512 matching, acceptance-record exclusion from the package, and invalidation after any packaged-byte change;
15
17
  - release-impact enforcement requiring a new package version and CHANGELOG section for release-relevant changes;
16
18
  - 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;
17
19
  - generated Cloudflare Worker types under ignored `.wrangler/` state and strict TypeScript checking, including unused-local and unused-parameter rejection; packaging rejects generated declarations;
@@ -24,7 +26,7 @@ The suite includes:
24
26
  - foreground takeover of active and orphaned background daemons with current service-lock metadata, foreground-process protection, bounded final lock-handoff retry, actual-PID exit waiting, POSIX non-escalating timeout behavior, Windows verified-daemon stop semantics, daemon lock mode/version/process-start metadata, launchd service-target semantics, and silent idempotent duplicate service starts; daemon fixture subprocesses intentionally do not inherit V8 coverage because their purpose is ownership timing rather than code measurement;
25
27
  - fail-closed service lifecycle ordering for provider-stop, all-workspace daemon-stop, and definition removal, including platform/daemon/removal failure injection and normalized macOS/systemd/Windows results; Windows coverage reproduces an inline `/TR` command above 262 characters, proves the short launcher action remains bounded, verifies least-privilege logon registration, restart/log routing, language-independent `Ready`/`Running` observation, and state-observed stop/removal despite localized nonzero command output;
26
28
  - private service-environment capture/load coverage for exact allowlisting, value bounds and control-character rejection, non-proxy secret exclusion, runtime-value precedence, Windows case-insensitive replacement, explicit empty-value clearing, and preservation across a later environment-free startup;
27
- - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, frozen-wall-clock browser deadline coverage, installed-application discovery caching, and name-based task matching;
29
+ - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, broker result redaction, pre-registered runtime-handshake listeners, bounded socket/HTTP waits, direct loopback health despite hostile environment-proxy settings, stop-during-start generation invalidation, frozen-wall-clock browser deadline coverage, installed-application discovery caching, and name-based task matching;
28
30
  - Worker health direct/proxy routing through a real local HTTP CONNECT proxy, `NO_PROXY` bypass, exact `workers.dev` origin/name validation, redirect rejection, body/deadline bounds, error classification, bounded propagation retry, and invalid proxy fail-fast behavior without endpoint or credential disclosure;
29
31
  - Worker deployment ambiguity/idempotency: a successful Wrangler result followed by health timeout persists the fingerprint, a second start performs no upload, an actual process restart and disk-state reload still performs no upload, definitive stale-version evidence redeploys under the same name, accidental name changes are rejected, forced replacements clear current endpoint state, and prior names remain in uninstall inventory;
30
32
  - relay environment-proxy direct/bypass/agent selection, unsupported proxy rejection, fail-fast invalid configuration, and route observability without endpoint or credential disclosure;
@@ -38,13 +40,13 @@ The suite includes:
38
40
  - author-email privacy in `git_log`;
39
41
  - isolated command HOME/temp/cache behavior;
40
42
  - one-shot timeout, descendant process-group/tree termination including descendants that ignore graceful shutdown after the direct child has already exited, cancellation, and process-session interaction;
41
- - layered fixed runtime diagnostics for filesystem, direct process, shell, managed-job storage, and resource availability;
43
+ - layered fixed runtime diagnostics for filesystem, direct process, shell, managed-job storage, and resource availability; machine-readable execution guardrails that must keep CPU, memory, and network isolation marked unenforced unless an actual OS boundary is added;
42
44
  - local resource CLI registration, permission checks, dynamic reload, state-path redaction, and content non-disclosure;
43
45
  - real Ed25519 and RSA generation, idempotent reuse, public/private correspondence, mode enforcement, incomplete/mismatched/symlink rejection, and private-content non-disclosure;
44
46
  - real-machine canonical-full sandbox acceptance for outside-workspace I/O, direct/shell execution, full environment inheritance, SSH prerequisites, temporary authorized-key writing, and detached cleanup without external state changes;
45
47
  - deterministic injected atomic-replace failures, sustained transient Windows sharing contention beyond the old retry budget, bounded exponential delay selection, and repeated Windows full-sandbox runs;
46
48
  - canonical named-profile repair and full-only tool exposure parity between local and Worker policy filters;
47
- - managed-job staging/local approval/cancel-before-start, detachment, job-scoped temporary files, resource hash verification/redaction, discard capture, finally execution, descendant-tree escalation, token/snapshot-safe transition locks, runner process identity, plan scrubbing, PID-reuse-safe dead-runner recovery, and rejection of numeric-only runner records;
49
+ - managed-job staging/local approval/cancel-before-start, detachment, job-scoped temporary files, resource hash verification/redaction, discard capture, finally execution, descendant-tree escalation, token/snapshot-safe transition locks, runner process identity, spawn-without-PID rejection, asynchronous runner-error observability, plan scrubbing, PID-reuse-safe dead-runner recovery, and rejection of numeric-only runner records;
48
50
  - daemon/startup locking, successfully-read corrupt-JSON recovery, and explicit propagation/preservation of oversized, symbolic-link, permission, and I/O failures;
49
51
  - guarded state-root removal, unsafe state-root/workspace overlap rejection before creation, all-profile lock/daemon scanning, strict current-schema validation, corrupt-JSON isolation, and policy-origin persistence;
50
52
  - no filename-based sensitive-file denial under unrestricted policy;
@@ -97,9 +99,10 @@ npm sbom --sbom-format cyclonedx
97
99
  npm pack --dry-run
98
100
  npm run version:check
99
101
  npm run release-impact:check
102
+ npm run release:acceptance:verify
100
103
  ```
101
104
 
102
- GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI downloads the exact npm 12.0.1 tarball from the canonical registry, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH` before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and run OpenSSF Scorecard. The SARIF gate fails closed when rule metadata is absent, CodeQL permits only one exact, expiring non-shell process-boundary result, and Scorecard permits only exact reviewed governance/time-dependent findings; new dependency-pinning, fuzzing, or other supply-chain results fail before upload. Scorecard's signed analysis job contains only pinned `uses` steps, while a dependent gate job downloads the SARIF, enforces the accepted inventory, and uploads it to code scanning. Security property tests use a `.js` `fast-check` suite with deterministic seeds because the pinned Scorecard scanner recognizes JavaScript property testing only in `*.js`/`*.jsx`. The installed zero-argument startup smoke test also verifies that Windows creates and remembers `%USERPROFILE%\MachineBridge` while other platforms retain the package-free current directory. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot monitors both action and dependency updates. Source release creation additionally requires successful push-triggered CI, CodeQL, Governance, and Scorecard runs for the exact `main` commit.
105
+ GitHub Actions executes the main suite on Linux, macOS, and Windows using the pinned Node 26/npm 12 baseline. Because Node 26 currently bundles npm 11, CI downloads the exact npm 12.0.1 tarball from the canonical registry, rejects redirects, verifies its recorded SHA-512 SRI, and exposes that verified CLI through `GITHUB_PATH` before any project-local npm command can trigger strict `devEngines`. Checkout fetches version tags so the release-impact gate can compare the branch with the latest release. A separate package-audit job scans reachable Git history, audits both the complete dependency graph and the production-only graph, verifies registry signatures and attestations, validates a CycloneDX SBOM written under the runner temporary directory, exercises the documented isolated global installation, then performs a dry-run package build. Separate pinned workflows validate governance titles, review dependency changes, run CodeQL over JavaScript/TypeScript and GitHub Actions, and run OpenSSF Scorecard. The SARIF gate fails closed when rule metadata is absent, CodeQL permits only one exact, expiring non-shell process-boundary result, and Scorecard permits only exact reviewed governance/time-dependent findings; new dependency-pinning, fuzzing, or other supply-chain results fail before upload. Scorecard's signed analysis job contains only pinned `uses` steps, while a dependent gate job downloads the SARIF, enforces the accepted inventory, and uploads it to code scanning. Security property tests use a `.js` `fast-check` suite with deterministic seeds because the pinned Scorecard scanner recognizes JavaScript property testing only in `*.js`/`*.jsx`. The installed zero-argument startup smoke test also verifies that Windows creates and remembers `%USERPROFILE%\MachineBridge` while other platforms retain the package-free current directory. The macOS matrix job also runs the installation smoke test because Wrangler's optional `fsevents` resolution is platform-specific. Third-party Actions are pinned to immutable 40-character commit SHAs; architecture tests reject movable tags, and Dependabot groups GitHub Action updates into one atomic PR so coupled suites such as CodeQL cannot drift across versions. Pull-request CI also rebuilds the package and verifies the tracked repository-owner acceptance record before running install/package audit stages. Source release creation additionally requires successful push-triggered CI, CodeQL, Governance, and Scorecard runs for the exact `main` commit.
103
106
 
104
107
  ## Test design rules
105
108
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
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",
@@ -47,7 +47,7 @@
47
47
  "worker:types": "node scripts/generate-worker-types.mjs",
48
48
  "typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit && npm run typecheck:local",
49
49
  "syntax": "node scripts/syntax-check.mjs",
50
- "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run worker-deployment:test && npm run relay:test && npm run secure-file:test && npm run worker-secret-file:test && npm run sarif-security:test && npm run security-properties:test && npm run shell:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run worker-oauth-controller:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-boundaries:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run cli-service:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run lint:test && npm run lint && npm run typecheck && npm run syntax && npm run deadline:test && npm run process-lock:test && npm run service-lifecycle:test && npm run service-environment:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run install:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test && npm run oauth-browser:test",
50
+ "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release:acceptance:test && npm run release-state:test && npm run release-ci:test && npm run network-retry:test && npm run worker-deployment:test && npm run relay:test && npm run secure-file:test && npm run worker-secret-file:test && npm run sarif-security:test && npm run security-properties:test && npm run shell:test && npm run architecture:test && npm run markdown:test && npm run project-metadata:test && npm run numbers:test && npm run records:test && npm run state-inventory:test && npm run commit-message:test && npm run policy:test && npm run account:test && npm run worker-oauth-controller:test && npm run policy-docs:check && npm run tool-docs:check && npm run runtime-infrastructure:test && npm run runtime-boundaries:test && npm run runtime-handlers:test && npm run cli-entrypoint:test && npm run cli-service:test && npm run lifecycle:test && npm run logging-structure:test && npm run coverage:test && npm run worker-runtime-infrastructure:test && npm run lint:test && npm run lint && npm run typecheck && npm run syntax && npm run deadline:test && npm run process-lock:test && npm run service-lifecycle:test && npm run service-environment:test && npm run service-platform:test && npm run catalog:test && npm run agent-context:test && npm run capability-ranking:test && npm run browser-command:test && npm run browser-devtools-input:test && npm run browser-service-worker:test && npm run browser-page-automation:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run install:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test && npm run oauth-browser:test",
51
51
  "worker:dry-run": "wrangler deploy --dry-run",
52
52
  "worker:integration-test": "node tests/worker-integration-test.mjs",
53
53
  "release:check": "node scripts/github-release.mjs --check",
@@ -117,12 +117,17 @@
117
117
  "typecheck:local": "tsc -p tsconfig.local.json --noEmit",
118
118
  "runtime-boundaries:test": "node tests/runtime-boundaries-test.mjs",
119
119
  "worker-oauth-controller:test": "node tests/worker-oauth-controller-test.mjs",
120
- "cli-service:test": "node tests/cli-service-test.mjs"
120
+ "cli-service:test": "node tests/cli-service-test.mjs",
121
+ "release:acceptance:test": "node tests/release-acceptance-test.mjs",
122
+ "release:candidate": "npm run check && node scripts/local-release-acceptance.mjs --prepare",
123
+ "release:accept": "node scripts/local-release-acceptance.mjs --record",
124
+ "release:acceptance:verify": "node scripts/local-release-acceptance.mjs --verify",
125
+ "github:push": "node scripts/github-push.mjs"
121
126
  },
122
127
  "dependencies": {
123
128
  "https-proxy-agent": "9.1.0",
124
129
  "proxy-from-env": "2.1.0",
125
- "wrangler": "4.111.0",
130
+ "wrangler": "4.112.0",
126
131
  "ws": "8.21.1"
127
132
  },
128
133
  "devDependencies": {
@@ -161,8 +166,8 @@
161
166
  "allowScripts": {
162
167
  "esbuild@0.28.1": true,
163
168
  "sharp@0.34.5": true,
164
- "workerd@1.20260710.1": true,
165
- "fsevents": false
169
+ "fsevents": false,
170
+ "workerd@1.20260714.1": true
166
171
  },
167
172
  "packageManager": "npm@12.0.1",
168
173
  "devEngines": {
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
7
+
8
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+
10
+ try {
11
+ const status = output("git", ["status", "--porcelain"]);
12
+ if (status) throw new Error(`working tree is not clean:\n${status}`);
13
+ const branch = output("git", ["branch", "--show-current"]);
14
+ if (!branch) throw new Error("cannot push from a detached HEAD");
15
+ if (branch === "main") throw new Error("direct pushes to main are prohibited; push a reviewed branch and merge through a pull request");
16
+
17
+ const acceptance = verifyCurrentReleaseAcceptance(root);
18
+ if (acceptance.required) {
19
+ const path = `release-acceptance/v${acceptance.metadata.package_version}.json`;
20
+ run("git", ["ls-files", "--error-unmatch", path], { capture: true });
21
+ console.log(`Verified repository-owner local acceptance for ${acceptance.metadata.filename}.`);
22
+ }
23
+
24
+ run("git", ["push", "--set-upstream", "origin", "HEAD"]);
25
+ console.log(`Pushed accepted branch ${branch} to origin.`);
26
+ } catch (error) {
27
+ console.error(`GitHub push blocked: ${error?.message || error}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ function output(command, args) {
32
+ return String(run(command, args, { capture: true }).stdout || "").trim();
33
+ }
34
+
35
+ function run(command, args, options = {}) {
36
+ const result = spawnSync(command, args, {
37
+ cwd: root,
38
+ encoding: "utf8",
39
+ env: process.env,
40
+ stdio: options.capture ? "pipe" : "inherit",
41
+ windowsHide: true,
42
+ });
43
+ if (result.error) throw result.error;
44
+ if (result.status !== 0) {
45
+ const detail = options.capture ? String(result.stderr || result.stdout).trim() : "";
46
+ throw new Error(`${command} ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`);
47
+ }
48
+ return result;
49
+ }
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
12
12
  import { runNetworkCommand } from "./network-retry.mjs";
13
13
  import { requireSuccessfulWorkflowRun } from "./release-ci.mjs";
14
14
  import { tagSyncError } from "./release-state.mjs";
15
+ import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
15
16
  import { fileURLToPath } from "node:url";
16
17
 
17
18
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -178,6 +179,7 @@ function releaseInfo(tag) {
178
179
 
179
180
  function assertCoreSync({ requireReleaseAsset }) {
180
181
  const pkg = packageMetadata();
182
+ assertLocalAcceptance();
181
183
  const tag = `v${pkg.version}`;
182
184
  const head = output("git", ["rev-parse", "HEAD"]);
183
185
  const originMain = output("git", ["rev-parse", "origin/main"]);
@@ -299,18 +301,12 @@ function publishCurrent() {
299
301
  run("npm", ["run", "check"]);
300
302
  run("npm", ["run", "version:check"]);
301
303
  ensureClean();
304
+ assertLocalAcceptance();
302
305
 
303
306
  const head = output("git", ["rev-parse", "HEAD"]);
304
307
  const originMain = output("git", ["rev-parse", "origin/main"]);
305
308
  if (head !== originMain) {
306
- const ancestor = run("git", ["merge-base", "--is-ancestor", "origin/main", "HEAD"], {
307
- capture: true,
308
- allowFailure: true,
309
- });
310
- if (ancestor.status !== 0) {
311
- fail("origin/main is not an ancestor of HEAD; refusing a non-fast-forward release");
312
- }
313
- runNetwork("git", ["push", "origin", "HEAD:main"]);
309
+ fail("HEAD does not match origin/main; local acceptance must be committed, pushed through npm run github:push, reviewed, and merged before release publication");
314
310
  }
315
311
  assertSuccessfulCi(head);
316
312
 
@@ -342,6 +338,17 @@ function publishCurrent() {
342
338
  assertCoreSync({ requireReleaseAsset: true });
343
339
  }
344
340
 
341
+ function assertLocalAcceptance() {
342
+ try {
343
+ const result = verifyCurrentReleaseAcceptance(root);
344
+ if (result.required) {
345
+ console.log(`Repository-owner local acceptance matches ${result.metadata.filename} (${result.metadata.shasum}).`);
346
+ }
347
+ } catch (error) {
348
+ fail(String(error?.message || error));
349
+ }
350
+ }
351
+
345
352
  function backfillMissingReleases() {
346
353
  ensureClean();
347
354
  fetchRemote();
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ mkdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import {
12
+ ACCEPTANCE_CONFIRMATION,
13
+ ACCEPTANCE_SCHEMA_VERSION,
14
+ acceptancePath,
15
+ packProject,
16
+ requiresLocalAcceptance,
17
+ verifyAcceptanceRecord,
18
+ verifyCurrentReleaseAcceptance,
19
+ verifyTarball,
20
+ } from "./release-acceptance.mjs";
21
+
22
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
23
+ const candidateDirectory = join(root, ".release-candidate");
24
+ const candidateManifestPath = join(candidateDirectory, "manifest.json");
25
+ const mode = process.argv[2] || "--verify";
26
+
27
+ try {
28
+ if (mode === "--prepare") prepareCandidate();
29
+ else if (mode === "--record") recordAcceptance();
30
+ else if (mode === "--verify") verifyAcceptance();
31
+ else fail("usage: node scripts/local-release-acceptance.mjs [--prepare|--record|--verify]");
32
+ } catch (error) {
33
+ fail(error?.message || error);
34
+ }
35
+
36
+ function prepareCandidate() {
37
+ const pkg = readPackage();
38
+ if (!requiresLocalAcceptance(pkg.version)) {
39
+ throw new Error(`local acceptance policy begins at ${pkg.name} 1.2.8; current version is ${pkg.version}`);
40
+ }
41
+ rmSync(candidateDirectory, { recursive: true, force: true });
42
+ mkdirSync(candidateDirectory, { recursive: true });
43
+ const metadata = packProject(root, candidateDirectory);
44
+ const manifest = {
45
+ schema_version: ACCEPTANCE_SCHEMA_VERSION,
46
+ result: "pending",
47
+ ...metadata,
48
+ prepared_at: new Date().toISOString(),
49
+ };
50
+ writeFileSync(candidateManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
51
+ const phrase = confirmationPhrase(pkg.name, pkg.version);
52
+ console.log(`Release candidate created: ${join(candidateDirectory, metadata.filename)}`);
53
+ console.log("Test this exact tarball on the maintainer machine using the documented normal startup path.");
54
+ console.log("After the repository owner confirms it works, record that confirmation with:");
55
+ console.log(`npm run release:accept -- --confirm \"${phrase}\"`);
56
+ console.log("Do not push this release-relevant branch before that command succeeds and its acceptance record is committed.");
57
+ }
58
+
59
+ function recordAcceptance() {
60
+ const pkg = readPackage();
61
+ const supplied = argumentValue("--confirm");
62
+ const expected = confirmationPhrase(pkg.name, pkg.version);
63
+ if (supplied !== expected) {
64
+ throw new Error(`owner confirmation must exactly match: ${expected}`);
65
+ }
66
+ const pending = readJson(candidateManifestPath, "release candidate manifest");
67
+ if (pending.result !== "pending" || pending.package_name !== pkg.name || pending.package_version !== pkg.version) {
68
+ throw new Error("release candidate manifest does not match the current package");
69
+ }
70
+ verifyTarball(join(candidateDirectory, pending.filename), pending);
71
+
72
+ const verificationDirectory = join(candidateDirectory, "verification");
73
+ rmSync(verificationDirectory, { recursive: true, force: true });
74
+ mkdirSync(verificationDirectory, { recursive: true });
75
+ const current = packProject(root, verificationDirectory);
76
+ for (const key of ["package_name", "package_version", "filename", "shasum", "integrity"]) {
77
+ if (pending[key] !== current[key]) {
78
+ throw new Error(`source changed after candidate preparation: ${key} no longer matches`);
79
+ }
80
+ }
81
+
82
+ const record = {
83
+ schema_version: ACCEPTANCE_SCHEMA_VERSION,
84
+ result: "passed",
85
+ confirmation: ACCEPTANCE_CONFIRMATION,
86
+ package_name: current.package_name,
87
+ package_version: current.package_version,
88
+ filename: current.filename,
89
+ shasum: current.shasum,
90
+ integrity: current.integrity,
91
+ accepted_at: new Date().toISOString(),
92
+ };
93
+ verifyAcceptanceRecord(record, current);
94
+ const path = acceptancePath(root, pkg.version);
95
+ mkdirSync(dirname(path), { recursive: true });
96
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
97
+ console.log(`Local owner acceptance recorded: ${path}`);
98
+ console.log("Commit this record with the candidate. Any packaged-file change invalidates it.");
99
+ }
100
+
101
+ function verifyAcceptance() {
102
+ const result = verifyCurrentReleaseAcceptance(root);
103
+ if (!result.required) {
104
+ console.log(`Local release acceptance is not required for ${result.version}.`);
105
+ return;
106
+ }
107
+ console.log(`Local release acceptance matches ${result.metadata.filename} (${result.metadata.shasum}).`);
108
+ }
109
+
110
+ function readPackage() {
111
+ return readJson(join(root, "package.json"), "package.json");
112
+ }
113
+
114
+ function readJson(path, label) {
115
+ try {
116
+ return JSON.parse(readFileSync(path, "utf8"));
117
+ } catch (error) {
118
+ throw new Error(`${label} is unavailable or invalid: ${error.message}`);
119
+ }
120
+ }
121
+
122
+ function argumentValue(name) {
123
+ const exact = process.argv.find((value) => value.startsWith(`${name}=`));
124
+ if (exact) return exact.slice(name.length + 1);
125
+ const index = process.argv.indexOf(name);
126
+ return index >= 0 ? process.argv[index + 1] : "";
127
+ }
128
+
129
+ function confirmationPhrase(name, version) {
130
+ return `I TESTED ${name} ${version} LOCALLY AND IT WORKS`;
131
+ }
132
+
133
+ function fail(message) {
134
+ console.error(`local release acceptance failed: ${message}`);
135
+ process.exit(1);
136
+ }