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/CHANGELOG.md +21 -0
- package/CONTRIBUTING.md +26 -19
- package/README.md +15 -4
- package/SECURITY.md +3 -3
- package/browser-extension/manifest.json +2 -2
- package/docs/ARCHITECTURE.md +10 -8
- package/docs/AUDIT.md +26 -0
- package/docs/ENGINEERING.md +2 -2
- package/docs/OPERATIONS.md +5 -3
- package/docs/PROJECT_STANDARDS.md +9 -5
- package/docs/RELEASING.md +64 -32
- package/docs/TESTING.md +7 -4
- package/package.json +11 -6
- package/scripts/github-push.mjs +49 -0
- package/scripts/github-release.mjs +15 -8
- package/scripts/local-release-acceptance.mjs +136 -0
- package/scripts/release-acceptance.mjs +169 -0
- package/scripts/release-impact-check.mjs +19 -3
- package/src/local/browser-bridge.mjs +67 -22
- package/src/local/cli-local-admin.mjs +2 -3
- package/src/local/daemon-process.mjs +39 -4
- package/src/local/execution-limits.mjs +36 -0
- package/src/local/job-runner.mjs +5 -8
- package/src/local/loopback-health.mjs +67 -0
- package/src/local/managed-jobs.mjs +22 -9
- package/src/local/process-contract.mjs +19 -0
- package/src/local/process-execution.mjs +11 -10
- package/src/local/process-sessions.mjs +25 -50
- package/src/local/process-tracker.mjs +1 -1
- package/src/local/process-tree.mjs +53 -0
- package/src/local/runtime-reporting.mjs +3 -1
- package/src/local/runtime.mjs +4 -5
- package/src/local/shell.mjs +2 -19
- package/src/worker/index.ts +1 -1
package/docs/RELEASING.md
CHANGED
|
@@ -2,58 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
The release invariant is:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
16
|
+
## Change classification
|
|
16
17
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
22
|
+
## Prepare the candidate locally
|
|
22
23
|
|
|
23
|
-
1. Set the new version without creating
|
|
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
|
|
31
|
-
4. Inspect the complete diff
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
+
Required local access:
|
|
74
104
|
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
165
|
-
"
|
|
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
|
-
|
|
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
|
+
}
|