Skip to content

Upstream the 'every published soldeer version has its frozen snapshot' check to rainix #271

Description

@thedavidmeister

Problem

The invariant "every version published to soldeer has its frozen deploy snapshot committed" is currently enforced (badly) per-repo, and it fails silently.

rain.math.float hand-rolls it as a forge test that FFIs a bash script which curls the soldeer registry:

  • test/src/lib/deploy/LibDecimalFloatDeployTaggedConstants.t.sol → vm.ffi(["bash", "script/check-published-deploy-constants.sh"])
  • the script prints OK / MISSING: <names> / SKIP: <reason> (registry unreachable)
  • on SKIP the test calls vm.skip(true)

This is self-contradictory: rainix's own no-ignored-tests gate bans exactly that vm.skip ("a test either runs and passes, or it is deleted"), and it currently reds rain.math.float main on both rs-static and rainix-sol / static. The repo cannot satisfy both rainix gates at once — the network-tolerance escape hatch the check needs is the thing the other gate forbids.

Removing the vm.skip doesn't fix it either: whatever replaces it (an early return) still means the test silently verifies nothing whenever the registry is unreachable. A forge unit test structurally cannot verify remote state.

Ask

Upstream the check itself to rainix: for a package's published soldeer versions, assert that every one of them has its committed frozen snapshot (src/generated/<tag>/). Fail CI when any published version lacks its snapshot.

The soldeer registry is the ground truth for "what is published", so the check queries it — and that network dependency is precisely why this belongs in rainix rather than in a consumer's test suite: in a CI action, "registry unreachable" is a loud, retryable failure; in a forge test it can only be a silent skip. That asymmetry is the whole point.

Why rainix

  • It's a repo-agnostic release invariant, not a unit test. Every soldeer-publishing repo needs it.
  • rainix already ships the natural sibling: .github/actions/frozen-snapshots-append-only (polices src/generated/<tag>/ as append-only). The pair reads cleanly — snapshots are never rewritten + every published version has one. Running this alongside it (e.g. from rainix-sol-static) is the obvious placement.
  • Consumer repos have zero local CI logic by convention (every workflow is a thin reusable caller), so there is nowhere correct to put this repo-side anyway.

Evidence it fails in practice

rain-math-float 0.1.7 was published to soldeer on 2026-07-14T18:54:19Z while LibDecimalFloatDeploy.sol pins only the _0_1_1 family. Published set {0.1.1, 0.1.7} vs pinned set {0.1.1}. Nothing stopped it, and the local test only reports it after the fact — and skips entirely when offline.

Note the publish fired from a markdown-only merge (an audit/*.md deletion), which suggests the autopublish content gate may warrant a look of its own.

Optional strengthening (in addition, NOT instead)

rainix-autopublish could additionally assert, before forge soldeer push, that the version it is about to publish has its committed snapshot — making an unpinned publish impossible rather than merely detectable.

This is a complement, not a substitute: it only ever knows about the one version being published, so it cannot see history. A publish-time gate added today would not flag that rain-math-float 0.1.7 is already published unpinned. The completeness check above is what covers that, and is the thing being asked for here.

Consumers

Once this lands, consumers delete their hand-rolled equivalents — rain.math.float's script/check-published-deploy-constants.sh + LibDecimalFloatDeployTaggedConstants.t.sol — which also clears the vm.skip gate conflict.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions