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
116 changes: 57 additions & 59 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
shell: bash
run: rustup show active-toolchain || rustup toolchain install

- name: Resolve version
id: ver
run: |
Expand Down Expand Up @@ -224,66 +220,68 @@ jobs:
--verify-tag
fi

- name: Setup Node
uses: actions/setup-node@v4
# Packaging and the npm publish are owned by the shared action, which
# also verifies what the registry serves afterwards. It reads
# artifacts/<rust-target>/quire[.exe] (the layout normalized above).
# static-musl: the Linux binaries are fully static musl builds that run
# on glibc as well, so their packages carry no libc restriction.
# Public npm via OIDC Trusted Publishing (id-token: write above); the
# NPM_TOKEN secret is only a first-publish bootstrap and may be unset.
- name: Package and publish the npm distribution
uses: agent-ix/nodejs-actions/publish-native-npm@main
with:
node-version: "22" # OIDC Trusted Publishing requires Node >= 22.14
package: "@agent-ix/quire-cli"
binary: quire
version: ${{ steps.ver.outputs.version }}
artifacts-dir: artifacts
repository: ${{ github.repository }}
description: "Static CLI over quire-rs (parse, extract, lookup, edit, validate, schema, lint) for Markdown artifacts."
self-update-command: update
static-musl: "true"
npm-token: ${{ secrets.NPM_TOKEN }}
publish: ${{ inputs.publish }}

# OIDC Trusted Publishing needs npm >= 11.5.1.
- name: Upgrade npm for Trusted Publishing
run: npm install -g npm@11

# Public npm via OIDC: route @agent-ix at registry.npmjs.org with no auth
# token. `npm publish` performs the tokenless OIDC token exchange (the
# id-token: write permission above supplies the GitHub OIDC token).
# Written to $HOME so every `npm publish` (run from each package dir) picks it up.
# Public npm. Steady state is tokenless OIDC (id-token: write above). The
# NPM_TOKEN fallback exists only to BOOTSTRAP: npm requires a package to
# exist before a trusted publisher can be attached, and these 5 packages'
# binaries can only be built by this CI matrix. Add NPM_TOKEN as a repo
# secret for the first release, configure the trusted publishers on
# npmjs.com, then DELETE the secret — every later release uses OIDC.
# provenance=false: this repo is private, where provenance is unsupported.
- name: Configure npm for public npm
- name: Smoke the published npm packages
if: inputs.publish
shell: bash
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ steps.ver.outputs.version }}
run: |
if [ -n "$NPM_TOKEN" ]; then
printf 'registry=https://registry.npmjs.org/\n@agent-ix:registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=%s\nprovenance=false\n' "$NPM_TOKEN" > "$HOME/.npmrc"
else
printf 'registry=https://registry.npmjs.org/\n@agent-ix:registry=https://registry.npmjs.org/\nprovenance=false\n' > "$HOME/.npmrc"
fi
set -euo pipefail

- name: Generate per-platform npm packages
run: |
cargo run --locked -p quire-dist -- verify-release \
--version "${{ steps.ver.outputs.version }}"
cargo run --locked -p quire-dist -- package-npm \
--version "${{ steps.ver.outputs.version }}" \
--artifacts-dir artifacts
PREFIX="$(mktemp -d)"
NPMRC="$(mktemp)"
printf '@agent-ix:registry=https://registry.npmjs.org/\n' > "$NPMRC"
export NPM_CONFIG_USERCONFIG="$NPMRC"
export NPM_CONFIG_PREFIX="$PREFIX"
export PATH="$PREFIX/bin:$PATH"

