Skip to content

feat(autopublish): soldeer next-version release lifecycle (one process)#264

Merged
thedavidmeister merged 5 commits into
mainfrom
feat/autopublish-next-version-lifecycle
Jul 10, 2026
Merged

feat(autopublish): soldeer next-version release lifecycle (one process)#264
thedavidmeister merged 5 commits into
mainfrom
feat/autopublish-next-version-lifecycle

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Replaces the soldeer autopublish behaviour with the single next-version lifecycle — no mode flag, no branch. [package].version is the NEXT, unpublished version; on a content change vs the published revision the job publishes THAT version, bumps to the next, runs an optional generate hook, and commits. One merge-driven process every consumer adopts (adoption = a one-time toml bump to the next unpublished version; the generate hook is empty for repos with nothing to generate).

Changes (soldeer path only)

  • Source-only change gate. The content hash now excludes src/generated/ (per-release <tag>/ snapshots + generated aliasing libs). They're derived from source and a fresh <tag>/ dir appears every release, so hashing them would flag "changed" on every merge and republish identical bytecode forever. This also removes the old per-constant *_DEPLOYED_..._x_y_z pin-stripping: the versioned pins live in the excluded snapshots, so pinning can no longer self-trigger the bump→pin→bump loop that hack existed to prevent.
  • Publish the in-dev version. Publishes [package].version from the pre-bump tree so the package contents match the version it's published under, then bumps.
  • New soldeer-generate-cmd input (optional). Runs in the sol-shell right after the bump, before the release commit, to re-key per-release artifacts; git add -A stages its output into the born-green "Package Release" commit. Empty default = no-op.
  • Stale-toml guard. If [package].version isn't ahead of the registry (e.g. a prior run published but its bump-commit push failed), the gate fails loud with a clear action (bump the version) rather than silently re-publishing / mis-bumping — matching the 'CI never red without a clear action to green it' philosophy.
  • No soldeer-next-version toggle: there is one process, not two.

⚠️ Review-critical (can't be end-to-end tested from a branch)

Autopublish only exercises on a real push-to-main, so this is a draft for review. The parts that most warrant your eyes:

  1. Publish-before-push ordering (publish → bump-commit → tag+push) and its stale-toml recovery on a partial failure.
  2. The git add -A capture of the generate output into the release commit.
  3. The interaction with the shared Tag and push rebase step.

Suggest a sandbox-repo test-merge before this lands.

Paired change

st0x.deploy #243 (the first next-version consumer): drop soldeer-next-version, set soldeer-generate-cmd to the forge BuildPointers regen. It's the reason this input existed as a not-yet-supported call, and it merges after this.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Enhanced automated Soldeer package releases with a “next-version” lifecycle, deterministic content gating, and consistent version-based release/tagging.
    • Added an optional post-bump generate command step during release automation.
    • Extended Rust-based Rainix tooling for CI checks and dev-shell availability.
  • Bug Fixes
    • Improved reliability of content-change detection and enforcement that local versions advance beyond the latest published revision.
  • Documentation
    • Updated tooling guidance to keep substantive logic in Rust and limit CI shell usage to orchestration.

Replace the soldeer autopublish behaviour with the single next-version
lifecycle — no mode flag, no branch. `[package].version` is the NEXT,
unpublished version; on a content change vs the published revision the job
publishes THAT version, bumps to the next, runs an optional generate hook,
and commits — one merge-driven process every consumer adopts.

- **Source-only change gate.** Hash excludes `src/generated/` (per-release
  `<tag>/` snapshots + generated aliasing libs): they're derived from source
  and a fresh `<tag>/` appears every release, so hashing them would flag
  "changed" on every merge and republish identical bytecode. This also
  removes the old per-constant `*_DEPLOYED_..._x_y_z` pin-stripping — the
  versioned pins live in the excluded snapshots, so pinning can't self-trigger
  the bump→pin→bump loop the hack existed to prevent.
- **Publish the in-dev version.** Publishes `[package].version` (LOCAL) from
  the PRE-bump tree so the package contents match the version it's published
  under, then bumps to the next.
- **`soldeer-generate-cmd` hook** (new, optional). Runs right after the bump,
  before the release commit, to re-key per-release artifacts; staged into the
  "Package Release" commit so the bump lands born-green. Empty = no-op.
- **Stale-toml guard.** If `[package].version` isn't ahead of the registry
  (e.g. a prior run published but its bump-commit push failed), fail loud with
  a clear action (bump the version) rather than silently re-publish / mis-bump.
- Remove `soldeer-next-version` framing — there is one process, not two.

REVIEW-CRITICAL (can't be end-to-end tested from a branch — autopublish only
runs on push-to-main): the publish-before-push ordering + its stale-toml
recovery, and the `git add -A` capture of the generate output. Suggest a
sandbox-repo test-merge before this lands. Paired st0x change: drop
`soldeer-next-version`, set `soldeer-generate-cmd` to the forge BuildPointers
regen (st0x.deploy#243).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The rainix-static Rust CLI now provides Soldeer content gating and modular subcommands. The workflow publishes the current version before bumping and regenerating the next version, while flake tooling and repository guidance support Rust-based release logic.

Changes

Soldeer release tooling

Layer / File(s) Summary
Rust CLI subcommands and checks
rainix-static/src/main.rs, rainix-static/src/no_submodules.rs
Adds subcommand dispatch, shared argument/error handling, and modular no-submodules detection with gitlink tests.
Rust Soldeer gate implementation
rainix-static/Cargo.toml, rainix-static/src/soldeer_gate.rs
Adds registry and package handling, normalized content hashing, version operations, GitHub output emission, subprocess helpers, and unit tests.
Release tooling environment
flake.nix, CLAUDE.md
Adds curl and rainix-static to shell inputs and documents Rust binaries as the location for substantive tooling logic.
Workflow publish and version lifecycle
.github/workflows/rainix-autopublish.yaml
Replaces inline gate logic with rainix-static, publishes the current version, bumps and optionally regenerates the next version, and uses the current version for tags and releases.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant RainixStatic
  participant SoldeerRegistry
  participant Forge
  participant Repository
  GitHubActions->>RainixStatic: run soldeer-gate
  RainixStatic->>SoldeerRegistry: fetch published revision metadata
  RainixStatic->>Forge: create dry-run package zip
  Forge-->>RainixStatic: local package contents
  RainixStatic-->>GitHubActions: changed, version, next
  GitHubActions->>Repository: publish current version
  GitHubActions->>Repository: bump, regenerate, stage, and commit next version
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a Soldeer next-version release lifecycle for autopublish.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/autopublish-next-version-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister
thedavidmeister marked this pull request as ready for review July 10, 2026 09:23
thedavidmeister and others added 3 commits July 10, 2026 09:35
The soldeer content gate used a base64'd Python hash helper + python3 for the
registry-JSON parse and version math — the same ambient-python3 reliance the
next-version work is eliminating elsewhere (st0x #243's forge codegen; rainix
#261). rainix is the Rust/nix toolchain; the gate shouldn't lean on Python.

- Content hash is now a small inline bash `nh()` — unzip + find (excluding
  src/generated/) + blank foundry.toml version + sort + sha256sum — mirroring
  the pure-bash `norm_hash` the cargo gate directly above already uses.
- Registry JSON parsed with `jq` instead of `python3 -c`.
- Version bump + the ahead-of-registry compare done in bash (`IFS=. read`,
  `sort -V`).

No behaviour change; zero Python in the soldeer path. (The cargo gate's own
python3 JSON parse is pre-existing and out of scope here.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oling

The Soldeer content gate was inline bash (jq/awk/sha256sum/sort -V). Move all
its logic into a Rust `rainix-static soldeer-gate` subcommand: read
foundry.toml, parse the registry JSON, enforce the next-version ahead-invariant,
normalize-hash both package zips (exclude src/generated/**, blank the version
line, byte-sorted name\0content -> SHA-256), semver-bump, emit changed/version/
next. Only `forge soldeer push --dry-run` and the two HTTP GETs are external-tool
calls, so curl joins sol-build-inputs.

rainix-static broadens from "static checks" to general rainix tooling: on PATH
in every shell (common-shell-inputs). The workflow gate is now one line that runs
it inside sol-shell. CLAUDE.md documents the rule this enforces: tooling is Rust,
never Python or bash logic.

- 14 unit tests (norm-hash bump-invariance / generated-exclusion / order-
  independence / source-detection, semver parse+compare+bump, registry parse,
  foundry read) run in the flake build's doCheck.
- `nix flake check` + CI-way sol-shell closure test green: the added binary does
  not leak the rust toolchain into the slim sol-shell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rainix-static/src/main.rs`:
- Around line 243-246: Handle the `None` result from `curl_stdout` separately
instead of converting it to `"{}"`: propagate or retain the fetch failure, and
fail the workflow when the local project has a version but the registry cannot
be reached. Only treat a successfully fetched, valid empty registry response as
a first-ever publish, preserving the existing version checks around
`parse_registry` and the next-version gate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 773fba21-3fc2-45d0-9c1d-e48f8485fe96

📥 Commits

Reviewing files that changed from the base of the PR and between 0d86e55 and 0c2a923.

⛔ Files ignored due to path filters (1)
  • rainix-static/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • .github/workflows/rainix-autopublish.yaml
  • CLAUDE.md
  • flake.nix
  • rainix-static/Cargo.toml
  • rainix-static/src/main.rs

Comment thread rainix-static/src/main.rs Outdated
Comment on lines +243 to +246
let json = curl_stdout(&format!(
"https://api.soldeer.xyz/api/v1/revision?project_name={pkg}&offset=0&limit=1"
))
.unwrap_or_else(|| "{}".to_string());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Registry fetch failure is indistinguishable from "nothing published" — can cause silent re-publish.

curl_stdout returns None on any failure (network error, timeout, non-2xx), and unwrap_or("{}".to_string()) converts that to empty-registry JSON. parse_registry("{}") then yields (None, None), so the next-version invariant check at lines 253-260 is skipped entirely. If the registry is temporarily unreachable but a prior run already published the current in-dev version (e.g., its bump-commit push failed), the gate would see changed=true (old_hash="none") and the workflow would re-publish the same version to Soldeer.

Consider distinguishing fetch failure from empty-registry by propagating the Option from curl_stdout and failing when the registry is unreachable but a local version exists, while still allowing first-ever publishes when the registry returns a valid empty response.

🛡️ Proposed fix
     // Latest published revision (version + zip url); {} on any fetch failure.
-    let json = curl_stdout(&format!(
+    let json = match curl_stdout(&format!(
         "https://api.soldeer.xyz/api/v1/revision?project_name={pkg}&offset=0&limit=1"
-    ))
-    .unwrap_or_else(|| "{}".to_string());
+    )) {
+        Some(body) => body,
+        None => fail(&format!(
+            "soldeer-gate: cannot reach Soldeer registry to check published revisions; \
+             refusing to proceed without knowing the published state (possible re-publish)."
+         )),
+    };
     let (remote, url) = parse_registry(&json);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let json = curl_stdout(&format!(
"https://api.soldeer.xyz/api/v1/revision?project_name={pkg}&offset=0&limit=1"
))
.unwrap_or_else(|| "{}".to_string());
let json = match curl_stdout(&format!(
"https://api.soldeer.xyz/api/v1/revision?project_name={pkg}&offset=0&limit=1"
)) {
Some(body) => body,
None => fail(&format!(
"soldeer-gate: cannot reach Soldeer registry to check published revisions; \
refusing to proceed without knowing the published state (possible re-publish)."
)),
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rainix-static/src/main.rs` around lines 243 - 246, Handle the `None` result
from `curl_stdout` separately instead of converting it to `"{}"`: propagate or
retain the fetch failure, and fail the workflow when the local project has a
version but the registry cannot be reached. Only treat a successfully fetched,
valid empty registry response as a first-ever publish, preserving the existing
version checks around `parse_registry` and the next-version gate.

@thedavidmeister thedavidmeister added the ai:design AI vetter: raises a design question label Jul 10, 2026
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

🤖 ai:vetter
Reviewed 0c2a923: design — implements st0x.deploy#244's next-version lifecycle (paired with st0x.deploy#243) — Rust soldeer-gate + workflow rewiring are locally sound (fail-closed version parse, publish-before-bump, loud stale-toml guard), but this is an org-wide release-process decision: every soldeer consumer must one-time bump foundry.toml or its next content merge fails the gate; and rainix-static/curl enter sol-shell only in this PR, so the gate is command-not-found until the post-merge RAINIX_SHA bump (sequencing: land flake change + sha bump before the workflow switch, or sandbox test-merge as the body itself requests) — human ruling on the lifecycle before this lands
cost 812 — org-wide publish lifecycle, rust gate, bootstrap ordering

main.rs becomes a thin dispatcher (arg parse + crate::fail); the two subcommands
move to their own modules — no_submodules.rs (static check) and soldeer_gate.rs
(the Soldeer content gate). Pure refactor, no behavior change: the 14 unit tests
move with their code and still pass; clippy/fmt/reuse clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rainix-static/src/soldeer_gate.rs`:
- Around line 198-210: Add a finite curl timeout to both network call sites:
include a suitable --max-time value in the curl arguments used for the published
zip download in the old_hash calculation and the registry API fetch near the
corresponding registry request, preserving the existing flags and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20410bcd-6457-42da-9a68-021617df44c6

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2a923 and deb7a4f.

📒 Files selected for processing (3)
  • rainix-static/src/main.rs
  • rainix-static/src/no_submodules.rs
  • rainix-static/src/soldeer_gate.rs

Comment on lines +198 to +210
let old_hash = match (&remote, url.as_deref()) {
(Some(_), Some(u)) if !u.is_empty() => {
let tmp = std::env::temp_dir().join("soldeer_pub.zip");
run_cmd(
Command::new("curl").args(["-fsSL", u, "-o"]).arg(&tmp),
"curl published zip",
);
let mut pub_entries = read_zip(&tmp);
let _ = std::fs::remove_file(&tmp);
norm_hash(&mut pub_entries)
}
_ => "none".to_string(),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add --max-time to curl commands to prevent indefinite hangs.

Neither the published-zip download (line 202) nor the registry API fetch (line 253) sets a curl timeout. If the Soldeer API or download server accepts the connection but never responds, the gate blocks until the GitHub Actions job timeout, wasting CI minutes. Adding --max-time is a one-line fix per call site.

⏱️ Proposed fix
 fn curl_stdout(url: &str) -> Option<String> {
-    let out = Command::new("curl").args(["-fsSL", url]).output().ok()?;
+    let out = Command::new("curl").args(["-fsSL", "--max-time", "30", url]).output().ok()?;

And for the published zip download:

             run_cmd(
-                Command::new("curl").args(["-fsSL", u, "-o"]).arg(&tmp),
+                Command::new("curl").args(["-fsSL", "--max-time", "60", u, "-o"]).arg(&tmp),
                 "curl published zip",
             );

Also applies to: 251-257

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rainix-static/src/soldeer_gate.rs` around lines 198 - 210, Add a finite curl
timeout to both network call sites: include a suitable --max-time value in the
curl arguments used for the published zip download in the old_hash calculation
and the registry API fetch near the corresponding registry request, preserving
the existing flags and behavior.

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed deb7a4f: Soldeer content gate ported from inline bash (jq/awk/sha256sum/sort -V) to a Rust rainix-static soldeer-gate subcommand; rainix-static broadened to general rainix tooling on every shell's PATH (via common-shell-inputs), curl added to sol-build-inputs, and main.rs split into no_submodules.rs + soldeer_gate.rs modules. All gate logic (foundry.toml read, registry JSON parse, next-version ahead-invariant, normalize-hash with src/generated exclusion + version blanking, semver bump) is pure Rust with 14 unit tests running in the flake build's doCheck. Validated locally: nix flake check --impure, nix build .#rainix-static, CI-way sol-shell closure test (no rust-toolchain leak), reuse lint. CI green on ubuntu + macos. Maintainer approved merge ("merge it"). Follow-up after merge: bump RAINIX_SHA to the merge commit so the pinned sol-shell carries curl + the binary.

@thedavidmeister
thedavidmeister merged commit 53e96a7 into main Jul 10, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

SIZE=L

This was referenced Jul 10, 2026
thedavidmeister added a commit to S01-Issuer/st0x.deploy that referenced this pull request Jul 10, 2026
The paired rainix PR (rainlanguage/rainix#264) merged, so reconcile this repo's
package-release call and version with the final contract:

- Drop `soldeer-next-version: true` — that input was removed from the reusable;
  the next-version lifecycle is implicit whenever `soldeer-package` is set.
  Passing a now-undefined input would fail the reusable at startup.
- `soldeer-generate-cmd` is now bare `forge script ./script/BuildPointers.sol` —
  the reusable already runs it inside its own PINNED sol-shell, so the nested
  `nix develop github:rainlanguage/rainix#sol-shell` was redundant and
  reintroduced an unpinned (429-prone) flake ref.
- Bump foundry.toml [package].version 0.1.5 -> 0.1.6: 0.1.5 is now the published
  revision, so the in-dev version must be the next unpublished one (the new
  ahead-invariant fails loud otherwise).
- Regenerate: new src/generated/0_1_6/ snapshot + LibProdDeploy{V4,Current}.
  Deterministic (idempotent re-run = no diff); bytecode identical to 0_1_5 since
  the contract source is unchanged. LibProdDeployV4.t: 37/37 pass (tagged
  keccak(RUNTIME)==CODEHASH incl 0_1_6, frozen-redeploy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:design AI vetter: raises a design question

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant