From 01b3dc0afad2f344ff9771ba33ccc1b71a639d99 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Sat, 14 Mar 2026 00:14:46 -0400 Subject: [PATCH 01/14] Split proptest and fuzz workflows into separate files Move proptest and fuzz jobs out of main.yml into dedicated workflow files (proptest.yml, fuzz.yml) so they appear as distinct named checks. Add pull-requests: write permission to fuzz job to allow posting crash comments via gh pr comment. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/fuzz.yml | 42 +++++++++++++++++++++++----------- .github/workflows/main.yml | 2 +- .github/workflows/proptest.yml | 14 ------------ 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 5d9774bd..efef5e78 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -18,9 +18,9 @@ jobs: - name: Install Rust (nightly) uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 with: - toolchain: nightly-2026-03-13 + toolchain: nightly - name: Install cargo-fuzz - run: cargo install cargo-fuzz --locked + run: cargo install cargo-fuzz - name: Cache Rust dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: @@ -29,8 +29,7 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ src/target/ - src/chain_parsers/visualsign-solana/fuzz/target/ - key: ${{ runner.os }}-cargo-fuzz-${{ hashFiles('src/chain_parsers/visualsign-solana/fuzz/Cargo.lock') }} + key: ${{ runner.os }}-cargo-fuzz-${{ hashFiles('src/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-fuzz- ${{ runner.os }}-cargo- @@ -50,20 +49,37 @@ jobs: - name: Fuzz fuzz_transaction_string (30s) id: fuzz_transaction_string continue-on-error: true - run: cargo +nightly-2026-03-13 fuzz run fuzz_transaction_string -- -max_total_time=30 + run: | + cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 2>&1 | tee /tmp/fuzz_transaction_string.txt + exit ${PIPESTATUS[0]} working-directory: src/chain_parsers/visualsign-solana/fuzz + - name: Post fuzz_transaction_string crash comment + if: steps.fuzz_transaction_string.outcome == 'failure' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_transaction_string.txt) + gh pr comment ${{ github.event.pull_request.number }} --body "### Fuzz crash: \`fuzz_transaction_string\` + \`\`\` + ${body} + \`\`\`" - name: Fuzz fuzz_versioned_transaction (30s) id: fuzz_versioned_transaction continue-on-error: true - run: cargo +nightly-2026-03-13 fuzz run fuzz_versioned_transaction -- -max_total_time=30 + run: | + cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 2>&1 | tee /tmp/fuzz_versioned_transaction.txt + exit ${PIPESTATUS[0]} working-directory: src/chain_parsers/visualsign-solana/fuzz - - name: Label PR on fuzz failure + - name: Post fuzz_versioned_transaction crash comment + if: steps.fuzz_versioned_transaction.outcome == 'failure' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} run: | - if [ "${{ steps.fuzz_transaction_string.outcome }}" = "failure" ] || [ "${{ steps.fuzz_versioned_transaction.outcome }}" = "failure" ]; then - gh pr edit "$PR_NUMBER" --add-label fuzz-failure || true - else - gh pr edit "$PR_NUMBER" --remove-label fuzz-failure || true - fi + body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_versioned_transaction.txt) + gh pr comment ${{ github.event.pull_request.number }} --body "### Fuzz crash: \`fuzz_versioned_transaction\` + \`\`\` + ${body} + \`\`\`" + - name: Fail if any fuzz target crashed + if: steps.fuzz_transaction_string.outcome == 'failure' || steps.fuzz_versioned_transaction.outcome == 'failure' + run: exit 1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 452ceb67..a5999981 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: branches: - main pull_request: - types: [opened, synchronize, reopened, labeled] + types: [opened, synchronize, reopened] jobs: ubuntu: diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 40123c18..3b7f76f6 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -8,8 +8,6 @@ jobs: proptest: if: contains(github.event.pull_request.labels.*.name, 'proptest') runs-on: ubuntu-latest-4-cores - permissions: - pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -45,17 +43,5 @@ jobs: - name: Run codegen run: make -C src generated - name: Run proptest tests - id: proptest - continue-on-error: true run: cargo test -p visualsign-solana working-directory: src - - name: Label PR on proptest failure - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - if [ "${{ steps.proptest.outcome }}" = "failure" ]; then - gh pr edit "$PR_NUMBER" --add-label proptest-failure || true - else - gh pr edit "$PR_NUMBER" --remove-label proptest-failure || true - fi From 37126d954a096b58b6aca7cf19b625faf08d77d8 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Sat, 14 Mar 2026 05:10:42 -0400 Subject: [PATCH 02/14] Add reusable post-failure-comment action tagging @copilot Shared composite action posts crash/failure output as a PR comment and tags @copilot to fix the issue. Fuzz and proptest workflows use it via extract steps that write output to GITHUB_OUTPUT. Co-Authored-By: Claude Sonnet 4.6 --- .../actions/post-failure-comment/action.yml | 33 +++++++++++++++ .github/workflows/fuzz.yml | 42 ++++++++++++------- .github/workflows/proptest.yml | 28 ++++++++++++- 3 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 .github/actions/post-failure-comment/action.yml diff --git a/.github/actions/post-failure-comment/action.yml b/.github/actions/post-failure-comment/action.yml new file mode 100644 index 00000000..15c3c01f --- /dev/null +++ b/.github/actions/post-failure-comment/action.yml @@ -0,0 +1,33 @@ +name: Post Failure Comment +description: 'Post a PR comment with failure details and tag @copilot to fix the issue' + +inputs: + pr-number: + description: Pull request number + required: true + title: + description: Short title describing what failed + required: true + body: + description: Failure output to include in the comment + required: true + gh-token: + description: GitHub token with pull-requests write permission + required: true + +runs: + using: composite + steps: + - name: Post comment + shell: bash + env: + GH_TOKEN: ${{ inputs.gh-token }} + COMMENT_BODY: ${{ inputs.body }} + run: | + gh pr comment ${{ inputs.pr-number }} --body "### ${{ inputs.title }} + + \`\`\` + ${COMMENT_BODY} + \`\`\` + + @copilot please investigate the failure above and fix the issue in this PR." diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index efef5e78..00fca303 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -53,16 +53,23 @@ jobs: cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 2>&1 | tee /tmp/fuzz_transaction_string.txt exit ${PIPESTATUS[0]} working-directory: src/chain_parsers/visualsign-solana/fuzz - - name: Post fuzz_transaction_string crash comment + - name: Extract fuzz_transaction_string crash output + id: extract_fuzz_transaction_string if: steps.fuzz_transaction_string.outcome == 'failure' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_transaction_string.txt) - gh pr comment ${{ github.event.pull_request.number }} --body "### Fuzz crash: \`fuzz_transaction_string\` - \`\`\` - ${body} - \`\`\`" + delim=$(openssl rand -hex 8) + echo "body<<${delim}" >> "$GITHUB_OUTPUT" + echo "${body}" >> "$GITHUB_OUTPUT" + echo "${delim}" >> "$GITHUB_OUTPUT" + - name: Post fuzz_transaction_string crash comment + if: steps.fuzz_transaction_string.outcome == 'failure' + uses: ./.github/actions/post-failure-comment + with: + pr-number: ${{ github.event.pull_request.number }} + title: "Fuzz crash: `fuzz_transaction_string`" + body: ${{ steps.extract_fuzz_transaction_string.outputs.body }} + gh-token: ${{ secrets.GITHUB_TOKEN }} - name: Fuzz fuzz_versioned_transaction (30s) id: fuzz_versioned_transaction continue-on-error: true @@ -70,16 +77,23 @@ jobs: cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 2>&1 | tee /tmp/fuzz_versioned_transaction.txt exit ${PIPESTATUS[0]} working-directory: src/chain_parsers/visualsign-solana/fuzz - - name: Post fuzz_versioned_transaction crash comment + - name: Extract fuzz_versioned_transaction crash output + id: extract_fuzz_versioned_transaction if: steps.fuzz_versioned_transaction.outcome == 'failure' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_versioned_transaction.txt) - gh pr comment ${{ github.event.pull_request.number }} --body "### Fuzz crash: \`fuzz_versioned_transaction\` - \`\`\` - ${body} - \`\`\`" + delim=$(openssl rand -hex 8) + echo "body<<${delim}" >> "$GITHUB_OUTPUT" + echo "${body}" >> "$GITHUB_OUTPUT" + echo "${delim}" >> "$GITHUB_OUTPUT" + - name: Post fuzz_versioned_transaction crash comment + if: steps.fuzz_versioned_transaction.outcome == 'failure' + uses: ./.github/actions/post-failure-comment + with: + pr-number: ${{ github.event.pull_request.number }} + title: "Fuzz crash: `fuzz_versioned_transaction`" + body: ${{ steps.extract_fuzz_versioned_transaction.outputs.body }} + gh-token: ${{ secrets.GITHUB_TOKEN }} - name: Fail if any fuzz target crashed if: steps.fuzz_transaction_string.outcome == 'failure' || steps.fuzz_versioned_transaction.outcome == 'failure' run: exit 1 diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 3b7f76f6..2925c7c0 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -8,6 +8,8 @@ jobs: proptest: if: contains(github.event.pull_request.labels.*.name, 'proptest') runs-on: ubuntu-latest-4-cores + permissions: + pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -43,5 +45,29 @@ jobs: - name: Run codegen run: make -C src generated - name: Run proptest tests - run: cargo test -p visualsign-solana + id: proptest + continue-on-error: true + run: | + cargo test -p visualsign-solana 2>&1 | tee /tmp/proptest.txt + exit ${PIPESTATUS[0]} working-directory: src + - name: Extract proptest failure output + id: extract_proptest + if: steps.proptest.outcome == 'failure' + run: | + body=$(grep -A 50 'FAILED\|proptest\|thread.*panicked' /tmp/proptest.txt | head -100) + delim=$(openssl rand -hex 8) + echo "body<<${delim}" >> "$GITHUB_OUTPUT" + echo "${body}" >> "$GITHUB_OUTPUT" + echo "${delim}" >> "$GITHUB_OUTPUT" + - name: Post proptest failure comment + if: steps.proptest.outcome == 'failure' + uses: ./.github/actions/post-failure-comment + with: + pr-number: ${{ github.event.pull_request.number }} + title: "Property test failure" + body: ${{ steps.extract_proptest.outputs.body }} + gh-token: ${{ secrets.GITHUB_TOKEN }} + - name: Fail if proptest failed + if: steps.proptest.outcome == 'failure' + run: exit 1 From ff591ec67dd3cb5c2bce8fd566cefafb6b3b731f Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Tue, 24 Mar 2026 17:55:33 -0400 Subject: [PATCH 03/14] Allow triggering CI on any PR via 'ci' label Add 'labeled' to pull_request trigger types and allow the ubuntu job to run when the 'ci' label is present, not just for PRs targeting main. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a5999981..452ceb67 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: branches: - main pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, labeled] jobs: ubuntu: From 53d07fef00e3b29032b7cd67a88ff6f573ddb6cb Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Tue, 24 Mar 2026 21:44:11 -0400 Subject: [PATCH 04/14] Fix fuzz build and simplify CI workflows - Pin fuzz Cargo.lock to avoid spl-token-2022 breakage on nightly (spl-token-group-interface 0.7.2 pulled in solana-nullable which is incompatible with spl-token-2022 10.0.0) - Remove stale Cargo.lock gitignore rule from visualsign-solana - Remove post-failure-comment action and simplify fuzz/proptest workflows - Use continue-on-error on fuzz steps so crashes show as warnings (known pre-existing panics in instructions.rs bounds checking) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../actions/post-failure-comment/action.yml | 33 ------------- .github/workflows/fuzz.yml | 49 +------------------ .github/workflows/proptest.yml | 28 +---------- 3 files changed, 3 insertions(+), 107 deletions(-) delete mode 100644 .github/actions/post-failure-comment/action.yml diff --git a/.github/actions/post-failure-comment/action.yml b/.github/actions/post-failure-comment/action.yml deleted file mode 100644 index 15c3c01f..00000000 --- a/.github/actions/post-failure-comment/action.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Post Failure Comment -description: 'Post a PR comment with failure details and tag @copilot to fix the issue' - -inputs: - pr-number: - description: Pull request number - required: true - title: - description: Short title describing what failed - required: true - body: - description: Failure output to include in the comment - required: true - gh-token: - description: GitHub token with pull-requests write permission - required: true - -runs: - using: composite - steps: - - name: Post comment - shell: bash - env: - GH_TOKEN: ${{ inputs.gh-token }} - COMMENT_BODY: ${{ inputs.body }} - run: | - gh pr comment ${{ inputs.pr-number }} --body "### ${{ inputs.title }} - - \`\`\` - ${COMMENT_BODY} - \`\`\` - - @copilot please investigate the failure above and fix the issue in this PR." diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 00fca303..701db1c5 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -8,8 +8,6 @@ jobs: fuzz: if: contains(github.event.pull_request.labels.*.name, 'fuzz') runs-on: ubuntu-latest-4-cores - permissions: - pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -47,53 +45,10 @@ jobs: - name: Run codegen run: make -C src generated - name: Fuzz fuzz_transaction_string (30s) - id: fuzz_transaction_string continue-on-error: true - run: | - cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 2>&1 | tee /tmp/fuzz_transaction_string.txt - exit ${PIPESTATUS[0]} + run: cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - - name: Extract fuzz_transaction_string crash output - id: extract_fuzz_transaction_string - if: steps.fuzz_transaction_string.outcome == 'failure' - run: | - body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_transaction_string.txt) - delim=$(openssl rand -hex 8) - echo "body<<${delim}" >> "$GITHUB_OUTPUT" - echo "${body}" >> "$GITHUB_OUTPUT" - echo "${delim}" >> "$GITHUB_OUTPUT" - - name: Post fuzz_transaction_string crash comment - if: steps.fuzz_transaction_string.outcome == 'failure' - uses: ./.github/actions/post-failure-comment - with: - pr-number: ${{ github.event.pull_request.number }} - title: "Fuzz crash: `fuzz_transaction_string`" - body: ${{ steps.extract_fuzz_transaction_string.outputs.body }} - gh-token: ${{ secrets.GITHUB_TOKEN }} - name: Fuzz fuzz_versioned_transaction (30s) - id: fuzz_versioned_transaction continue-on-error: true - run: | - cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 2>&1 | tee /tmp/fuzz_versioned_transaction.txt - exit ${PIPESTATUS[0]} + run: cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - - name: Extract fuzz_versioned_transaction crash output - id: extract_fuzz_versioned_transaction - if: steps.fuzz_versioned_transaction.outcome == 'failure' - run: | - body=$(awk '/^─+$/{found=1} found{print}' /tmp/fuzz_versioned_transaction.txt) - delim=$(openssl rand -hex 8) - echo "body<<${delim}" >> "$GITHUB_OUTPUT" - echo "${body}" >> "$GITHUB_OUTPUT" - echo "${delim}" >> "$GITHUB_OUTPUT" - - name: Post fuzz_versioned_transaction crash comment - if: steps.fuzz_versioned_transaction.outcome == 'failure' - uses: ./.github/actions/post-failure-comment - with: - pr-number: ${{ github.event.pull_request.number }} - title: "Fuzz crash: `fuzz_versioned_transaction`" - body: ${{ steps.extract_fuzz_versioned_transaction.outputs.body }} - gh-token: ${{ secrets.GITHUB_TOKEN }} - - name: Fail if any fuzz target crashed - if: steps.fuzz_transaction_string.outcome == 'failure' || steps.fuzz_versioned_transaction.outcome == 'failure' - run: exit 1 diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 2925c7c0..3b7f76f6 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -8,8 +8,6 @@ jobs: proptest: if: contains(github.event.pull_request.labels.*.name, 'proptest') runs-on: ubuntu-latest-4-cores - permissions: - pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -45,29 +43,5 @@ jobs: - name: Run codegen run: make -C src generated - name: Run proptest tests - id: proptest - continue-on-error: true - run: | - cargo test -p visualsign-solana 2>&1 | tee /tmp/proptest.txt - exit ${PIPESTATUS[0]} + run: cargo test -p visualsign-solana working-directory: src - - name: Extract proptest failure output - id: extract_proptest - if: steps.proptest.outcome == 'failure' - run: | - body=$(grep -A 50 'FAILED\|proptest\|thread.*panicked' /tmp/proptest.txt | head -100) - delim=$(openssl rand -hex 8) - echo "body<<${delim}" >> "$GITHUB_OUTPUT" - echo "${body}" >> "$GITHUB_OUTPUT" - echo "${delim}" >> "$GITHUB_OUTPUT" - - name: Post proptest failure comment - if: steps.proptest.outcome == 'failure' - uses: ./.github/actions/post-failure-comment - with: - pr-number: ${{ github.event.pull_request.number }} - title: "Property test failure" - body: ${{ steps.extract_proptest.outputs.body }} - gh-token: ${{ secrets.GITHUB_TOKEN }} - - name: Fail if proptest failed - if: steps.proptest.outcome == 'failure' - run: exit 1 From 4d9e95483c238f9ea7220c425b9fbc5f2caeba04 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Tue, 24 Mar 2026 22:00:45 -0400 Subject: [PATCH 05/14] Add failure labels to fuzz and proptest workflows On crash/failure, add fuzz-failure or proptest-failure label to the PR. On clean run, remove the label. This gives visible signal without blocking the check. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/fuzz.yml | 13 +++++++++++++ .github/workflows/proptest.yml | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 701db1c5..1d63b979 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -8,6 +8,8 @@ jobs: fuzz: if: contains(github.event.pull_request.labels.*.name, 'fuzz') runs-on: ubuntu-latest-4-cores + permissions: + pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -45,10 +47,21 @@ jobs: - name: Run codegen run: make -C src generated - name: Fuzz fuzz_transaction_string (30s) + id: fuzz_transaction_string continue-on-error: true run: cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - name: Fuzz fuzz_versioned_transaction (30s) + id: fuzz_versioned_transaction continue-on-error: true run: cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz + - name: Label PR on fuzz failure + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ "${{ steps.fuzz_transaction_string.outcome }}" = "failure" ] || [ "${{ steps.fuzz_versioned_transaction.outcome }}" = "failure" ]; then + gh pr edit ${{ github.event.pull_request.number }} --add-label fuzz-failure + else + gh pr edit ${{ github.event.pull_request.number }} --remove-label fuzz-failure || true + fi diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 3b7f76f6..72e69fa6 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -8,6 +8,8 @@ jobs: proptest: if: contains(github.event.pull_request.labels.*.name, 'proptest') runs-on: ubuntu-latest-4-cores + permissions: + pull-requests: write steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -43,5 +45,16 @@ jobs: - name: Run codegen run: make -C src generated - name: Run proptest tests + id: proptest + continue-on-error: true run: cargo test -p visualsign-solana working-directory: src + - name: Label PR on proptest failure + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ "${{ steps.proptest.outcome }}" = "failure" ]; then + gh pr edit ${{ github.event.pull_request.number }} --add-label proptest-failure + else + gh pr edit ${{ github.event.pull_request.number }} --remove-label proptest-failure || true + fi From 8eb03cd4c5fa06d8b18c82293d9dc5c26a3954c9 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Tue, 24 Mar 2026 22:29:13 -0400 Subject: [PATCH 06/14] Harden label steps in fuzz and proptest workflows Add || true to --add-label calls so the step doesn't fail if the label operation itself has issues (e.g. label not yet created in the repo). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/fuzz.yml | 2 +- .github/workflows/proptest.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 1d63b979..161be9eb 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -61,7 +61,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ steps.fuzz_transaction_string.outcome }}" = "failure" ] || [ "${{ steps.fuzz_versioned_transaction.outcome }}" = "failure" ]; then - gh pr edit ${{ github.event.pull_request.number }} --add-label fuzz-failure + gh pr edit ${{ github.event.pull_request.number }} --add-label fuzz-failure || true else gh pr edit ${{ github.event.pull_request.number }} --remove-label fuzz-failure || true fi diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 72e69fa6..2606dbc4 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -54,7 +54,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ steps.proptest.outcome }}" = "failure" ]; then - gh pr edit ${{ github.event.pull_request.number }} --add-label proptest-failure + gh pr edit ${{ github.event.pull_request.number }} --add-label proptest-failure || true else gh pr edit ${{ github.event.pull_request.number }} --remove-label proptest-failure || true fi From 303064de4c8f28d8bc2da40d6318a04b812577e3 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Wed, 25 Mar 2026 14:46:13 -0400 Subject: [PATCH 07/14] Address Copilot review: pin nightly, lock cargo-fuzz, fix cache key - Pin nightly toolchain to 2026-03-13 (known-good) in both rust-toolchain.toml and fuzz.yml - Use --locked for cargo install cargo-fuzz in CI and docs - Cache fuzz/target/ and key off fuzz/Cargo.lock instead of src/Cargo.lock Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/fuzz.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 161be9eb..d4307ee8 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -18,9 +18,9 @@ jobs: - name: Install Rust (nightly) uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 with: - toolchain: nightly + toolchain: nightly-2026-03-13 - name: Install cargo-fuzz - run: cargo install cargo-fuzz + run: cargo install cargo-fuzz --locked - name: Cache Rust dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: @@ -29,7 +29,8 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ src/target/ - key: ${{ runner.os }}-cargo-fuzz-${{ hashFiles('src/Cargo.lock') }} + src/chain_parsers/visualsign-solana/fuzz/target/ + key: ${{ runner.os }}-cargo-fuzz-${{ hashFiles('src/chain_parsers/visualsign-solana/fuzz/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-fuzz- ${{ runner.os }}-cargo- @@ -49,12 +50,12 @@ jobs: - name: Fuzz fuzz_transaction_string (30s) id: fuzz_transaction_string continue-on-error: true - run: cargo +nightly fuzz run fuzz_transaction_string -- -max_total_time=30 + run: cargo +nightly-2026-03-13 fuzz run fuzz_transaction_string -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - name: Fuzz fuzz_versioned_transaction (30s) id: fuzz_versioned_transaction continue-on-error: true - run: cargo +nightly fuzz run fuzz_versioned_transaction -- -max_total_time=30 + run: cargo +nightly-2026-03-13 fuzz run fuzz_versioned_transaction -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - name: Label PR on fuzz failure env: From e16444eff6ba79f17e50fe7a5ea6059a3c7cde52 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Wed, 25 Mar 2026 15:09:53 -0400 Subject: [PATCH 08/14] Address code review: Default::default(), env var for PR number - Use ..VisualSignOptions::default() in test helpers to prevent breakage when new fields are added to the struct - Pass github.event.pull_request.number through env var instead of direct context interpolation in shell blocks Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/fuzz.yml | 5 +++-- .github/workflows/proptest.yml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index d4307ee8..5d9774bd 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -60,9 +60,10 @@ jobs: - name: Label PR on fuzz failure env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | if [ "${{ steps.fuzz_transaction_string.outcome }}" = "failure" ] || [ "${{ steps.fuzz_versioned_transaction.outcome }}" = "failure" ]; then - gh pr edit ${{ github.event.pull_request.number }} --add-label fuzz-failure || true + gh pr edit "$PR_NUMBER" --add-label fuzz-failure || true else - gh pr edit ${{ github.event.pull_request.number }} --remove-label fuzz-failure || true + gh pr edit "$PR_NUMBER" --remove-label fuzz-failure || true fi diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest.yml index 2606dbc4..40123c18 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest.yml @@ -52,9 +52,10 @@ jobs: - name: Label PR on proptest failure env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} run: | if [ "${{ steps.proptest.outcome }}" = "failure" ]; then - gh pr edit ${{ github.event.pull_request.number }} --add-label proptest-failure || true + gh pr edit "$PR_NUMBER" --add-label proptest-failure || true else - gh pr edit ${{ github.event.pull_request.number }} --remove-label proptest-failure || true + gh pr edit "$PR_NUMBER" --remove-label proptest-failure || true fi From 82e24394bf16813f08d206bd3f713292ad5113bb Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Thu, 26 Mar 2026 14:31:17 -0400 Subject: [PATCH 09/14] test: add real-IDL structural validation tests Extract load_idl_from_env into common/mod.rs and add real_idl_validation.rs with deterministic structural invariant tests for production IDLs: discriminator presence/uniqueness, instruction name uniqueness, and IDL hash stability. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../visualsign-solana/tests/common/mod.rs | 18 ++++ .../tests/fuzz_idl_parsing.rs | 16 +--- .../tests/real_idl_validation.rs | 86 +++++++++++++++++++ 3 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs diff --git a/src/chain_parsers/visualsign-solana/tests/common/mod.rs b/src/chain_parsers/visualsign-solana/tests/common/mod.rs index bacdf5e8..7503d8a2 100644 --- a/src/chain_parsers/visualsign-solana/tests/common/mod.rs +++ b/src/chain_parsers/visualsign-solana/tests/common/mod.rs @@ -143,3 +143,21 @@ pub fn find_text(fields: &[AnnotatedPayloadField], label: &str) -> Option Option<(String, solana_parser::solana::structs::Idl)> { + let path = std::env::var("IDL_FILE").ok()?; + let json = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("IDL_FILE={path}: {e}")); + match decode_idl_data(&json) { + Ok(idl) => Some((json, idl)), + Err(e) => { + eprintln!("IDL_FILE={path}: skipping — decode failed: {e}"); + None + } + } +} diff --git a/src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs b/src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs index d101ce72..55ae0a47 100644 --- a/src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs +++ b/src/chain_parsers/visualsign-solana/tests/fuzz_idl_parsing.rs @@ -35,6 +35,8 @@ use solana_parser::{decode_idl_data, parse_instruction_with_idl}; use solana_parser_fuzz_core::proptest as arb; use std::sync::Arc; +mod common; + // parse_instruction_with_idl ignores the program_id parameter (_program_id); // use an obviously fake value to avoid confusion with real known programs. const TEST_PROGRAM_ID: &str = "deadbeef1234deadbeef5678deadbeef"; @@ -849,19 +851,7 @@ fn size_guard_vec_u64_over_budget() { // // See scripts/fuzz_all_idls.sh to run against all embedded IDLs in one pass. -fn load_idl_from_env() -> Option<(String, solana_parser::solana::structs::Idl)> { - let path = std::env::var("IDL_FILE").ok()?; - let json = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("IDL_FILE={path}: {e}")); - match decode_idl_data(&json) { - Ok(idl) => Some((json, idl)), - Err(e) => { - // IDL failed validation (e.g. duplicate type names, cyclic references). - // Skip these tests — they are not valid inputs for real_idl_* tests. - eprintln!("IDL_FILE={path}: skipping — decode failed: {e}"); - None - } - } -} +use common::load_idl_from_env; /// Crash-safety test against a real IDL loaded from IDL_FILE. /// diff --git a/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs b/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs new file mode 100644 index 00000000..11caa613 --- /dev/null +++ b/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs @@ -0,0 +1,86 @@ +//! Structural invariant tests for real production IDLs. +//! +//! These tests assert properties of the decoded IDL structure itself — +//! no random input generation, no proptest. They verify that `decode_idl_data` +//! produces well-formed IDLs from production JSON files. +//! +//! Run: `IDL_FILE=/path/to/idl.json cargo test --test real_idl_validation` +//! All IDLs: `scripts/fuzz_all_idls.sh` + +mod common; + +use common::load_idl_from_env; + +/// Every instruction in the decoded IDL must have a discriminator computed by +/// decode_idl_data (either provided explicitly or derived via Anchor's SHA256 +/// scheme). A missing discriminator means the instruction is unreachable. +#[test] +fn real_idl_all_instructions_have_discriminators() { + let Some((_, idl)) = load_idl_from_env() else { + return; + }; + for inst in &idl.instructions { + let disc = inst + .discriminator + .as_ref() + .unwrap_or_else(|| panic!("instruction '{}' has no discriminator", inst.name)); + assert_eq!( + disc.len(), + 8, + "instruction '{}' discriminator must be 8 bytes, got {}", + inst.name, + disc.len() + ); + } +} + +/// No two instructions in the IDL may share a discriminator — a collision would +/// make them indistinguishable at parse time. +#[test] +fn real_idl_discriminators_are_unique() { + let Some((_, idl)) = load_idl_from_env() else { + return; + }; + let mut seen: std::collections::HashMap, &str> = std::collections::HashMap::new(); + for inst in &idl.instructions { + if let Some(disc) = &inst.discriminator { + if let Some(existing) = seen.get(disc) { + panic!( + "discriminator collision between '{}' and '{}': {:?}", + existing, inst.name, disc + ); + } + seen.insert(disc.clone(), &inst.name); + } + } +} + +/// No two instructions may share a name — duplicate names make dispatch results +/// ambiguous and hint at an IDL construction error. +#[test] +fn real_idl_instruction_names_are_unique() { + let Some((_, idl)) = load_idl_from_env() else { + return; + }; + let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new(); + for inst in &idl.instructions { + assert!( + seen.insert(inst.name.as_str()), + "duplicate instruction name: '{}'", + inst.name + ); + } +} + +/// compute_idl_hash must be deterministic — the same JSON must produce the +/// same hash on every call. +#[test] +fn real_idl_idl_hash_is_stable() { + let Some((json, _)) = load_idl_from_env() else { + return; + }; + let h1 = solana_parser::compute_idl_hash(&json); + let h2 = solana_parser::compute_idl_hash(&json); + assert_eq!(h1, h2, "IDL hash must be deterministic"); + assert!(!h1.is_empty(), "IDL hash must not be empty"); +} From cdaf4419f0e942779222c92a14f302500470e808 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Wed, 1 Apr 2026 14:38:37 -0400 Subject: [PATCH 10/14] Address code review: Solana-scope workflows, pin toolchain, extract nightly env var - Rename fuzz.yml -> fuzz-solana.yml and proptest.yml -> proptest-solana.yml with chain-scoped workflow names for future multi-chain CI - Extract nightly toolchain version to NIGHTLY_VERSION env var in fuzz workflow - Pin rust-toolchain.toml to 1.88.0 to match StageX pallet-rust:1.88.0 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/{fuzz.yml => fuzz-solana.yml} | 11 +++++++---- .../workflows/{proptest.yml => proptest-solana.yml} | 2 +- src/rust-toolchain.toml | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) rename .github/workflows/{fuzz.yml => fuzz-solana.yml} (88%) rename .github/workflows/{proptest.yml => proptest-solana.yml} (98%) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz-solana.yml similarity index 88% rename from .github/workflows/fuzz.yml rename to .github/workflows/fuzz-solana.yml index 5d9774bd..0e91baef 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz-solana.yml @@ -1,9 +1,12 @@ -name: Fuzz Testing +name: "Fuzz Testing: Solana" on: pull_request: types: [opened, synchronize, reopened, labeled] +env: + NIGHTLY_VERSION: nightly-2026-03-13 + jobs: fuzz: if: contains(github.event.pull_request.labels.*.name, 'fuzz') @@ -18,7 +21,7 @@ jobs: - name: Install Rust (nightly) uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 with: - toolchain: nightly-2026-03-13 + toolchain: ${{ env.NIGHTLY_VERSION }} - name: Install cargo-fuzz run: cargo install cargo-fuzz --locked - name: Cache Rust dependencies @@ -50,12 +53,12 @@ jobs: - name: Fuzz fuzz_transaction_string (30s) id: fuzz_transaction_string continue-on-error: true - run: cargo +nightly-2026-03-13 fuzz run fuzz_transaction_string -- -max_total_time=30 + run: cargo +${{ env.NIGHTLY_VERSION }} fuzz run fuzz_transaction_string -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - name: Fuzz fuzz_versioned_transaction (30s) id: fuzz_versioned_transaction continue-on-error: true - run: cargo +nightly-2026-03-13 fuzz run fuzz_versioned_transaction -- -max_total_time=30 + run: cargo +${{ env.NIGHTLY_VERSION }} fuzz run fuzz_versioned_transaction -- -max_total_time=30 working-directory: src/chain_parsers/visualsign-solana/fuzz - name: Label PR on fuzz failure env: diff --git a/.github/workflows/proptest.yml b/.github/workflows/proptest-solana.yml similarity index 98% rename from .github/workflows/proptest.yml rename to .github/workflows/proptest-solana.yml index 40123c18..5e57d54e 100644 --- a/.github/workflows/proptest.yml +++ b/.github/workflows/proptest-solana.yml @@ -1,4 +1,4 @@ -name: Property Tests +name: "Property Tests: Solana" on: pull_request: diff --git a/src/rust-toolchain.toml b/src/rust-toolchain.toml index bb2af9b2..dd5e6333 100644 --- a/src/rust-toolchain.toml +++ b/src/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.88" +channel = "1.88.0" components = [ "rustfmt", "cargo", "clippy" ] profile = "minimal" From 4d0968587ff717126a3687f31c248eca33371abf Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Thu, 2 Apr 2026 19:36:32 -0400 Subject: [PATCH 11/14] ci: add parser_cli to StageX build matrix Add Containerfile for parser_cli (Solana-only) with cargo test before build. Tests run first to fail fast and warm the compilation cache. Add Makefile target and stagex.yml matrix entry. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/stagex.yml | 1 + Makefile | 4 ++++ images/parser_cli/Containerfile | 28 ++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 images/parser_cli/Containerfile diff --git a/.github/workflows/stagex.yml b/.github/workflows/stagex.yml index b655f784..04b1fdbe 100644 --- a/.github/workflows/stagex.yml +++ b/.github/workflows/stagex.yml @@ -37,6 +37,7 @@ jobs: matrix: target: - name: parser_app + - name: parser_cli - name: parser_gateway steps: - name: Checkout sources diff --git a/Makefile b/Makefile index 071fcc3c..172737d9 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,10 @@ out/parser_app/index.json: \ $(shell git ls-files images/parser_app src) $(call build,parser_app) +out/parser_cli/index.json: \ + $(shell git ls-files images/parser_cli src) + $(call build,parser_cli) + out/parser_gateway/index.json: \ $(shell git ls-files images/parser_gateway src) $(call build,parser_gateway) diff --git a/images/parser_cli/Containerfile b/images/parser_cli/Containerfile new file mode 100644 index 00000000..fbef175e --- /dev/null +++ b/images/parser_cli/Containerfile @@ -0,0 +1,28 @@ +FROM stagex/pallet-rust:1.88.0@sha256:b9021d2b75eac64fe8b931d96dde63ef11792e5023cee77c3471ccc34a95a377 AS build + +# Rust configuration +ENV RUSTFLAGS='-C target-feature=+crt-static' +ENV CARGOFLAGS='--target x86_64-unknown-linux-musl --no-default-features --locked --release' + +# Directory for Rust artifacts +ENV RELEASE_DIR=/src/target/x86_64-unknown-linux-musl/release + +# Load Rust sources +ADD src /src +WORKDIR /src/ + +# pre-fetch all workspace deps; we need them to build with `--network=none` later +RUN cargo fetch + +WORKDIR /src/parser/cli +RUN --network=none <<-EOF + set -eu + cargo test ${CARGOFLAGS} --features solana + cargo build ${CARGOFLAGS} --features solana + mkdir -p /rootfs/usr/local/bin + mv ${RELEASE_DIR}/parser_cli /rootfs/usr/local/bin/ +EOF + +# Use busybox as a base so we can easily cp the pivot binary if needed +FROM stagex/core-busybox:1.36.1@sha256:cac5d773db1c69b832d022c469ccf5f52daf223b91166e6866d42d6983a3b374 AS package +COPY --from=build /rootfs/. . From 05d335fd02225c1d2eed72eded47f4d33aa4b607 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Fri, 10 Apr 2026 10:05:02 -0400 Subject: [PATCH 12/14] ci: label parser_cli build as Solana-specific in stagex matrix Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/stagex.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stagex.yml b/.github/workflows/stagex.yml index 04b1fdbe..bc153c1a 100644 --- a/.github/workflows/stagex.yml +++ b/.github/workflows/stagex.yml @@ -21,7 +21,7 @@ on: jobs: parser: - name: Build parser images + name: Build ${{ matrix.target.label || matrix.target.name }} runs-on: ubuntu-latest timeout-minutes: 60 permissions: @@ -38,6 +38,7 @@ jobs: target: - name: parser_app - name: parser_cli + label: "parser_cli (Solana)" - name: parser_gateway steps: - name: Checkout sources From f994ed1943dbd81f354c975afb8cfd148454ff11 Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Fri, 10 Apr 2026 10:45:40 -0400 Subject: [PATCH 13/14] Address code review: use short Idl type, fix doc comment, require discriminators - Use imported `Idl` type alias instead of fully-qualified path - Update doc comment to note decode failures are logged, not silent - Require discriminators on all instructions in uniqueness test Co-Authored-By: Claude Opus 4.6 (1M context) --- .../visualsign-solana/tests/common/mod.rs | 4 ++-- .../tests/real_idl_validation.rs | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/chain_parsers/visualsign-solana/tests/common/mod.rs b/src/chain_parsers/visualsign-solana/tests/common/mod.rs index 7503d8a2..5657747c 100644 --- a/src/chain_parsers/visualsign-solana/tests/common/mod.rs +++ b/src/chain_parsers/visualsign-solana/tests/common/mod.rs @@ -149,8 +149,8 @@ pub fn find_text(fields: &[AnnotatedPayloadField], label: &str) -> Option Option<(String, solana_parser::solana::structs::Idl)> { +/// allowing `real_idl_*` tests to be skipped in CI (decode failures are logged to stderr). +pub fn load_idl_from_env() -> Option<(String, Idl)> { let path = std::env::var("IDL_FILE").ok()?; let json = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("IDL_FILE={path}: {e}")); match decode_idl_data(&json) { diff --git a/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs b/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs index 11caa613..868c7be4 100644 --- a/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs +++ b/src/chain_parsers/visualsign-solana/tests/real_idl_validation.rs @@ -43,15 +43,17 @@ fn real_idl_discriminators_are_unique() { }; let mut seen: std::collections::HashMap, &str> = std::collections::HashMap::new(); for inst in &idl.instructions { - if let Some(disc) = &inst.discriminator { - if let Some(existing) = seen.get(disc) { - panic!( - "discriminator collision between '{}' and '{}': {:?}", - existing, inst.name, disc - ); - } - seen.insert(disc.clone(), &inst.name); + let disc = inst + .discriminator + .as_ref() + .unwrap_or_else(|| panic!("instruction '{}' has no discriminator", inst.name)); + if let Some(existing) = seen.get(disc) { + panic!( + "discriminator collision between '{}' and '{}': {:?}", + existing, inst.name, disc + ); } + seen.insert(disc.clone(), &inst.name); } } From 2bf5b2cababe17e85f1be49d70f0db6c2a75d64a Mon Sep 17 00:00:00 2001 From: Shahan Khatchadourian Date: Fri, 10 Apr 2026 23:03:37 -0400 Subject: [PATCH 14/14] ci: label-triggered stagex builds and cache/credential fixes Incorporates changes from PR #226: - stagex.yml: PR builds require `stagex` label instead of auto-triggering on path changes. GHCR login moved after build step. - main.yml: disable setup-rust-toolchain built-in cache which fails because Cargo.toml is in src/, not repo root. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/main.yml | 1 + .github/workflows/stagex.yml | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 452ceb67..7e4d6595 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,7 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 with: components: clippy, rustfmt + cache: false - name: Cache Rust dependencies uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 diff --git a/.github/workflows/stagex.yml b/.github/workflows/stagex.yml index bc153c1a..9c54d214 100644 --- a/.github/workflows/stagex.yml +++ b/.github/workflows/stagex.yml @@ -12,16 +12,15 @@ on: - "images/**" - "Makefile" pull_request: - paths: - - ".github/**" - - "src/**" - - "images/**" - - "Makefile" + types: [labeled] workflow_dispatch: # Allows manual invocation jobs: parser: name: Build ${{ matrix.target.label || matrix.target.name }} + if: >- + github.event_name != 'pull_request' || + github.event.label.name == 'stagex' runs-on: ubuntu-latest timeout-minutes: 60 permissions: @@ -49,7 +48,6 @@ jobs: uses: ./.github/actions/docker-setup with: dockerHub: ${{ secrets.DOCKERHUB_API_KEY }} - ghcr: ${{ secrets.GITHUB_TOKEN }} - name: Build ${{ matrix.target.name }} shell: bash @@ -60,6 +58,10 @@ jobs: run: | env -C out/${{ matrix.target.name }} tar -cf - . | docker load + - name: Login to GHCR + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + - name: Upload to GHCR run: | for tag in ${tags}; do