- name: Publish to public npm
if: inputs.publish
run: |
set -e
# Platform packages first so the launcher's optionalDependencies resolve.
for pkg in npm/dist/*/; do
echo "publishing $pkg"
(cd "$pkg" && npm publish)
# Public npm can take a little while to serve a just-published
# version; retry with backoff rather than failing on propagation.
attempt=0
max_attempts=8
until npm install --global "@agent-ix/quire-cli@${VERSION}"; do
attempt=$((attempt + 1))
if [ "$attempt" -ge "$max_attempts" ]; then
echo "::error::npm install of @agent-ix/quire-cli@${VERSION} did not succeed after $attempt attempts" >&2
exit 1
fi
sleep 15
done
echo "publishing launcher"
(cd npm/quire-cli && npm publish)

# PLAT-885: package-npm only ever checked what it wrote locally.
# Publishing is a separate step that can partially fail (the launcher's
# own `npm publish` erroring after the platform packages already
# succeeded) or diverge from local state entirely -- this asserts what
# the registry actually now serves, reading the launcher's published
# optionalDependencies from the registry itself, not from the working
# tree.
- name: Verify published packages resolve
if: inputs.publish
run: |
cargo run --locked -p quire-dist -- verify-published \
--version "${{ steps.ver.outputs.version }}" \
--registry https://registry.npmjs.org/
actual_version="$(quire --version | cut -d' ' -f2)"
if [ "$actual_version" != "$VERSION" ]; then
echo "::error::quire --version printed '$actual_version', expected '$VERSION'" >&2
exit 1
fi

if quire update 2>update.err; then
echo "::error::quire update exited 0 under an npm install; it must defer to npm instead" >&2
cat update.err >&2
exit 1
fi
if ! grep -q "npm install -g @agent-ix/quire-cli@latest" update.err; then
echo "::error::quire update's stderr did not name the exact npm command to run" >&2
cat update.err >&2
exit 1
fi
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ output schemas (see `spec/non-functional/NFR-006-cli-stability.md`).

## [Unreleased]

- **npm publishing moves to the shared `publish-native-npm` action (PLAT-1012).** `release.yml` now publishes `@agent-ix/quire-cli` and its four platform packages through `agent-ix/nodejs-actions/publish-native-npm` (`static-musl`, so the Linux packages carry no libc restriction). The repository-owned launcher `npm/quire-cli/bin/quire.js`, `quire-dist package-npm`, `verify-published`, `set-version` and `verify-release`, and the `make dist-package`, `dist-verify`, `dist-verify-published` and `set-version` targets are removed; `quire-dist verify-binary` stays. The documented local `npm.ix` publish path, which depended on those commands, is removed. FR-023, FR-024 and NFR-008 are removed, FR-022 and US-007 are rewritten, and ADR-0003 supersedes ADR-0002.

- **Breaking: digests and hashes removed from outputs.** `quire properties --json` no longer emits `statement_hash` on a criterion obligation. The `quire assurance` v1 export locator is `{path, line}` only (no `digest`), obligations carry no `statement_hash`, and symbol ids are JSON-array identity parts rather than SHA-256 hex. `quire clauses` human binding output no longer prints a clause-set digest. Follows quire-rs assurance-v1 edited in place.

## [0.38.3] - 2026-11-04
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ ownership in the engine.
- Does any new logic belong upstream in `quire-rs`?
- Does the change preserve stdout/stderr and exit-code contracts?
- Are new behavioral claims backed by executable tests and `make spec`?
- Does npm distribution policy remain in the Rust `quire-dist` tool, with the
ADR-0002 Node launcher limited to transparent host selection and execution?
- Does npm packaging stay in the shared `publish-native-npm` action (ADR-0003),
with no packaging or launcher code in this repository?
- Were `make ci` and the relevant release build run locally?
18 changes: 0 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 1 addition & 29 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,7 @@ help:
@echo " make clean - cargo clean"
@echo " make deny - cargo deny check (advisories, bans, licenses, sources)"
@echo " make audit-unsafe - Enforce // SAFETY: comments on unsafe blocks"
@echo " make dist-test - Test native npm distribution tooling"
@echo " make dist-verify - Verify committed Cargo/npm release versions"
@echo " make dist-package - Build npm packages from artifacts/"
@echo " make dist-verify-published VERSION=X.Y.Z REGISTRY=<url> - Post-publish"
@echo " gate: the published launcher and every platform"
@echo " package its optionalDependencies declares"
@echo " resolve on REGISTRY"
@echo " make set-version VERSION=X.Y.Z - Update Cargo/npm versions"
@echo " make dist-test - Test the release tooling (verify-binary)"
@echo " make bench - Latency budget (NFR-001): p95 of a quire invocation ≤ 50 ms (needs hyperfine)"
@echo " make ci - All CI gates locally, including specification traceability"

Expand Down Expand Up @@ -76,27 +69,6 @@ cargo-audit:
dist-test:
$(CARGO) test --locked -p quire-dist

.PHONY: dist-verify
dist-verify:
$(CARGO) run --locked -p quire-dist -- verify-release --version $(VERSION)

.PHONY: dist-package
dist-package:
$(CARGO) run --locked -p quire-dist -- package-npm --version $(VERSION)

# Post-publish gate (PLAT-885): after `npm publish` for every generated
# platform package and the launcher, assert each optionalDependency the
# launcher declares actually resolves at its pinned version on REGISTRY.
# Not part of `make ci` — like dist-verify/dist-package, it runs against a
# real release and a real registry, never in the offline unit-test lane.
.PHONY: dist-verify-published
dist-verify-published:
$(CARGO) run --locked -p quire-dist -- verify-published --version $(VERSION) --registry $(REGISTRY)

.PHONY: set-version
set-version:
$(CARGO) run --locked -p quire-dist -- set-version --version $(VERSION)

.PHONY: audit-unsafe
audit-unsafe:
$(CARGO) run --locked -p quire-qualify -- unsafe-comments
Expand Down
120 changes: 11 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,122 +363,24 @@ make deny # cargo deny check licenses
make deny-bans # cargo deny check bans
make audit-unsafe # every unsafe block carries a // SAFETY: comment
make audit-thin-boundary # src/ stays a thin wrapper over quire-rs
make dist-test # Rust package/launcher tests, including offline npm
make dist-verify VERSION=0.32.0 # check Cargo/npm release-version agreement
make set-version VERSION=0.33.0 # update Cargo/npm versions in lockstep
make dist-package VERSION=0.32.0 # generate npm/dist from artifacts/
make dist-test # release tooling tests (verify-binary)
make refresh-fixtures # re-sync tests/fixtures/iso from ../quire-rs
make ci # local CI gauntlet
```

The release binary is audited to link only baseline system libraries on
Linux. See `tests/audit_ldd.rs`.

## Local publish to npm.ix

This is the procedure for the local, manual publish to the internal `npm.ix`
registry. It exists so the publish is repeatable and the steps are written
down. It is not what keeps a release correct. Two checks in `quire-dist` do
that, whichever way the publish is run:

- `package-npm` writes the launcher's `optionalDependencies` from the
executables actually present in `artifacts/`, one entry per platform built.
It cannot write a pin for a platform it did not build, so a partial local
build produces a smaller, consistent package set.
- `make dist-verify-published` reads the launcher as the registry serves it
and fails unless every platform package it declares resolves there.

The manually dispatched `.github/workflows/release.yml` is the other publish
path. It targets public npm, builds all four platforms in one run, and runs the
same `verify-release`, `package-npm` and `verify-published` steps. The commands
below are the local equivalent for one host.

Prerequisites: a clean checkout of the commit being released, a Rust toolchain
(see `rust-toolchain.toml`), `npm` on `PATH`, and an `~/.npmrc` auth token for
`//npm.ix/`. Set `VERSION` to the release version (`X.Y.Z`, no leading `v`).

1. Set the version and commit it. `set-version` updates `Cargo.toml` and
`npm/quire-cli/package.json` together. It does not touch `Cargo.lock`, and
the `--locked` builds below need the lock to agree, so refresh the
workspace entries (this does not move any dependency).

```bash
make set-version VERSION=$VERSION
cargo update --workspace
make dist-verify VERSION=$VERSION
git commit -am "release: $VERSION"
git status --porcelain # must print nothing
```

Commit before you build. The binary records the source revision and
whether the tree was clean, and `0.32.2` shipped a binary that reported a
dirty tree and a commit that no longer existed (see `CHANGELOG.md`,
`0.32.3`).

2. Build the release binary for each platform this machine can build, and
confirm it reports `VERSION`. Use the Rust target names from `TARGETS` in
`tools/quire-dist/src/lib.rs`; `aarch64-apple-darwin` is the example here.

```bash
TARGET=aarch64-apple-darwin
cargo build --locked --release --target $TARGET
cargo run --locked -p quire-dist -- verify-binary \
--version $VERSION --binary target/$TARGET/release/quire
```

3. Stage each binary as `artifacts/<rust-target>/quire` (`quire.exe` for
Windows). The directory must hold only that file, and only catalog target
names are accepted.

```bash
rm -rf artifacts && mkdir -p artifacts/$TARGET
cp target/$TARGET/release/quire artifacts/$TARGET/quire
```

4. Generate the npm packages. `package-npm` checks each executable's format
and rewrites `npm/quire-cli/package.json` (so the working tree is dirty
after this step) and `npm/dist/`.

```bash
make dist-test
make dist-package VERSION=$VERSION
git diff npm/quire-cli/package.json # optionalDependencies = platforms built
```

5. Publish the platform packages, then the launcher. Every generated
`package.json` carries `publishConfig.registry` set to
`https://registry.npmjs.org/`, and `~/.npmrc` may scope `@agent-ix` to a
different registry. Pass the scope override explicitly, as `quire-dist`
itself does when it queries a registry, and run `--dry-run` first to read
the target registry from npm's own output before the real publish.

```bash
REGISTRY=http://npm.ix/
for pkg in npm/dist/*/; do
(cd "$pkg" && npm publish --dry-run --@agent-ix:registry=$REGISTRY)
done
(cd npm/quire-cli && npm publish --dry-run --@agent-ix:registry=$REGISTRY)
# when the dry runs name $REGISTRY, repeat without --dry-run
```

Platform packages go first so the launcher's `optionalDependencies`
resolve as soon as it appears.

6. Check what the registry now serves.

```bash
make dist-verify-published VERSION=$VERSION REGISTRY=$REGISTRY
```

A failure here means the registry and the package set disagree. Do not
reuse the version; publish a new one.

7. Restore the working tree. `npm/dist/` is ignored; delete `artifacts/`. The
rewritten `npm/quire-cli/package.json` lists only the platforms this run
built, so do not commit it; `git checkout npm/quire-cli` restores it.

This procedure has not been walked end to end against a live registry as
written. Record the first full run here, including any step that had to change.
## Releasing

The manually dispatched `.github/workflows/release.yml` builds the four target
binaries, creates the GitHub Release, and publishes to public npm through the
shared `agent-ix/nodejs-actions/publish-native-npm` action (ADR-0003). The
action owns package layout, the `npm publish` calls and the post-publish
registry check; this repository carries no npm packaging code. Set the version
in `Cargo.toml`, refresh the lock with `cargo update --workspace`, commit, and
dispatch the workflow with `publish` left false for a dry run first. Each built
binary is checked with `quire-dist verify-binary --version X.Y.Z --binary <path>`.

## Spec And Plan

Expand Down
5 changes: 0 additions & 5 deletions npm/.gitignore

This file was deleted.

Loading
Loading