Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ jobs:
name: npm-package
path: packages/agent-cli/artifacts/*
if-no-files-found: error
# npm trusted publishing does not support self-hosted runners
# (https://docs.npmjs.com/trusted-publishers/). This job authenticates with
# the organization NPM_TOKEN secret instead. That drops OIDC trusted
# publishing and automatic provenance.
publish:
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && vars.BLOXWAP_PUBLISH == '1'
Expand All @@ -101,7 +105,8 @@ jobs:
cancel-in-progress: false
permissions:
contents: write
id-token: write
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -115,6 +120,8 @@ jobs:
bun-version: '1.3.10'
- name: Recheck tagged main ancestry before publishing
run: node packages/agent-cli/scripts/verify-release.mjs
- name: Verify npm publish authentication
run: node packages/agent-cli/scripts/check-publish-auth.mjs
- uses: actions/download-artifact@v4
with:
pattern: native-*
Expand All @@ -126,11 +133,9 @@ jobs:
path: release
- name: Prepare release checksums
run: node packages/agent-cli/scripts/release-checksums.mjs release
- name: Publish the tested tarball with provenance
- name: Publish the tested tarball
run: node packages/agent-cli/scripts/publish-artifact.mjs release
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Verify published integrity and provenance
- name: Verify published integrity
run: node packages/agent-cli/scripts/verify-publication.mjs release
- name: Verify public npm install and bunx
run: npm run test:package
Expand Down
4 changes: 2 additions & 2 deletions docs/ACCEPTANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ Node 22.12 minimum.
| #33–#36 | New approval verified before signer handoff/old revocation, resumable rotation and immediate local revocation tests; fsynced cross-process hash-chain with intent/decision/signature digest/response/fill linkage; tamper/redaction tests and linked full-lifecycle records. CLI/RPC pre-schema hex/mnemonic detector and threat model. |
| #37–#38 | Skill source/frontmatter/error-catalog tests and every documented command input parsed through actual CLI schemas offline; user-owned Privy, ordered external approvals, bounded polling, exchange reconciliation and supported-test-route guidance; install/custody README, generated command reference and static Pages site. The skill deliverable is complete; the unproven coding-agent testnet session is tracked explicitly in #39. |
| #39 | Offline CLI/SDK/RPC lifecycle plus fifteen-stage testnet harness with external wallet approval adapter or separate CI-only disposable signer; exact prices, correlated events, post-rotation trading, partial-rotation cleanup and zero orders/agents/positions verification. Schema 2 reports/CI summaries validate stage and service evidence; Relay remains mocked. Missing configuration fails explicitly. Nightly live evidence and the independent coding-agent skill session remain pending. |
| #40–#42 | Script-free packed global installation and installed bunx smoke, executable bin, license and matching skill versions; Node 22.12/24 install CI; Linux/macOS ARM/x64 native binary smoke; changesets/tagged-main/version/provenance/asset pipeline with candidate-only manual runs. Publishing and public registry verification await npm configuration. |
| #40–#42 | Script-free packed global installation and installed bunx smoke, executable bin, license and matching skill versions; Node 22.12/24 install CI; Linux/macOS ARM/x64 native binary smoke; changesets/tagged-main/version/token-publish/asset pipeline with candidate-only manual runs. Publishing and public registry verification await the `BLOXWAP_PUBLISH` gate. |
| #49 | Token guard rejects declarations anywhere in formatted/minified CSS. Unit regression and a mutation of global.css prove docs:check rejects the issue's exact same-line copy. |

No package publication or credentialed account trading is claimed by this review.
Close the live criteria only with an actual run ID, commit, lifecycle report and
event/audit artifacts, or a published version with verified integrity/provenance
event/audit artifacts, or a published version with verified integrity
and public installation evidence. Mocks, missing-secret failures and candidate
artifacts are not substitutes for those records.
4 changes: 2 additions & 2 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
| #38 | done | Install/testnet README, custody architecture, schema-generated command reference and static docs site with gated Pages workflow. |
| #39 | partial | Fifteen-stage testnet harness supports external wallet signing and separate CI-only disposable keys, exact exchange reconciliation, correlated WebSocket/follow/replay events, post-rotation trading, audit verification, partial-rotation cleanup and zero orders/agents/positions checks. Schema 2 reports and CI summaries require evidence. Offline regressions pass; nightly live journals and the independent coding-agent skill session transferred from #37 still require dedicated QA configuration and evidence. |
| #40 | partial | Script-free tarball global install and actual installed bunx help/schema smoke pass locally; executable/skill/license checks and Node 22.12/24 macOS/Linux CI. Public registry installation awaits publication. |
| #41 | partial | Validated Changesets patch workflow with optional version PR, native binary smoke/checksums, tagged-main version gates, exact tested tarball publication with provenance and registry verification. Manual candidates cannot publish. Actual release needs npm authentication and the publishing gate; organization policy blocks automatic version PRs, so the default workflow uploads a reviewable patch. |
| #41 | partial | Validated Changesets patch workflow with optional version PR, native binary smoke/checksums, tagged-main version gates, exact tested tarball publication and registry verification. Publish runs on the Mac mini fleet with the organization `NPM_TOKEN` (no OIDC trusted publishing or automatic provenance). Manual candidates cannot publish. Actual release needs the publishing gate; organization policy blocks automatic version PRs, so the default workflow uploads a reviewable patch. |
| #42 | done | Final packaged flow audited and corrected (network/config isolation, exact loss/exposure/receipt checks, trigger preservation, readiness and stream errors). Skill shipped/embedded in npm and compiled CLI; Claude/Codex installation with CODEX_HOME, version synchronization, idempotency and overwrite protection. |
| #49 | done | Docs token guard checks declarations anywhere on a line; regression coverage includes formatted and minified radius, motion, density and type copies. The issue's same-line mutation makes docs:check fail. |

Expand Down Expand Up @@ -74,7 +74,7 @@ account trading were used for this review.

The validated version-patch workflow prepared 0.2.0, consuming the pending
changesets and synchronizing package/lock/skill versions and generated docs.
This is release preparation; public npm installation/provenance is still pending.
This is release preparation; public npm installation is still pending.

## Testnet harness — 2026-10-18

Expand Down
20 changes: 18 additions & 2 deletions docs/ci-runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ The labels are case-sensitive. Register each runner with exactly `self-hosted`,
`macOS`, and `ARM64`. Do not use GitHub-hosted `ubuntu-*` or `macos-*` images.

Jobs may stay queued when the fleet is busy. That is expected: the Mac minis are
the only runners.
the only runners. Register them in runner group **JWS** as macOS user `admin`.

## Trusted publishing is unavailable

[npm trusted publishing does not support self-hosted runners](https://docs.npmjs.com/trusted-publishers/).
The release `publish` job therefore authenticates with the organization
`NPM_TOKEN` secret (`NODE_AUTH_TOKEN`). That **drops OIDC trusted publishing
and automatic provenance**. Do not bind a trusted publisher to `release.yml`.

GitHub Pages deploy still uses OIDC (`id-token: write`) on the minis.

## Required software

Expand All @@ -24,10 +33,16 @@ Install these on the runner account. Workflows never use `sudo` or `apt`.
| GitHub Actions runner | Must stay logged in as a user that can run Colima and Docker without privilege escalation |
| Xcode Command Line Tools | `git`, `clang`, `tar`, `unzip`, `curl` |
| Homebrew + `gnu-tar` | `actions/upload-pages-artifact` needs `gtar` (`brew install gnu-tar`) |
| `gh` | Optional local debugging; already on the JWS minis |
| `gnupg` | Already on the JWS minis |
| Colima + Docker CLI | Linux containers for binary and install smokes |
| Node.js 24 | Workflows also pin Node with `actions/setup-node` |
| Bun 1.3.10 | Native compiles and package smokes; workflows also pin Bun with `oven-sh/setup-bun` |

`actions/setup-python` cannot install on these minis (`/Users/runner` is not
writable). This repository does not need Python; if a job ever does, use
Homebrew `[email protected]` on `PATH` instead.

One-time Colima VM (persistent; later jobs run `colima status || colima start`):

```sh
Expand Down Expand Up @@ -94,7 +109,8 @@ The runner needs outbound access to:

## How jobs use the Mac mini

- **CI / version / docs deploy / npm publish** run natively on darwin-arm64.
- **CI / version / docs deploy** run natively on darwin-arm64. **npm publish**
also runs natively and uses `NPM_TOKEN` (no provenance).
- **Release** cross-compiles `linux-x64`, `linux-arm64`, `darwin-x64`, and
`darwin-arm64` with Bun on the Mac. Linux binaries are smoke-tested in
`linux/amd64` and `linux/arm64` containers. The `darwin-x64` binary is
Expand Down
42 changes: 18 additions & 24 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,24 @@ binary uses Bun's baseline target; binaries do not load incidental `.env` or

## Configure publishing

[npm trusted publishing does not support self-hosted runners](https://docs.npmjs.com/trusted-publishers/).
The gated publish job therefore authenticates with the organization `NPM_TOKEN`
secret as `NODE_AUTH_TOKEN`. That **drops OIDC trusted publishing and automatic
provenance**. Do not bind a trusted publisher to `release.yml` on these Mac minis.

As of 2026-10-16, `@bloxwap/agent-cli` is not publicly available on npm (registry
404), no `BLOXWAP_PUBLISH` variable or `NPM_TOKEN` secret is configured, and the
`npm-release` GitHub environment does not exist. The install and publication
criteria remain pending until an actual public release passes registry smoke.
No version tag, npm publication or GitHub release was created by this audit.
404) and the `npm-release` GitHub environment / `BLOXWAP_PUBLISH` gate were not
configured. The organization now shares a granular `NPM_TOKEN` (publish rights
for `@bloxwap` packages) with this repository. Publication still awaits the
environment and `BLOXWAP_PUBLISH=1` gate, plus an actual public registry smoke.

Before enabling publication:

1. Confirm npm write access to the `@bloxwap` scope and the package name. Configure
a granular npm token as the `NPM_TOKEN` secret in the `npm-release` environment
for the initial publication, or configure an authorized npm trusted publisher.
A local `npm whoami` does not verify OIDC trusted-publisher permissions.
1. Confirm the organization `NPM_TOKEN` secret is available to this repository
and can publish `@bloxwap/agent-cli`. The publish job checks the token
(`whoami`, then collaborator write access, or a first-publish 404).
2. Create the `npm-release` GitHub environment and apply the desired tag/reviewer
restrictions before setting the repository variable `BLOXWAP_PUBLISH=1`.
3. Prefer npm trusted publishing after the package exists. Bind it to organization
`bloxwap`, repository `agent-cli`, workflow **`release.yml`**, and environment
**`npm-release`**, with direct `npm publish` allowed. Node 24 includes a supported
npm CLI (trusted publishing requires npm 11.5.1+ and Node 22.14+). Do not leave a
token configured after migrating fully to OIDC. New trusted publisher bindings
must complete their first successful publish within npm's activation window.

See the current [npm trusted publishing documentation](https://docs.npmjs.com/trusted-publishers/)
for scope ownership, activation and authentication details. The package's
repository metadata identifies the public GitHub source required for provenance.

## Publish a reviewed version

Expand All @@ -81,9 +75,9 @@ The workflow rejects mismatched, prerelease or unreviewed branch tags. A tag wit
publishing disabled still builds candidate artifacts; enabling the gate later
requires rerunning the existing tag's workflow, not moving the tag.

After all four native candidates succeed, the gated publish job downloads the
exact tested tarball, computes `SHA256SUMS`, publishes it with `--provenance`, and
verifies registry integrity plus the provenance attestation. It then tests a
After the candidate job succeeds, the gated publish job downloads the
exact tested tarball, computes `SHA256SUMS`, publishes it with `NPM_TOKEN`, and
verifies registry integrity. It then tests a
public global npm install and bunx registry execution before creating generated
GitHub release notes and attaching the npm tarball, four binaries, SKILL.md,
CHANGELOG.md, LICENSE and checksums. Downloaded binaries need `chmod +x` before
Expand All @@ -93,6 +87,6 @@ execution. Verify downloads with `sha256sum -c SHA256SUMS` (Linux) or
A published npm version is immutable. If publication succeeds but a later step
fails, inspect the registry and failure before rerunning the job; keep the tag in
place. The publish step accepts an existing version only when its registry hash
matches the tested archive and its provenance is present, then continues the
remaining release steps. Authentication and network errors never count as an
unpublished version. A conflicting existing archive stops the workflow.
matches the tested archive, then continues the remaining release steps.
Authentication and network errors never count as an unpublished version. A
conflicting existing archive stops the workflow.
83 changes: 83 additions & 0 deletions packages/agent-cli/scripts/check-publish-auth.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Verify the token npm publish will use, even when the current version is
// already published. Never print or persist the token.
// npm trusted publishing is not available on self-hosted runners:
// https://docs.npmjs.com/trusted-publishers/
import { readFile } from 'node:fs/promises';
import { pathToFileURL } from 'node:url';

export function readPublishToken(env = process.env) {
const token = env.NODE_AUTH_TOKEN || env.NPM_TOKEN;
if (typeof token !== 'string' || token.length === 0) {
throw new Error(
'NPM_TOKEN is missing. Set the organization NPM_TOKEN secret with permission to publish @bloxwap/agent-cli.',
);
}
return token;
}

export function assertCollaboratorWrite(name, username, access) {
const permission = access?.[username];
if (
permission !== 'write' &&
permission !== 'read-write' &&
permission !== true
) {
throw new Error(`npm token for ${username} cannot publish ${name}.`);
}
}

export async function verifyPublishAuth({
token = readPublishToken(),
name,
fetchImpl = fetch,
} = {}) {
const whoami = await fetchImpl('https://registry.npmjs.org/-/whoami', {
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(30_000),
});
if (!whoami.ok) {
throw new Error(
`npm token authentication failed (HTTP ${whoami.status}). Check the organization NPM_TOKEN secret.`,
);
}
const identity = await whoami.json();
if (typeof identity.username !== 'string' || !identity.username) {
throw new Error('npm whoami did not return a username.');
}

const escapedName = name.replace('/', '%2f');
const collaborators = await fetchImpl(
`https://registry.npmjs.org/-/package/${escapedName}/collaborators`,
{
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(30_000),
},
);
if (collaborators.status === 404) {
return {
username: identity.username,
unpublished: true,
message: `npm accepted the organization NPM_TOKEN for ${name} as ${identity.username}; the package is not on the registry yet.`,
};
}
if (!collaborators.ok) {
throw new Error(
`npm collaborator check failed (HTTP ${collaborators.status}). The NPM_TOKEN secret must be able to publish ${name}.`,
);
}
const access = await collaborators.json();
assertCollaboratorWrite(name, identity.username, access);
return {
username: identity.username,
unpublished: false,
message: `npm accepted the organization NPM_TOKEN for ${name} as ${identity.username}.`,
};
}

if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
const { name } = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8'),
);
const result = await verifyPublishAuth({ name });
console.log(result.message);
}
5 changes: 4 additions & 1 deletion packages/agent-cli/scripts/publication.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ export function readRegistryDist(spec, run = execFileSync) {
return JSON.parse(output);
}

export function npmPublishArgs(tarball) {
return ['publish', tarball, '--access', 'public', '--ignore-scripts'];
}

export function verifyPublication(dist, integrity) {
assert.equal(
dist?.integrity,
integrity,
'Published tarball differs from tested release artifact',
);
assert.ok(dist?.attestations?.url, 'npm provenance attestation is missing');
}
16 changes: 3 additions & 13 deletions packages/agent-cli/scripts/publish-artifact.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { execFileSync } from 'node:child_process';
import {
npmPublishArgs,
readRegistryDist,
releaseArtifact,
verifyPublication,
Expand All @@ -10,19 +11,8 @@ const published = readRegistryDist(artifact.spec);
if (published) {
verifyPublication(published, artifact.integrity);
console.log(
`Already published identical provenanced artifact: ${artifact.spec}; continuing release`,
`Already published identical artifact: ${artifact.spec}; continuing release`,
);
} else {
execFileSync(
'npm',
[
'publish',
artifact.tarball,
'--provenance',
'--access',
'public',
'--ignore-scripts',
],
{ stdio: 'inherit' },
);
execFileSync('npm', npmPublishArgs(artifact.tarball), { stdio: 'inherit' });
}
2 changes: 1 addition & 1 deletion packages/agent-cli/scripts/verify-publication.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ for (let attempt = 0; attempt < 6; attempt++) {
try {
verifyPublication(readRegistryDist(artifact.spec), artifact.integrity);
console.log(
`PASS: ${artifact.spec} registry integrity and provenance attestation`,
`PASS: ${artifact.spec} registry integrity`,
);
failure = undefined;
break;
Expand Down
Loading
Loading