Skip to content

feat(npm): distribute the ant CLI via npm as @withautonomi/ant - #192

Open
jacderida wants to merge 1 commit into
WithAutonomi:mainfrom
jacderida:chrisoneil/v2-1152-distribute-the-ant-cli-via-npm
Open

jacderida wants to merge 1 commit into
WithAutonomi:mainfrom
jacderida:chrisoneil/v2-1152-distribute-the-ant-cli-via-npm

Conversation

@jacderida

@jacderida jacderida commented Sep 4, 2026

Copy link
Copy Markdown
Member

Linear issue

Closes V2-1152

https://linear.app/autonominetwork/issue/V2-1152/distribute-the-ant-cli-via-npm
(mirrors #190)

Risk tier

  • T0 — docs / tooling / CI / pure UX-output. Repo CI only.
  • T1 — client-only, no network-facing behavior change. CI + prod compat smoke.
  • T2 — node/client logic with behavioral surface, no protocol/format/economics change. Dev testnet + ADR.
  • T3 — protocol / storage format / payments / routing. T2 evidence + adversarial testing.

Mostly packaging and CI, which alone would be T0. Proposed as T1 because two
client-side behaviours do change: ant update refuses to self-replace on a
package-manager-owned install, and ant now falls back to a bootstrap peer list
compiled into the binary when no config file is present. Neither touches node
behaviour, the wire protocol, stored-data format, or payments. The upgrade
mechanism is narrowed rather than altered — self-replacement is unchanged for
every install that exists today, and is only declined where it would corrupt a
package manager's view of its own files.

Compatibility

  • Wire: none.
  • Storage: none.
  • API: additive only. New: ant_core::install (InstallMethod, detect,
    classify_path) and ant_core::update::DeferredUpdate. No existing signature
    changed. ant_core::update::perform_update gains an early error return for
    package-manager-owned installs — a new failure mode on a path that previously
    always attempted replacement. config::resolve_bootstrap_multiaddrs gains a
    fallback tier: where it previously returned Error::NoBootstrapPeers with no
    config file, it now returns the seeds already bundled by network_defaults.

Semver impact

  • breaking
  • feature
  • fix

Test evidence

Rebased and reworked onto current main (2026-09-22)

The branch was 118 commits behind and conflicting. main had independently moved
bootstrap_peers.toml to ant-core/resources/, changed its format to quic/webrtc
multiaddresses, and added ant-core/src/network_defaults.rs, which already embeds that
file with include_str! and exposes bundled_bootstrap_seeds() for the WASM build.

Rather than resolve four conflicts, the branch was rebuilt on top of main and only the
still-wanted parts re-applied. That removed work rather than adding it: the file move,
the three workflow cp path edits, a bespoke EMBEDDED_BOOTSTRAP_PEERS constant with its
own parser and tests, an ant-core/examples/bench-quoting.rs edit, and a rustdoc fix for a
link on the constant that no longer exists. The remaining fallback is a handful of lines
reusing bundled_bootstrap_seeds().

Gates run locally on the reworked tree:

Command Result
cargo fmt --all -- --check clean
cargo clippy --all-targets --all-features -- -D warnings clean
cargo doc --all-features --no-deps with RUSTDOCFLAGS="-D warnings" clean
cargo test --lib --all 724 passed, 0 failed
cargo test -p ant-core --test unit_self_encrypt 16 passed, 2 ignored
cargo test -p ant-core --test merkle_unit 8 passed
cargo test -p ant-core --test daemon_integration --test node_add_integration -- --test-threads=1 6 + 3 passed

The devnet-backed e2e suites are left to CI, which runs them in their own jobs.

Tests covering this change: 11 in ant-core/src/install.rs (npm global, npm local, nested
node_modules, Windows paths, install.sh/cargo/local-build locations, and a directory
whose name merely contains the substring). The fallback's own invariants are already
covered on mainnetwork_defaults asserts the bundled seeds parse and are non-empty,
and three config tests assert that an explicitly selected devnet manifest never falls
back.

npm behaviour, verified against a local registry

Done before the rework, against a verdaccio registry with a scratch npm_config_prefix
and XDG_CONFIG_HOME. The npm packaging is unchanged by the rework.

Acceptance criterion Result
AC1 — npm install -g @withautonomi/ant gives a working ant --version pass (ant 0.3.6)
AC2 — published binaries byte-identical to the release assets pass (cmp clean, sha256 7d7847f1…)
AC3 — npm uninstall -g removes everything except the config directory pass

Beyond the ACs:

  • npm 12 blocks install scripts by default (allow-scripts is empty out of the box),
    so a plain install does not write bootstrap_peers.toml. With postinstall blocked,
    ant still resolved peers and connected. This is the reason the fallback exists.
  • With --allow-scripts, the postinstall wrote the file, and a reinstall over a
    user-edited copy left it untouched.
  • ant update --json on the npm-installed binary returned "install_method": "npm" with
    "update_command": "npm update -g @withautonomi/ant"; --force explained the refusal
    rather than silently doing nothing.

dist-tag behaviour on a pre-release debut

npm sets latest from a package's first publish even when --tag names something else,
and later pre-release publishes move only their own tag:

first publish of 0.3.7-beta.1 with --tag beta
  -> {'latest': '0.3.7-beta.1', 'beta': '0.3.7-beta.1'}
then publish 0.3.8-beta.1 with --tag beta
  -> {'latest': '0.3.7-beta.1', 'beta': '0.3.8-beta.1'}

A bare install against that registry resolved to the beta. Publishing a stable 0.3.7 with
--tag latest afterwards reclaimed the tag cleanly. Hence the debut guard. Its dist-tag
mapping was replayed offline across 0.3.7, -beta.N, -rc.N, -alpha.N and
-betamax.N against both 200 and 404 registry responses, and the registry probe checked
against the real registry (@withautonomi/ant → 404, @stripe/cli → 200).

Not verified locally: cross-platform binaries (only the host target was built; the other
four packages carried a stand-in binary), the real npm registry, OIDC provenance, and the
publish job itself. install-test.yml gains four workflow_dispatch jobs — npm install on
Linux/macOS/Windows plus an --ignore-scripts job — to cover those against a real published
version.

New dependency

None — no new Rust crates. The npm packages declare no runtime dependencies;
the launcher and postinstall use only Node's standard library. Node ≥18 becomes a
requirement for the npm install path only, and the release workflow gains
actions/setup-node.

ADR

n/a — Tier 1.

Mitigation / rollback

The publish job is the last job in the release workflow and runs only after the
GitHub release has been created, so a failure there cannot affect the release. It
also skips itself with a warning when NPM_TOKEN is absent, so merging this before
the token is configured cannot turn a release red. Backing the distribution out
means deprecating the npm packages and deleting the job; no released artifact
changes.

The two client-side behaviours are independently reversible. The ant update
deferral only triggers on a node_modules path — no existing install can reach it.
The embedded peer fallback only runs where the previous code returned
Error::NoBootstrapPeers, so it can turn a hard failure into a success but never
change the outcome of a currently-working configuration.

🤖 Generated with Claude Code

https://claude.ai/code/session_012H3LsaffUstGkhHQdZeQD3

@jacderida
jacderida force-pushed the chrisoneil/v2-1152-distribute-the-ant-cli-via-npm branch from e458702 to 0d1b820 Compare September 8, 2026 16:11
Agent sandboxes and locked-down CI allow package-manager traffic by default
while blocking direct binary downloads — Claude Code cloud sessions return 403
for release assets of repos not attached to the session, and skill-directory
scanners flag piped installers while passing package managers. This makes
getting `ant` onto such a machine a one-liner.

Packaging only: nothing is rebuilt. The npm tarballs carry the same binaries
the release workflow has already built and signed, verified against
SHA256SUMS.txt and their ML-DSA-65 signatures before packaging, so they are
byte-identical to the GitHub release assets. install.sh, install.ps1 and the
release page are untouched.

Six packages, following the mechanism esbuild established: a meta package plus
one per platform, guarded by os/cpu and declared as optionalDependencies, so
npm skips the four that do not match and a user downloads exactly one binary.
A JS launcher resolves and execs it rather than a postinstall copying it into
place, because install scripts cannot be relied on (below). Published from
ant-cli-release.yml after the GitHub release, with provenance via OIDC and a
dist-tag derived from the version: latest, beta, or rc.

A pre-release may not be the package's first publish. npm sets `latest` from a
package's very first publish even when --tag names something else, and later
pre-release publishes move only their own tag, so a beta debut would pin
`latest` to that beta until a stable release moved it — and the plain
`npm install -g @withautonomi/ant` would serve it. Both behaviours were
verified against a local registry. The publish job asks the registry whether
the package exists and skips a pre-release publish when it does not; the
condition clears itself once a stable release has gone out.

`ant update` now recognises a package-manager-owned install and defers to it.
Self-replacing an npm-managed binary left npm's metadata describing a file that
was no longer there, so the next `npm update` silently rolled the user back.
The check still reports whether a newer version exists, then prints
`npm update -g @withautonomi/ant`; a second guard in ant-core refuses
self-replacement for any other consumer of the crate. Detection is by
resolved-path inspection rather than a build flag, since a build flag would
produce a binary that is not byte-identical to the release asset.

Finally, `config::resolve_bootstrap_multiaddrs` falls back to the seeds already
bundled into the binary by `network_defaults` when no bootstrap_peers.toml is
present, instead of returning NoBootstrapPeers. npm 12 blocks package install
scripts by default, so the postinstall that copies that file into the config
directory frequently does not run, which would otherwise leave a CLI that
installs and reports its version but fails every network command — in exactly
the sandboxes this change targets. Priority is unchanged (explicit -b peers,
then a devnet manifest, then the config file, then the bundled seeds), and an
explicitly selected devnet manifest still errors rather than reaching for
mainnet peers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012H3LsaffUstGkhHQdZeQD3
@jacderida
jacderida force-pushed the chrisoneil/v2-1152-distribute-the-ant-cli-via-npm branch from 05157f1 to b70d808 Compare September 22, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant