From fcb3ca62857ccb610611bc2a00e1554020bdcd6e Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 06:08:26 -0500 Subject: [PATCH 1/7] ci: add snarkvm upgrade pre-check script --- .github/scripts/snarkvm_check.sh | 70 ++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 .github/scripts/snarkvm_check.sh diff --git a/.github/scripts/snarkvm_check.sh b/.github/scripts/snarkvm_check.sh new file mode 100755 index 00000000..f11f6068 --- /dev/null +++ b/.github/scripts/snarkvm_check.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Is the SDK's snarkvm pin behind the latest upstream release, and is an +# upgrade PR already open? +# +# Output (stdout `key=value`; also appended to $GITHUB_OUTPUT when set): +# current=v4.8.1 SDK's pinned snarkvm version +# latest=v4.8.1 newest bare vX.Y.Z tag upstream (no testnet-/canary-) +# crates_io=true|false is `latest` published on crates.io? +# needs_upgrade=true|false +# +# Usage: snarkvm_check.sh [--current vX.Y.Z] +# --current overrides pin detection (testing / workflow force_from_tag). +set -euo pipefail + +REPO_URL="https://github.com/ProvableHQ/snarkVM" +CARGO_TOML="$(cd "$(dirname "$0")/../.." && pwd)/sdk/Cargo.toml" + +current="" +if [[ "${1:-}" == "--current" ]]; then + current="${2:?--current requires a tag argument}" +else + dep_line=$(grep -m1 '^snarkvm = {' "$CARGO_TOML") + if [[ $dep_line =~ tag\ =\ \"(v[0-9]+\.[0-9]+\.[0-9]+)\" ]]; then + current="${BASH_REMATCH[1]}" + elif [[ $dep_line =~ version\ =\ \"([0-9]+\.[0-9]+\.[0-9]+)\" ]]; then + current="v${BASH_REMATCH[1]}" + else + echo "error: could not parse snarkvm pin from $CARGO_TOML" >&2 + exit 1 + fi +fi + +latest=$(git ls-remote --tags "$REPO_URL" \ + | awk '{print $2}' \ + | sed 's|^refs/tags/||; s|\^{}$||' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -uV | tail -1) +if [[ -z "$latest" ]]; then + echo "error: no vX.Y.Z release tags found on $REPO_URL" >&2 + exit 1 +fi + +crates_io=false +if curl -fsS -o /dev/null -A "ProvableHQ/python-sdk snarkvm-upgrade check" \ + "https://crates.io/api/v1/crates/snarkvm/${latest#v}"; then + crates_io=true +fi + +# If an upgrade PR for `latest` is already open, don't re-attempt daily. +# Fail-soft when gh is unavailable/unauthenticated: worst case the skill's +# own `gh pr create` collides on the existing head branch and errors there. +pr_open=0 +if command -v gh >/dev/null 2>&1; then + pr_open=$(gh pr list --head "snarkvm-upgrade/${latest}" --state open \ + --json number --jq length 2>/dev/null || echo 0) +fi + +needs_upgrade=false +if [[ "$current" != "$latest" && "$pr_open" == "0" ]]; then + needs_upgrade=true +fi + +out="current=$current +latest=$latest +crates_io=$crates_io +needs_upgrade=$needs_upgrade" +echo "$out" +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "$out" >> "$GITHUB_OUTPUT" +fi From f8ff245e41ea7537254aa88ad2676b71df8e3cf1 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 06:09:14 -0500 Subject: [PATCH 2/7] feat: add /upgrade-snarkvm skill --- .agents/skills/upgrade-snarkvm/SKILL.md | 168 ++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 .agents/skills/upgrade-snarkvm/SKILL.md diff --git a/.agents/skills/upgrade-snarkvm/SKILL.md b/.agents/skills/upgrade-snarkvm/SKILL.md new file mode 100644 index 00000000..015ba950 --- /dev/null +++ b/.agents/skills/upgrade-snarkvm/SKILL.md @@ -0,0 +1,168 @@ +--- +name: upgrade-snarkvm +description: Use when upgrading the SDK's pinned snarkVM to the latest upstream release — compares tags, adapts sdk/ and sdk-abi/ to the changeset, runs the native validation loop, bumps PyPI package versions, and opens a PR. Invoked as /upgrade-snarkvm interactively or by the snarkvm-upgrade workflow. +--- + +# Upgrade snarkVM + +Upgrade the pinned `snarkvm` dependency to the latest upstream release and +adapt the SDK. Same procedure interactively and in CI. + +## Hard rules + +- Plain commit messages — NO Co-Authored-By trailers, NO "Generated with" + footers. +- Every version number comes from a command output. Never guess one. +- `sdk-abi`'s snarkvm dep is type-coupled to leo: it stays in git-tag form + with the exact tag and feature set leo's pinned rev uses, or + `Process`/`Program` in leo-disassembler's signatures become distinct + types. Never give sdk-abi a crates.io-form snarkvm dep. +- Validate seriously before giving up: on failures, re-read the changeset, + fix, and re-run. Only after several genuine fix attempts do you fall back + to the draft-PR path in step 8. + +## 1. Detect + +```bash +bash .github/scripts/snarkvm_check.sh +``` + +If `needs_upgrade=false`, report why (already at `latest`, or an open PR +exists for it — check `gh pr list --head "snarkvm-upgrade/"`) and +STOP. Otherwise set CUR=, NEW=, CRATES_IO= and +continue. + +## 2. Study the changeset + +```bash +git clone --filter=blob:none https://github.com/ProvableHQ/snarkVM /tmp/snarkvm-upgrade-src +cd /tmp/snarkvm-upgrade-src +git log --oneline $CUR..$NEW +git diff --stat $CUR..$NEW -- console/ circuit/ synthesizer/ ledger/ algorithms/ utilities/ parameters/ +``` + +Diff in detail the areas the SDK binds (what `sdk/src/*.rs` and +`sdk-abi/src/*.rs` `use`): console types (Address, PrivateKey, ViewKey, +Signature, Plaintext, Record, Literal), ledger (Block, Transaction, +Transition, queries), synthesizer (Process, Program, Authorization, +execution/deployment), and anything ARC-20/ARC-22-relevant. Write a short +breaking-change inventory (API renames, signature changes, semantic changes) +BEFORE editing anything. + +## 3. Bump the pin in sdk/ + +In `sdk/Cargo.toml`, replace the `snarkvm = {` line: + +- If CRATES_IO=true — switch to the crates.io form, keeping the feature list + verbatim: + ```toml + snarkvm = { version = "X.Y.Z", default-features = false, features = [ + "console", "circuit", "synthesizer", "ledger", "utilities", "algorithms", "parameters", + ] } + ``` +- If CRATES_IO=false — keep the git form, only changing the tag: + ```toml + snarkvm = { git = "https://github.com/ProvableHQ/snarkVM.git", tag = "vX.Y.Z", default-features = false, features = [ ... ] } + ``` + +`sdk/Cargo.lock` refreshes on the next build — commit it with the change. + +## 4. Bump the pin in sdk-abi/ (leo-coupled) + +Check whether leo's default branch has picked up $NEW: + +```bash +curl -s https://raw.githubusercontent.com/ProvableHQ/leo/HEAD/Cargo.toml | grep -m1 '^snarkvm ' +``` + +- If it pins `tag = "$NEW"`: get the rev + (`git ls-remote https://github.com/ProvableHQ/leo HEAD`), then in + `sdk-abi/Cargo.toml` update EVERY leo crate's `rev = "..."` to that rev and + the snarkvm `tag = "..."` to $NEW. Keep the feature list verbatim + (`test_consensus_heights`, `dev_skip_checks`, `test_targets`, `history`) — + unless leo's own snarkvm line changed features, in which case mirror + leo's exactly. +- If leo still pins an older tag: leave `sdk-abi/` entirely untouched + (sdk and sdk-abi are separate build graphs; version skew is fine) and + record "sdk-abi held back at $CUR — leo hasn't adopted $NEW" for the PR + body. + +## 5. Adapt the code + +Fix `sdk/src/` (and `sdk-abi/src/` if bumped) guided by the step-2 +inventory. Start with `cargo check`; compile errors first, then semantics. + +## 6. Validate (native loop, escalating — all from `sdk/`) + +Interactively, work inside `sdk/.env` (`source .env/bin/activate`); in CI use +the runner's python directly. + +```bash +cd sdk +cargo fmt --check +cargo clippy --no-default-features --features mainnet -- -D warnings +cargo check --no-default-features --features testnet + +pip install maturin +maturin build --release --features mainnet --out dist +sed -i.bak 's/module-name = "aleo._aleolib_mainnet"/module-name = "aleo._aleolib_testnet"/' pyproject.toml +maturin build --release --no-default-features --features testnet --out dist-testnet +mv pyproject.toml.bak pyproject.toml +python ../.github/scripts/merge_testnet_so.py dist dist-testnet +pip install --force-reinstall dist/aleo_sdk-*.whl +pip install pytest pytest-xdist httpx pynacl responses pytest-asyncio requests + +python -m pytest python/tests -v -n auto -m "not slow and not live and not devnode" +python -m pytest python/tests -v -m slow +python -m pytest python/tests/test_testnet.py -v +``` + +Then the abi package (run these even if sdk-abi was held back — they prove +the version skew is harmless): + +```bash +cd .. +maturin build --release --manifest-path sdk-abi/Cargo.toml --out dist +pip install --force-reinstall dist/aleo_contract_abi_generator-*.whl +( cd sdk-abi && python -m pytest python/tests -v ) +( cd sdk && python -m pytest python/tests/test_abi_hook.py -v ) +# Build-graph isolation canary — leo feature flags must not leak into sdk: +test "$(grep -c dev_skip_checks sdk/Cargo.lock || true)" = "0" +``` + +On any failure: diagnose against the changeset inventory, fix, re-run the +failing stage, then re-run the full loop once everything passes. + +## 7. Bump PyPI package versions + +For each of `aleo-sdk`, `aleo-contract-abi-generator`, `shield-swap-sdk`: + +```bash +curl -fsS https://pypi.org/pypi//json | python3 -c "import json,sys; print(json.load(sys.stdin)['info']['version'])" +``` + +New version = live version with patch incremented (e.g. 0.3.0 → 0.3.1). If +any query fails, STOP the version-bump step and say so — never guess. +Apply to: +- `sdk/pyproject.toml` + `sdk/Cargo.toml` `[package] version` (aleo-sdk) +- `sdk-abi/pyproject.toml` + `sdk-abi/Cargo.toml` `[package] version` +- `shield-swap-sdk/pyproject.toml` + +Rebuild lockfiles by running `cargo check` in `sdk/` and `sdk-abi/`; commit +lockfile changes. + +## 8. PR + +```bash +git checkout -b "snarkvm-upgrade/$NEW" # or reset it if a stale branch exists without an open PR +git add -A ':!docs/superpowers' && git commit -m "deps: upgrade snarkvm to $NEW" +git push -u origin "snarkvm-upgrade/$NEW" --force-with-lease +``` + +- All green → `gh pr create --title "deps: upgrade snarkvm to $NEW" --body ...` + with: the breaking-change inventory, what was adapted, dep-form decision + (crates.io vs git, sdk-abi held back or not), validation evidence (which + suites ran, pass counts), and the version bumps. +- Genuinely stuck after repeated fix attempts → same but + `gh pr create --draft`, body additionally lists exact failing tests with + output excerpts and what was attempted. From 8f71b74323c94419b964a79bf650cc64480077a7 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 06:09:45 -0500 Subject: [PATCH 3/7] ci: daily snarkvm upgrade workflow (cron + manual) --- .github/workflows/snarkvm-upgrade.yml | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/snarkvm-upgrade.yml diff --git a/.github/workflows/snarkvm-upgrade.yml b/.github/workflows/snarkvm-upgrade.yml new file mode 100644 index 00000000..96b6118b --- /dev/null +++ b/.github/workflows/snarkvm-upgrade.yml @@ -0,0 +1,79 @@ +# Daily snarkVM upgrade check. On a new upstream release, a Claude session +# runs the /upgrade-snarkvm skill (.agents/skills/upgrade-snarkvm/SKILL.md) +# to bump, adapt, validate, and open a PR. +# +# One-time setup (repo secrets): +# ANTHROPIC_API_KEY — for claude-code-action +# SNARKVM_UPGRADE_PAT — classic PAT or app token with repo scope; used for +# checkout/push/gh so the created PR triggers +# on:pull_request CI (default GITHUB_TOKEN PRs don't) +name: snarkVM Upgrade + +on: + workflow_dispatch: + inputs: + force_from_tag: + description: "Pretend the SDK is pinned at this tag (testing, e.g. v4.8.0)" + required: false + default: "" + schedule: + - cron: "0 15 * * *" # 08:00 PDT (07:00 PST — GitHub cron is UTC-only) + +permissions: + contents: write + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + outputs: + current: ${{ steps.check.outputs.current }} + latest: ${{ steps.check.outputs.latest }} + needs_upgrade: ${{ steps.check.outputs.needs_upgrade }} + steps: + - uses: actions/checkout@v4 + - id: check + env: + GH_TOKEN: ${{ github.token }} + run: | + args=() + if [ -n "${{ inputs.force_from_tag }}" ]; then + args=(--current "${{ inputs.force_from_tag }}") + fi + bash .github/scripts/snarkvm_check.sh "${args[@]}" + + upgrade: + needs: check + if: needs.check.outputs.needs_upgrade == 'true' + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.SNARKVM_UPGRADE_PAT }} + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + sdk + sdk-abi + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + # SRS/proving parameters — same cache key as sdk.yml so it's warm. + - uses: actions/cache@v4 + with: + path: ~/.aleo + key: aleo-parameters-${{ runner.os }}-v1 + - uses: anthropics/claude-code-action@v1 + env: + GH_TOKEN: ${{ secrets.SNARKVM_UPGRADE_PAT }} + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.SNARKVM_UPGRADE_PAT }} + prompt: "/upgrade-snarkvm — upgrading ${{ needs.check.outputs.current }} -> ${{ needs.check.outputs.latest }}. You are in CI: work non-interactively to completion." + claude_args: | + --allowedTools "Bash,Read,Edit,Write,Glob,Grep" From 3797dba9b55e806bbac767ccad4c1a41a0bcecb8 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 06:31:53 -0500 Subject: [PATCH 4/7] fix: skill handles leo's crates.io snarkvm pin; prefer local snarkVM clone --- .agents/skills/upgrade-snarkvm/SKILL.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.agents/skills/upgrade-snarkvm/SKILL.md b/.agents/skills/upgrade-snarkvm/SKILL.md index 015ba950..f7997735 100644 --- a/.agents/skills/upgrade-snarkvm/SKILL.md +++ b/.agents/skills/upgrade-snarkvm/SKILL.md @@ -34,6 +34,9 @@ continue. ## 2. Study the changeset +Use an existing local snarkVM clone if one is available (fetch its tags +first); otherwise clone to a scratch dir: + ```bash git clone --filter=blob:none https://github.com/ProvableHQ/snarkVM /tmp/snarkvm-upgrade-src cd /tmp/snarkvm-upgrade-src @@ -75,14 +78,15 @@ Check whether leo's default branch has picked up $NEW: curl -s https://raw.githubusercontent.com/ProvableHQ/leo/HEAD/Cargo.toml | grep -m1 '^snarkvm ' ``` -- If it pins `tag = "$NEW"`: get the rev +- If leo's pin resolves to $NEW — either `tag = "$NEW"` or crates.io + `version = "X.Y.Z"` matching $NEW without the `v` (leo migrated to the + crates.io form around v4.8.1): get the rev (`git ls-remote https://github.com/ProvableHQ/leo HEAD`), then in - `sdk-abi/Cargo.toml` update EVERY leo crate's `rev = "..."` to that rev and - the snarkvm `tag = "..."` to $NEW. Keep the feature list verbatim - (`test_consensus_heights`, `dev_skip_checks`, `test_targets`, `history`) — - unless leo's own snarkvm line changed features, in which case mirror - leo's exactly. -- If leo still pins an older tag: leave `sdk-abi/` entirely untouched + `sdk-abi/Cargo.toml` update EVERY leo crate's `rev = "..."` to that rev + and **mirror leo's snarkvm dep line exactly** — same form (tag or + version), same feature list. Type-coupling means matching leo's source, + whatever shape it takes. +- If leo still pins an older snarkvm: leave `sdk-abi/` entirely untouched (sdk and sdk-abi are separate build graphs; version skew is fine) and record "sdk-abi held back at $CUR — leo hasn't adopted $NEW" for the PR body. From 50d70b00c53162d266f75e2c5dba1564fb0f5667 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 06:35:13 -0500 Subject: [PATCH 5/7] feat: report aleo-devnode snarkvm compatibility in upgrade PRs --- .agents/skills/upgrade-snarkvm/SKILL.md | 10 ++++++++- .github/scripts/snarkvm_check.sh | 29 ++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.agents/skills/upgrade-snarkvm/SKILL.md b/.agents/skills/upgrade-snarkvm/SKILL.md index f7997735..efa1ae7b 100644 --- a/.agents/skills/upgrade-snarkvm/SKILL.md +++ b/.agents/skills/upgrade-snarkvm/SKILL.md @@ -32,6 +32,11 @@ exists for it — check `gh pr list --head "snarkvm-upgrade/"`) and STOP. Otherwise set CUR=, NEW=, CRATES_IO= and continue. +Also note `devnode_latest`/`devnode_snarkvm`: which snarkvm the newest +aleo-devnode release pins. Purely informational — the SDK carries no devnode +pin (the binary comes from PATH and `-m devnode` tests skip in CI) — but the +PR body must say whether the devnode matches $NEW (step 8). + ## 2. Study the changeset Use an existing local snarkVM clone if one is available (fetch its tags @@ -166,7 +171,10 @@ git push -u origin "snarkvm-upgrade/$NEW" --force-with-lease - All green → `gh pr create --title "deps: upgrade snarkvm to $NEW" --body ...` with: the breaking-change inventory, what was adapted, dep-form decision (crates.io vs git, sdk-abi held back or not), validation evidence (which - suites ran, pass counts), and the version bumps. + suites ran, pass counts), the version bumps, and a devnode-compatibility + line: "aleo-devnode pins snarkvm — + matches $NEW" or "— lags $NEW: local `-m devnode` e2e tests may skip on + fee/consensus skew until a devnode release adopts it". - Genuinely stuck after repeated fix attempts → same but `gh pr create --draft`, body additionally lists exact failing tests with output excerpts and what was attempted. diff --git a/.github/scripts/snarkvm_check.sh b/.github/scripts/snarkvm_check.sh index f11f6068..874ced24 100755 --- a/.github/scripts/snarkvm_check.sh +++ b/.github/scripts/snarkvm_check.sh @@ -7,6 +7,9 @@ # latest=v4.8.1 newest bare vX.Y.Z tag upstream (no testnet-/canary-) # crates_io=true|false is `latest` published on crates.io? # needs_upgrade=true|false +# devnode_latest=v0.2.0 newest aleo-devnode release (informational — +# devnode_snarkvm=v4.8.1 the SDK carries no devnode pin; this tells the +# upgrade PR whether the devnode will skew) # # Usage: snarkvm_check.sh [--current vX.Y.Z] # --current overrides pin detection (testing / workflow force_from_tag). @@ -60,10 +63,34 @@ if [[ "$current" != "$latest" && "$pr_open" == "0" ]]; then needs_upgrade=true fi +# aleo-devnode compatibility (informational only — never gates the upgrade). +# Which snarkvm does the latest devnode release pin? Handles both git-tag and +# crates.io dep forms; fail-soft to "unknown" on network/parse trouble. +DEVNODE_URL="https://github.com/ProvableHQ/aleo-devnode" +devnode_latest=$(git ls-remote --tags "$DEVNODE_URL" 2>/dev/null \ + | awk '{print $2}' \ + | sed 's|^refs/tags/||; s|\^{}$||' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -uV | tail -1 || true) +devnode_snarkvm=unknown +if [[ -n "$devnode_latest" ]]; then + devnode_dep=$(curl -fsS \ + "https://raw.githubusercontent.com/ProvableHQ/aleo-devnode/${devnode_latest}/Cargo.toml" \ + 2>/dev/null | grep -m1 '^snarkvm ' || true) + if [[ $devnode_dep =~ tag\ =\ \"(v[0-9]+\.[0-9]+\.[0-9]+)\" ]]; then + devnode_snarkvm="${BASH_REMATCH[1]}" + elif [[ $devnode_dep =~ version\ =\ \"([0-9]+\.[0-9]+\.[0-9]+)\" ]]; then + devnode_snarkvm="v${BASH_REMATCH[1]}" + fi +fi +devnode_latest="${devnode_latest:-unknown}" + out="current=$current latest=$latest crates_io=$crates_io -needs_upgrade=$needs_upgrade" +needs_upgrade=$needs_upgrade +devnode_latest=$devnode_latest +devnode_snarkvm=$devnode_snarkvm" echo "$out" if [[ -n "${GITHUB_OUTPUT:-}" ]]; then echo "$out" >> "$GITHUB_OUTPUT" From 2f9838a45027cd578d913960fd7b8c0f53c06645 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 15:20:39 -0500 Subject: [PATCH 6/7] fix: resolve dep-form contradiction, fail-soft greps, concurrency guard - SKILL.md hard rules contradicted step 4 on sdk-abi's dep form; replace with the actual policy (prefer crates.io when it matches the latest tag, else git tag) plus sdk-abi's source-identity constraint and a lockfile canary - fail-soft the two greps whose failure exits under set -e, making the friendly parse/no-tags errors reachable - add a concurrency group so a dispatch can't race the cron on one branch - keep testnet-/canary- prefixes when reporting the devnode's snarkvm tag --- .agents/skills/upgrade-snarkvm/SKILL.md | 21 ++++++++++++++++----- .github/scripts/snarkvm_check.sh | 11 ++++++++--- .github/workflows/snarkvm-upgrade.yml | 7 +++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.agents/skills/upgrade-snarkvm/SKILL.md b/.agents/skills/upgrade-snarkvm/SKILL.md index efa1ae7b..97885418 100644 --- a/.agents/skills/upgrade-snarkvm/SKILL.md +++ b/.agents/skills/upgrade-snarkvm/SKILL.md @@ -13,10 +13,17 @@ adapt the SDK. Same procedure interactively and in CI. - Plain commit messages — NO Co-Authored-By trailers, NO "Generated with" footers. - Every version number comes from a command output. Never guess one. -- `sdk-abi`'s snarkvm dep is type-coupled to leo: it stays in git-tag form - with the exact tag and feature set leo's pinned rev uses, or - `Process`/`Program` in leo-disassembler's signatures become distinct - types. Never give sdk-abi a crates.io-form snarkvm dep. +- Dependency form follows one policy: prefer the crates.io release when its + version equals the latest GitHub tag; otherwise pin the git tag. The + pre-check's `crates_io` output already answers this — don't re-derive it. +- `sdk-abi` carries an extra constraint on top of that policy: its snarkvm + dep must resolve to the **same source** as leo's. Cargo compiles the same + crate from two different sources twice, so registry `4.8.1` and + `tag = "v4.8.1"` are distinct crates and `Process`/`Program` in + leo-disassembler's signatures stop unifying with the SDK's. Mirror leo's + snarkvm dep line exactly — same source, version/tag, and features — and + never pick sdk-abi's form independently. Verify after building: + `grep -c '^name = "snarkvm"' sdk-abi/Cargo.lock` must be 1. - Validate seriously before giving up: on failures, re-read the changeset, fix, and re-run. Only after several genuine fix attempts do you fall back to the draft-PR path in step 8. @@ -59,7 +66,9 @@ BEFORE editing anything. ## 3. Bump the pin in sdk/ -In `sdk/Cargo.toml`, replace the `snarkvm = {` line: +In `sdk/Cargo.toml`, replace the `snarkvm = {` line. CRATES_IO=true means +crates.io publishes a release whose version equals $NEW; false means the tag +is ahead of the registry, so the git tag is the only way to get $NEW. - If CRATES_IO=true — switch to the crates.io form, keeping the feature list verbatim: @@ -137,6 +146,8 @@ pip install --force-reinstall dist/aleo_contract_abi_generator-*.whl ( cd sdk && python -m pytest python/tests/test_abi_hook.py -v ) # Build-graph isolation canary — leo feature flags must not leak into sdk: test "$(grep -c dev_skip_checks sdk/Cargo.lock || true)" = "0" +# Source-identity canary — one snarkvm copy, or leo's types won't unify: +test "$(grep -c '^name = "snarkvm"' sdk-abi/Cargo.lock || true)" = "1" ``` On any failure: diagnose against the changeset inventory, fix, re-run the diff --git a/.github/scripts/snarkvm_check.sh b/.github/scripts/snarkvm_check.sh index 874ced24..4d941a6b 100755 --- a/.github/scripts/snarkvm_check.sh +++ b/.github/scripts/snarkvm_check.sh @@ -22,7 +22,9 @@ current="" if [[ "${1:-}" == "--current" ]]; then current="${2:?--current requires a tag argument}" else - dep_line=$(grep -m1 '^snarkvm = {' "$CARGO_TOML") + # Fail-soft the grep so the parse error below reports the real problem + # rather than set -e killing the script with a bare exit 1. + dep_line=$(grep -m1 '^snarkvm = {' "$CARGO_TOML" || true) if [[ $dep_line =~ tag\ =\ \"(v[0-9]+\.[0-9]+\.[0-9]+)\" ]]; then current="${BASH_REMATCH[1]}" elif [[ $dep_line =~ version\ =\ \"([0-9]+\.[0-9]+\.[0-9]+)\" ]]; then @@ -37,7 +39,7 @@ latest=$(git ls-remote --tags "$REPO_URL" \ | awk '{print $2}' \ | sed 's|^refs/tags/||; s|\^{}$||' \ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ - | sort -uV | tail -1) + | sort -uV | tail -1 || true) if [[ -z "$latest" ]]; then echo "error: no vX.Y.Z release tags found on $REPO_URL" >&2 exit 1 @@ -77,7 +79,10 @@ if [[ -n "$devnode_latest" ]]; then devnode_dep=$(curl -fsS \ "https://raw.githubusercontent.com/ProvableHQ/aleo-devnode/${devnode_latest}/Cargo.toml" \ 2>/dev/null | grep -m1 '^snarkvm ' || true) - if [[ $devnode_dep =~ tag\ =\ \"(v[0-9]+\.[0-9]+\.[0-9]+)\" ]]; then + # The devnode tracks testnet tags as often as mainnet ones, so keep any + # testnet-/canary- prefix verbatim — "testnet-v4.9.0" != "v4.9.0" and the + # PR body should say which line the devnode is on. + if [[ $devnode_dep =~ tag\ =\ \"((testnet-|canary-)?v[0-9]+\.[0-9]+\.[0-9]+)\" ]]; then devnode_snarkvm="${BASH_REMATCH[1]}" elif [[ $devnode_dep =~ version\ =\ \"([0-9]+\.[0-9]+\.[0-9]+)\" ]]; then devnode_snarkvm="v${BASH_REMATCH[1]}" diff --git a/.github/workflows/snarkvm-upgrade.yml b/.github/workflows/snarkvm-upgrade.yml index 96b6118b..2c861ad7 100644 --- a/.github/workflows/snarkvm-upgrade.yml +++ b/.github/workflows/snarkvm-upgrade.yml @@ -19,6 +19,13 @@ on: schedule: - cron: "0 15 * * *" # 08:00 PDT (07:00 PST — GitHub cron is UTC-only) +# One upgrade attempt at a time: a manual dispatch overlapping the cron would +# otherwise put two agents on the same snarkvm-upgrade/ branch. Queue +# rather than cancel — a mid-flight run has real work in progress. +concurrency: + group: snarkvm-upgrade + cancel-in-progress: false + permissions: contents: write pull-requests: write From 25a52861b8afc05a304470845df4861cc830f0d9 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Wed, 29 Jul 2026 15:35:29 -0500 Subject: [PATCH 7/7] feat: track leo releases for sdk-abi alongside snarkvm for sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sdk-abi pins leo by rev, and leo moves on its own cadence, so a snarkvm-only trigger left it to drift. The pre-check now resolves leo's latest release (max version across leo-*-vX.Y.Z tags, since a release tags several crates at one rev but not every crate every time) and compares it against the version leo's manifest declares at sdk-abi's pinned rev — no new bookkeeping to drift. Either pin being behind triggers a run; the script composes the branch name so one source of truth encodes what moved. Dispatch inputs now arrive via env with format validation instead of being interpolated into the script body. --- .agents/skills/upgrade-snarkvm/SKILL.md | 165 +++++++++++++++++------- .github/scripts/snarkvm_check.sh | 135 +++++++++++++++---- .github/workflows/snarkvm-upgrade.yml | 35 ++++- 3 files changed, 256 insertions(+), 79 deletions(-) diff --git a/.agents/skills/upgrade-snarkvm/SKILL.md b/.agents/skills/upgrade-snarkvm/SKILL.md index 97885418..3132e7bb 100644 --- a/.agents/skills/upgrade-snarkvm/SKILL.md +++ b/.agents/skills/upgrade-snarkvm/SKILL.md @@ -1,12 +1,22 @@ --- name: upgrade-snarkvm -description: Use when upgrading the SDK's pinned snarkVM to the latest upstream release — compares tags, adapts sdk/ and sdk-abi/ to the changeset, runs the native validation loop, bumps PyPI package versions, and opens a PR. Invoked as /upgrade-snarkvm interactively or by the snarkvm-upgrade workflow. +description: Use when upgrading the SDK's pinned upstream Rust dependencies — snarkVM (sdk/) and leo (sdk-abi/) — to their latest releases. Compares tags, adapts the code to the changeset, runs the native validation loop, bumps PyPI package versions, and opens a PR. Invoked as /upgrade-snarkvm interactively or by the snarkvm-upgrade workflow. --- -# Upgrade snarkVM +# Upgrade snarkVM and leo -Upgrade the pinned `snarkvm` dependency to the latest upstream release and -adapt the SDK. Same procedure interactively and in CI. +Upgrade the SDK's two upstream Rust pins to their latest releases and adapt +the code. Same procedure interactively and in CI. + +Two independent pins, each with its own release cadence: + +- **`sdk/`** pins `snarkvm` — tracks snarkVM's latest mainnet release tag. +- **`sdk-abi/`** pins four `leo` crates by rev (`leo-abi`, `leo-ast`, + `leo-disassembler`, `leo-span`) plus a `snarkvm` dep that must match leo's + — tracks leo's latest release. + +Either can be behind independently, so a run may upgrade one, the other, or +both. The pre-check tells you which. ## Hard rules @@ -16,14 +26,21 @@ adapt the SDK. Same procedure interactively and in CI. - Dependency form follows one policy: prefer the crates.io release when its version equals the latest GitHub tag; otherwise pin the git tag. The pre-check's `crates_io` output already answers this — don't re-derive it. -- `sdk-abi` carries an extra constraint on top of that policy: its snarkvm - dep must resolve to the **same source** as leo's. Cargo compiles the same - crate from two different sources twice, so registry `4.8.1` and - `tag = "v4.8.1"` are distinct crates and `Process`/`Program` in - leo-disassembler's signatures stop unifying with the SDK's. Mirror leo's - snarkvm dep line exactly — same source, version/tag, and features — and - never pick sdk-abi's form independently. Verify after building: - `grep -c '^name = "snarkvm"' sdk-abi/Cargo.lock` must be 1. +- `sdk-abi` does NOT get to apply that policy. Its snarkvm dep must resolve + to the **same source *and* version as leo's**, because cargo compiles one + crate from two different sources twice: registry `4.8.1` and + `tag = "v4.8.1"` are distinct crates, so `Process`/`Program` in + leo-disassembler's signatures stop unifying with sdk-abi's. Mirror leo's + snarkvm dep line verbatim — same source, same version/tag, same features — + and never choose it independently. This means **sdk-abi's snarkvm can and + will lag `sdk/`'s** whenever leo hasn't adopted the newest snarkVM; that is + correct and expected, not a problem to fix. The two are separate build + graphs (`sdk-abi/Cargo.toml` declares its own `[workspace]`), so skew + between them is harmless. +- Proof the source identity held: after building sdk-abi, + `grep -c '^name = "snarkvm"' sdk-abi/Cargo.lock` must be exactly **1**. A + 2 means leo and sdk-abi disagree on the source and the type errors you're + about to see are that, not a real API break. - Validate seriously before giving up: on failures, re-read the changeset, fix, and re-run. Only after several genuine fix attempts do you fall back to the draft-PR path in step 8. @@ -34,17 +51,29 @@ adapt the SDK. Same procedure interactively and in CI. bash .github/scripts/snarkvm_check.sh ``` -If `needs_upgrade=false`, report why (already at `latest`, or an open PR -exists for it — check `gh pr list --head "snarkvm-upgrade/"`) and -STOP. Otherwise set CUR=, NEW=, CRATES_IO= and -continue. +If `needs_upgrade=false`, report why (both pins current, or an open PR exists +for `branch` — check `gh pr list --head ""`) and STOP. + +Otherwise read the outputs into: + +- `snarkvm_needs_upgrade` — do step 3. CUR=`current`, NEW=`latest`, + CRATES_IO=`crates_io`. +- `leo_needs_upgrade` — do step 4. LEO_CUR=`leo_current`, + LEO_NEW=`leo_latest`, LEO_CUR_REV=`leo_current_rev`, + LEO_REV=`leo_latest_rev`. +- `branch` — the branch to push in step 8. Use it verbatim; it already + encodes which pins moved. + +Skip whichever step's flag is false, and skip that package's edits entirely — +a leo-only run must leave `sdk/` untouched, and vice versa. Also note `devnode_latest`/`devnode_snarkvm`: which snarkvm the newest aleo-devnode release pins. Purely informational — the SDK carries no devnode pin (the binary comes from PATH and `-m devnode` tests skip in CI) — but the -PR body must say whether the devnode matches $NEW (step 8). +PR body must say whether the devnode matches $NEW (step 8). The devnode often +tracks `testnet-v*` tags, so a mismatch there is normal. -## 2. Study the changeset +## 2. Study the snarkVM changeset (only if snarkvm_needs_upgrade=true) Use an existing local snarkVM clone if one is available (fetch its tags first); otherwise clone to a scratch dir: @@ -64,7 +93,7 @@ execution/deployment), and anything ARC-20/ARC-22-relevant. Write a short breaking-change inventory (API renames, signature changes, semantic changes) BEFORE editing anything. -## 3. Bump the pin in sdk/ +## 3. Bump the snarkvm pin in sdk/ (only if snarkvm_needs_upgrade=true) In `sdk/Cargo.toml`, replace the `snarkvm = {` line. CRATES_IO=true means crates.io publishes a release whose version equals $NEW; false means the tag @@ -84,34 +113,54 @@ is ahead of the registry, so the git tag is the only way to get $NEW. `sdk/Cargo.lock` refreshes on the next build — commit it with the change. -## 4. Bump the pin in sdk-abi/ (leo-coupled) +## 4. Bump leo in sdk-abi/ (only if leo_needs_upgrade=true) + +sdk-abi tracks leo's releases. Leo tags per crate +(`leo-abi-vX.Y.Z`, `leo-lang-vX.Y.Z`, …) and one release tags several crates +at a single rev — but not every crate every time, so don't trust any single +crate's tag list. The pre-check already resolved the newest release for you: +LEO_NEW is the version, LEO_REV the rev its tags point at. -Check whether leo's default branch has picked up $NEW: +Study what changed first — sdk-abi binds leo's ABI surface, which moves more +than snarkVM's: ```bash -curl -s https://raw.githubusercontent.com/ProvableHQ/leo/HEAD/Cargo.toml | grep -m1 '^snarkvm ' +git clone --filter=blob:none https://github.com/ProvableHQ/leo /tmp/leo-upgrade-src +cd /tmp/leo-upgrade-src +git log --oneline $LEO_CUR_REV..$LEO_REV -- crates/abi/ crates/disassembler/ crates/ast/ +git diff $LEO_CUR_REV..$LEO_REV -- crates/abi/src crates/disassembler/src ``` -- If leo's pin resolves to $NEW — either `tag = "$NEW"` or crates.io - `version = "X.Y.Z"` matching $NEW without the `v` (leo migrated to the - crates.io form around v4.8.1): get the rev - (`git ls-remote https://github.com/ProvableHQ/leo HEAD`), then in - `sdk-abi/Cargo.toml` update EVERY leo crate's `rev = "..."` to that rev - and **mirror leo's snarkvm dep line exactly** — same form (tag or - version), same feature list. Type-coupling means matching leo's source, - whatever shape it takes. -- If leo still pins an older snarkvm: leave `sdk-abi/` entirely untouched - (sdk and sdk-abi are separate build graphs; version skew is fine) and - record "sdk-abi held back at $CUR — leo hasn't adopted $NEW" for the PR - body. +Then in `sdk-abi/Cargo.toml`: + +1. Update EVERY leo crate's `rev = "..."` to LEO_REV — all four must move + together (`leo-abi`, `leo-ast`, `leo-disassembler`, `leo-span`); a partial + bump gives you two leo versions in one graph and unresolvable type errors. +2. Read leo's snarkvm dep line **at LEO_REV** and mirror it verbatim: + +```bash +curl -fsS "https://raw.githubusercontent.com/ProvableHQ/leo/$LEO_REV/Cargo.toml" | grep -m1 '^snarkvm ' +``` + +Copy its source form, version/tag, and feature list into sdk-abi's `snarkvm` +line exactly — see the hard rule above on why this is not a choice. If that +version differs from `sdk/`'s, say so in the PR body; the skew is expected. ## 5. Adapt the code -Fix `sdk/src/` (and `sdk-abi/src/` if bumped) guided by the step-2 -inventory. Start with `cargo check`; compile errors first, then semantics. +Fix `sdk/src/` (if step 3 ran) and `sdk-abi/src/` (if step 4 ran), guided by +the step-2 and step-4 inventories. Start with `cargo check` in each affected +package; compile errors first, then semantics. For sdk-abi, a wall of +`Process`/`Program` mismatches usually means the snarkvm source didn't +match leo's — re-check that before touching any code. ## 6. Validate (native loop, escalating — all from `sdk/`) +Run the whole loop regardless of which pin moved: sdk-abi ships an import +hook into the main package (`sdk/python/tests/test_abi_hook.py`), so a +leo-only upgrade can still break `sdk/`'s tests. The cost is cache-warm +rebuilds, not correctness risk. + Interactively, work inside `sdk/.env` (`source .env/bin/activate`); in CI use the runner's python directly. @@ -173,19 +222,37 @@ lockfile changes. ## 8. PR +Use `branch` from step 1 verbatim — it already names what moved +(`deps-upgrade/snarkvm-v4.9.0`, `deps-upgrade/leo-v4.3.4`, or both joined). +Stage explicit paths, never `git add -A`: the validation loop leaves build +artifacts (`dist/`, `sdk/dist*/`) that are not gitignored. + ```bash -git checkout -b "snarkvm-upgrade/$NEW" # or reset it if a stale branch exists without an open PR -git add -A ':!docs/superpowers' && git commit -m "deps: upgrade snarkvm to $NEW" -git push -u origin "snarkvm-upgrade/$NEW" --force-with-lease +git checkout -B "$branch" # -B resets a stale branch with no open PR +git add sdk/Cargo.toml sdk/Cargo.lock sdk/pyproject.toml \ + sdk-abi/Cargo.toml sdk-abi/Cargo.lock sdk-abi/pyproject.toml \ + shield-swap-sdk/pyproject.toml +git add -u sdk/src sdk-abi/src # any code adaptations from step 5 +git commit -m "" # e.g. "deps: upgrade snarkvm to v4.9.0, leo to v4.3.4" +git push -u origin "$branch" --force-with-lease ``` -- All green → `gh pr create --title "deps: upgrade snarkvm to $NEW" --body ...` - with: the breaking-change inventory, what was adapted, dep-form decision - (crates.io vs git, sdk-abi held back or not), validation evidence (which - suites ran, pass counts), the version bumps, and a devnode-compatibility - line: "aleo-devnode <devnode_latest> pins snarkvm <devnode_snarkvm> — - matches $NEW" or "— lags $NEW: local `-m devnode` e2e tests may skip on - fee/consensus skew until a devnode release adopts it". -- Genuinely stuck after repeated fix attempts → same but - `gh pr create --draft`, body additionally lists exact failing tests with - output excerpts and what was attempted. +Title names only the pins that actually moved. Body must cover: + +- the breaking-change inventory per upgraded dep, and what was adapted +- `sdk/` dep-form decision (crates.io vs git tag, and why) +- `sdk-abi/`: leo rev old→new, and the snarkvm line mirrored from leo — + state explicitly when it differs from `sdk/`'s, e.g. "sdk-abi stays on + snarkvm 4.8.1 because leo v4.3.4 pins it; sdk/ is on 4.9.0. Expected — + separate build graphs." +- validation evidence: which suites ran, pass counts, and both canaries +- the PyPI version bumps +- devnode: "aleo-devnode <devnode_latest> pins snarkvm <devnode_snarkvm> — + matches $NEW" or "— differs from $NEW: local `-m devnode` e2e tests may + skip on fee/consensus skew until a devnode release adopts it" (the devnode + frequently tracks `testnet-v*`, so a mismatch is usually not actionable) + +Then `gh pr create --title "<title>" --body ...`. +Genuinely stuck after repeated fix attempts → same, but `gh pr create +--draft`, with the body additionally listing exact failing tests, output +excerpts, and what was attempted. diff --git a/.github/scripts/snarkvm_check.sh b/.github/scripts/snarkvm_check.sh index 4d941a6b..382f1786 100755 --- a/.github/scripts/snarkvm_check.sh +++ b/.github/scripts/snarkvm_check.sh @@ -1,26 +1,47 @@ #!/usr/bin/env bash -# Is the SDK's snarkvm pin behind the latest upstream release, and is an -# upgrade PR already open? +# Are the SDK's two upstream pins — snarkvm (sdk/) and leo (sdk-abi/) — behind +# their latest releases, and is an upgrade PR already open? # # Output (stdout `key=value`; also appended to $GITHUB_OUTPUT when set): -# current=v4.8.1 SDK's pinned snarkvm version -# latest=v4.8.1 newest bare vX.Y.Z tag upstream (no testnet-/canary-) -# crates_io=true|false is `latest` published on crates.io? -# needs_upgrade=true|false -# devnode_latest=v0.2.0 newest aleo-devnode release (informational — -# devnode_snarkvm=v4.8.1 the SDK carries no devnode pin; this tells the -# upgrade PR whether the devnode will skew) +# current=v4.8.1 sdk/'s pinned snarkvm version +# latest=v4.9.0 newest bare vX.Y.Z snarkvm tag (no testnet-/canary-) +# crates_io=true|false does crates.io publish a release equal to `latest`? +# snarkvm_needs_upgrade=true|false +# leo_current=4.3.2 leo version at sdk-abi's pinned rev +# leo_latest=4.3.4 newest leo release version (max over leo-*-vX.Y.Z tags) +# leo_current_rev=<sha> sdk-abi's pinned leo rev +# leo_latest_rev=<sha> rev the newest leo release tags point at +# leo_needs_upgrade=true|false +# branch=deps-upgrade/... branch the upgrade should use (empty if none needed) +# needs_upgrade=true|false either pin behind, and no PR open for `branch` +# devnode_latest=v0.2.2 newest aleo-devnode release (informational — +# devnode_snarkvm=testnet-v4.9.0 the SDK carries no devnode pin; this tells +# the upgrade PR whether the devnode will skew) # -# Usage: snarkvm_check.sh [--current vX.Y.Z] -# --current overrides pin detection (testing / workflow force_from_tag). +# Usage: snarkvm_check.sh [--current vX.Y.Z] [--leo-current X.Y.Z] +# Overrides pin detection (testing / workflow force_from_tag). set -euo pipefail REPO_URL="https://github.com/ProvableHQ/snarkVM" -CARGO_TOML="$(cd "$(dirname "$0")/../.." && pwd)/sdk/Cargo.toml" +LEO_URL="https://github.com/ProvableHQ/leo" +DEVNODE_URL="https://github.com/ProvableHQ/aleo-devnode" +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +CARGO_TOML="$ROOT/sdk/Cargo.toml" +ABI_CARGO="$ROOT/sdk-abi/Cargo.toml" + +force_current="" +force_leo="" +while [[ $# -gt 0 ]]; do + case "$1" in + --current) force_current="${2:?--current requires a tag argument}"; shift 2 ;; + --leo-current) force_leo="${2:?--leo-current requires a version argument}"; shift 2 ;; + *) echo "error: unknown argument '$1'" >&2; exit 1 ;; + esac +done -current="" -if [[ "${1:-}" == "--current" ]]; then - current="${2:?--current requires a tag argument}" +# ---------------------------------------------------------------- snarkvm ---- +if [[ -n "$force_current" ]]; then + current="$force_current" else # Fail-soft the grep so the parse error below reports the real problem # rather than set -e killing the script with a bare exit 1. @@ -45,30 +66,91 @@ if [[ -z "$latest" ]]; then exit 1 fi +# Prefer the crates.io release when it matches `latest`; otherwise the tag is +# ahead of the registry and only the git pin can reach it. crates_io=false if curl -fsS -o /dev/null -A "ProvableHQ/python-sdk snarkvm-upgrade check" \ "https://crates.io/api/v1/crates/snarkvm/${latest#v}"; then crates_io=true fi -# If an upgrade PR for `latest` is already open, don't re-attempt daily. +snarkvm_needs_upgrade=false +[[ "$current" != "$latest" ]] && snarkvm_needs_upgrade=true + +# -------------------------------------------------------------------- leo ---- +# sdk-abi pins leo by rev. Leo tags per crate (leo-abi-v4.3.4, leo-lang-v4.3.4, +# ...) and one release tags several crates at a single rev — but not every +# crate every time (leo-abi's newest tag lags leo-lang's). So take the highest +# version across ALL leo-*-v tags as the latest release, then resolve any tag +# at that version to its rev. +leo_current_rev="$(grep -m1 -oE 'rev = "[0-9a-f]{7,40}"' "$ABI_CARGO" \ + | grep -oE '[0-9a-f]{7,40}' || true)" + +leo_tags=$(git ls-remote --tags "$LEO_URL" 2>/dev/null \ + | awk '{print $2}' \ + | sed 's|^refs/tags/||; s|\^{}$||' \ + | grep -E '^leo-[a-z0-9-]+-v[0-9]+\.[0-9]+\.[0-9]+$' | sort -u || true) +leo_latest=$(printf '%s\n' "$leo_tags" | sed -E 's/^leo-[a-z0-9-]+-v//' \ + | sort -uV | tail -1 || true) + +leo_latest_rev="" +if [[ -n "$leo_latest" ]]; then + leo_latest_tag=$(printf '%s\n' "$leo_tags" \ + | grep -E -- "-v${leo_latest//./\\.}\$" | head -1 || true) + if [[ -n "${leo_latest_tag:-}" ]]; then + leo_latest_rev=$(git ls-remote "$LEO_URL" "refs/tags/$leo_latest_tag" \ + 2>/dev/null | awk '{print $1}' | head -1 || true) + fi +fi + +# Which leo version is the pinned rev? Read it from leo's own manifest at that +# rev — no extra bookkeeping in sdk-abi to drift out of date. +if [[ -n "$force_leo" ]]; then + leo_current="$force_leo" +elif [[ -n "$leo_current_rev" ]]; then + leo_current=$(curl -fsS \ + "https://raw.githubusercontent.com/ProvableHQ/leo/${leo_current_rev}/Cargo.toml" \ + 2>/dev/null | grep -m1 '^leo-abi ' \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true) + leo_current="${leo_current:-unknown}" +else + leo_current=unknown +fi + +# Only claim an upgrade when both sides are known — an unknown must never look +# like "behind" and kick off a 90-minute run on bad data. +leo_needs_upgrade=false +if [[ "$leo_current" != unknown && -n "$leo_latest" && -n "$leo_latest_rev" \ + && "$leo_current" != "$leo_latest" ]]; then + leo_needs_upgrade=true +fi + +# ----------------------------------------------------------------- branch ---- +slug="" +[[ "$snarkvm_needs_upgrade" == true ]] && slug="snarkvm-$latest" +if [[ "$leo_needs_upgrade" == true ]]; then + slug="${slug:+$slug-}leo-v$leo_latest" +fi +branch="${slug:+deps-upgrade/$slug}" + +# If a PR for this exact branch is already open, don't re-attempt daily. # Fail-soft when gh is unavailable/unauthenticated: worst case the skill's # own `gh pr create` collides on the existing head branch and errors there. pr_open=0 -if command -v gh >/dev/null 2>&1; then - pr_open=$(gh pr list --head "snarkvm-upgrade/${latest}" --state open \ +if [[ -n "$branch" ]] && command -v gh >/dev/null 2>&1; then + pr_open=$(gh pr list --head "$branch" --state open \ --json number --jq length 2>/dev/null || echo 0) fi needs_upgrade=false -if [[ "$current" != "$latest" && "$pr_open" == "0" ]]; then +if [[ -n "$branch" && "$pr_open" == "0" ]]; then needs_upgrade=true fi -# aleo-devnode compatibility (informational only — never gates the upgrade). -# Which snarkvm does the latest devnode release pin? Handles both git-tag and -# crates.io dep forms; fail-soft to "unknown" on network/parse trouble. -DEVNODE_URL="https://github.com/ProvableHQ/aleo-devnode" +# ---------------------------------------------------------------- devnode ---- +# Informational only — never gates the upgrade. Which snarkvm does the latest +# devnode release pin? Handles both git-tag and crates.io dep forms; fail-soft +# to "unknown" on network/parse trouble. devnode_latest=$(git ls-remote --tags "$DEVNODE_URL" 2>/dev/null \ | awk '{print $2}' \ | sed 's|^refs/tags/||; s|\^{}$||' \ @@ -93,6 +175,13 @@ devnode_latest="${devnode_latest:-unknown}" out="current=$current latest=$latest crates_io=$crates_io +snarkvm_needs_upgrade=$snarkvm_needs_upgrade +leo_current=$leo_current +leo_latest=${leo_latest:-unknown} +leo_current_rev=${leo_current_rev:-unknown} +leo_latest_rev=${leo_latest_rev:-unknown} +leo_needs_upgrade=$leo_needs_upgrade +branch=$branch needs_upgrade=$needs_upgrade devnode_latest=$devnode_latest devnode_snarkvm=$devnode_snarkvm" diff --git a/.github/workflows/snarkvm-upgrade.yml b/.github/workflows/snarkvm-upgrade.yml index 2c861ad7..e143ed4f 100644 --- a/.github/workflows/snarkvm-upgrade.yml +++ b/.github/workflows/snarkvm-upgrade.yml @@ -1,6 +1,7 @@ -# Daily snarkVM upgrade check. On a new upstream release, a Claude session -# runs the /upgrade-snarkvm skill (.agents/skills/upgrade-snarkvm/SKILL.md) -# to bump, adapt, validate, and open a PR. +# Daily upstream-dependency check for snarkvm (sdk/) and leo (sdk-abi/). When +# either is behind its latest release, a Claude session runs the +# /upgrade-snarkvm skill (.agents/skills/upgrade-snarkvm/SKILL.md) to bump, +# adapt, validate, and open a PR. # # One-time setup (repo secrets): # ANTHROPIC_API_KEY — for claude-code-action @@ -13,7 +14,11 @@ on: workflow_dispatch: inputs: force_from_tag: - description: "Pretend the SDK is pinned at this tag (testing, e.g. v4.8.0)" + description: "Pretend sdk/ pins this snarkvm tag (testing, e.g. v4.8.0)" + required: false + default: "" + force_leo_from_version: + description: "Pretend sdk-abi/ pins this leo version (testing, e.g. 4.3.2)" required: false default: "" schedule: @@ -36,16 +41,32 @@ jobs: outputs: current: ${{ steps.check.outputs.current }} latest: ${{ steps.check.outputs.latest }} + leo_current: ${{ steps.check.outputs.leo_current }} + leo_latest: ${{ steps.check.outputs.leo_latest }} + snarkvm_needs_upgrade: ${{ steps.check.outputs.snarkvm_needs_upgrade }} + leo_needs_upgrade: ${{ steps.check.outputs.leo_needs_upgrade }} + branch: ${{ steps.check.outputs.branch }} needs_upgrade: ${{ steps.check.outputs.needs_upgrade }} steps: - uses: actions/checkout@v4 - id: check + # Dispatch inputs come through env, not ${{ }} interpolation into the + # script body, so a crafted value is data rather than shell syntax. env: GH_TOKEN: ${{ github.token }} + FORCE_TAG: ${{ inputs.force_from_tag }} + FORCE_LEO: ${{ inputs.force_leo_from_version }} run: | args=() - if [ -n "${{ inputs.force_from_tag }}" ]; then - args=(--current "${{ inputs.force_from_tag }}") + if [ -n "$FORCE_TAG" ]; then + [[ $FORCE_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] \ + || { echo "::error::force_from_tag must look like v4.8.0"; exit 1; } + args+=(--current "$FORCE_TAG") + fi + if [ -n "$FORCE_LEO" ]; then + [[ $FORCE_LEO =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] \ + || { echo "::error::force_leo_from_version must look like 4.3.2"; exit 1; } + args+=(--leo-current "$FORCE_LEO") fi bash .github/scripts/snarkvm_check.sh "${args[@]}" @@ -81,6 +102,6 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.SNARKVM_UPGRADE_PAT }} - prompt: "/upgrade-snarkvm — upgrading ${{ needs.check.outputs.current }} -> ${{ needs.check.outputs.latest }}. You are in CI: work non-interactively to completion." + prompt: "/upgrade-snarkvm — snarkvm ${{ needs.check.outputs.current }} -> ${{ needs.check.outputs.latest }} (needed: ${{ needs.check.outputs.snarkvm_needs_upgrade }}); leo ${{ needs.check.outputs.leo_current }} -> ${{ needs.check.outputs.leo_latest }} (needed: ${{ needs.check.outputs.leo_needs_upgrade }}); branch ${{ needs.check.outputs.branch }}. Re-run the pre-check yourself for the full outputs. You are in CI: work non-interactively to completion." claude_args: | --allowedTools "Bash,Read,Edit,Write,Glob,Grep"