Skip to content

Resolve the Solidity verification toolchain from the repo's own CI - #195

Merged
thedavidmeister merged 9 commits into
mainfrom
2026-08-04-issue-116-sol-toolchain-from-repo-ci
Aug 5, 2026
Merged

Resolve the Solidity verification toolchain from the repo's own CI#195
thedavidmeister merged 9 commits into
mainfrom
2026-08-04-issue-116-sol-toolchain-from-repo-ci

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The producer verified Solidity through a hardcoded github:rainlanguage/rainix#sol-shell while the same sentence said "the repo's" for Rust and TS. That flake tracks rainix HEAD and no repo's CI runs it, so "it passes locally" was never a claim about CI — cyclofinance/cyclo.sol#42 is parked having spent its one permitted back-off attempt producing a forge fmt diff the repo's own pinned forge fmt --check could not accept.

What the survey found, and why "use the repo's flake" is not the fix

Every non-archived repo with a root foundry.toml across the pipeline's org scope (ORGS="rainlanguage cyclofinance S01-Issuer"), 45 repos, classified by what its push/PR-gating workflows actually invoke — the tally below is the built binary's own output over all 45 real workflow sets:

mode n what its Solidity checks run in
rainix-pin 33 uses: rainlanguage/rainix/.github/workflows/rainix-sol.yaml@main → every step is nix develop github:rainlanguage/rainix/${RAINIX_SHA}#sol-shell -c …, and RAINIX_SHA is declared in the reusable, currently 53e96a7d (2026-07-10)
repo-flake 4 a bare nix develop -c rainix-sol-<task> → the calling repo's flake, at its own flake.lock pin. cyclo.sol, flow, rain.tier.interface, st0x.atomic-bridge
foreign 4 a forge installed without nixfoundry-rs/foundry-toolchain@v1, three of them at nightly. rain.will-overflow, rain.erc1820, view-quoter-v3, dvin.poc
conflict 1 st0x.deploy gates on both: the reusable at RAINIX_SHA, and git-clean.yaml (on: [push]) running forge fmt at unpinned rainix HEAD
absent 3 nothing gates Solidity on a push: rain.solver, sushixswap-v2, st0x-timelock-deploy

So the issue's ask — verify with the repo's own flake, as Rust and TS already do — is exactly right for 4 repos and wrong for 33. Those 33 never read their own flake.lock in a Solidity check; nix develop <dir> -c there would be the same bug mirrored (rain.math.float pins rainix f22d4dcaca61, 2026-06-01; its CI runs 53e96a7d, 2026-07-10).

There is therefore no rule to memorise, which is why this is computed per checkout rather than written into the prompt.

The change

pr-review-report sol-toolchain <dir> reads the checkout's own push/PR workflows and prints the nix develop prefix its CI will judge with:

$ pr-review-report sol-toolchain <cyclo.sol clone>
mode: repo-flake
check: .github/workflows/rainix.yaml  the repo's own flake (nix develop, no flake argument)
flake: present
verify: nix develop <clone> -c

$ pr-review-report sol-toolchain <rain.math.float clone>
mode: rainix-pin
check: .github/workflows/rainix-sol.yaml  rainix reusable @main
flake: present
verify: nix develop github:rainlanguage/rainix/53e96a7d0a97d7c7c75c3b2412521324776fdac6#sol-shell -c

Exit 3 is the issue's "legible record rather than a silent fallback", and the mode: line says which of four distinct facts it is — never one undifferentiated "no answer":

$ pr-review-report sol-toolchain <rain.will-overflow clone>          # exit 3
mode: foreign
check: .github/workflows/slither.yml  NOT NIX — foundry-rs/foundry-toolchain@v1 (version: nightly)
check: .github/workflows/test.yml     NOT NIX — foundry-rs/foundry-toolchain@v1 (version: nightly)
flake: ABSENT
error: this checkout's Solidity checks run on foundry-rs/foundry-toolchain@v1 (version: nightly),
which is NOT a nix shell — there is no `nix develop` that enters it, so you cannot verify against
what CI will actually judge. This is NOT `absent`: there IS a toolchain here and it is one you
cannot match, so an exact check (`forge fmt --check`, `forge snapshot --check`) can red on a diff
that is correct in every shell you can open. Say so on the PR, naming that toolchain, and expect
the mismatch instead of spending a back-off attempt discovering it.

$ pr-review-report sol-toolchain <st0x.deploy clone>                 # exit 3
mode: conflict
check: .github/workflows/rainix-sol.yaml  rainix reusable @main
check: .github/workflows/git-clean.yaml   explicit flake github:rainlanguage/rainix#sol-shell
error: … 2 different Solidity toolchains … verify against the toolchain of the CHECK you are
fixing and say which.

Steps 4 and 3d take their prefix from it and route exit 3 to the run record, naming foreign specifically. Step 1's health check keeps the unpinned URL and a test pins that it does: "can this box realise a rainix shell and run forge at all" is a different question from "will this satisfy that repo's CI", and pinning it would prove a shell no clone enters.

Two facts the issue asked to settle

rainprotocol/rainix and rainlanguage/rainix are one repo. gh api repos/rainprotocol/rainix answers full_name: rainlanguage/rainix, same head SHA, same pushed_at — an org-rename redirect. Several repos' flake.nix say rainprotocol and their flake.lock records owner rainlanguage. The owner path in the issue is cosmetic; the whole skew is the rev.

sol-shell and default are the same Solidity toolchain at a given rev. rainix defines one sol-build-inputs (foundry-bin, slither-analyzer, solc_0_8_25) and both shells consume it; sol-shell is only the slimmer closure. So a repo-flake repo whose CI enters default is correctly verified through nix develop <dir>, and the reusables' #sol-shell is not a third thing.

Dev-shell availability: 40 of the 45 have a flake.nix, and all 40 re-export rainix's shells wholesale (devShells = rainix.devShells.${system}), so nix develop <dir> resolves everywhere it is asked to. The 5 without one — rain.erc1820, rain.will-overflow, sushixswap-v2, view-quoter-v3, dvin.poc — are the interesting case rather than a harmless one: four of them still gate Solidity, on a forge installed by ACTION. rain.will-overflow runs forge fmt --check on foundry nightly as a push gate. There is no nix develop that enters that, so the producer cannot match it — a fact it must state, not one it may read as freedom to pick a shell. mode: foreign says exactly that and names the toolchain; folding it into absent would leave #116's widest instance reported as "no toolchain here".

Known bound, stated rather than implied: foreign is detected from the ACQUISITION (foundry-rs/foundry-toolchain, foundryup, the paradigm installer), never from a bare forge in a run: — st0x.deploy opens nix develop --command bash -c ' and calls forge on the NEXT line, so an invocation-based reading would report a nix repo as foreign. A repo that gets forge a third way (a container image, a hand-rolled curl) still reads as absent; a_third_way_of_getting_forge_is_a_known_blind_spot puts that limit on the record.

The trigger block is also read in all four spellings GitHub accepts — YAML 1.1 reads a bare on as the boolean true, so formatters emit "on": and a 1.1 round trip can emit true:. No org repo writes those today, but the failure mode was a silent misclassification into absent, so it is handled rather than assumed. Comments inside that block are stripped, so # runs on push is prose rather than a gate.

Latent paths back into a wrong verdict, closed

Five CodeRabbit findings on this PR, all real, all fixed. None changes any of the 45 org repos' classification — verified by re-running the binary over every one and diffing, which comes back identical — so they were latent, and four of them landed in the same absent bucket this PR exists to stop being a dumping ground:

  • a column-zero comment closed the on: block (on: / # gate every branch / push: read as absent on a workflow that gates every branch);
  • a commented-out step registered as a toolchain, making a one-toolchain repo report mode: conflict — a disagreement invented out of a comment;
  • an unreadable checkout reported absent, so knowing nothing looked like knowing nothing gates it. There is now mode: unreadable, and a single unreadable FILE counts;
  • an unpinned explicit flake was handed over as authoritative. It still gets its command (it is what CI runs, and withholding it leaves no action at all) plus a warn: that it floats;
  • a relative dir reached the printed command verbatim while the doc comment claimed absolute. Canonicalized.

The sixth — route every exit-3 mode to flag-design, and give foreign a non-nix verify command — is rejected on the record: exit-3 modes are properties of the repo's CI, not of the PR, so flagging them re-files one standing fact per PR (the shape #108 retired flag-blocked-infra for), and acquiring foundry outside nix is refused by this repo's own tools come from the flake gate (#76).

Closes #116

QA

  • Discriminating tests: the_verify_steps_take_the_solidity_toolchain_from_the_repos_own_ci — fails on base (mutation 6 restores the base prompt clause byte-for-byte and it goes red). a_non_nix_gating_toolchain_is_never_reported_as_no_toolchain and absent_and_foreign_say_different_things are the follow-up's discriminators: both fail on this PR's own first round, where a foundry-toolchain repo classified as absent. The remaining tests cover code that does not exist on base, so their proof is the mutation table.
  • Mutations applied (18 applied, 18 killed): in_on = line.starts_with("on:")in_on || …a_push_after_the_on_block_does_not_count; is_sol_check's forge soldeer exclusion deleted → a_soldeer_install_is_not_a_solidity_check; None | Some(".")Nonea_nix_develop_step_is_read_for_its_flake_and_its_command; Some(Err(why)) => Err(…)Ok("…rainix#sol-shell -c")an_unreadable_pin_offers_no_command_at_all; agreed_rainix_sha's disagreement branch deleted → the_leaves_must_agree_on_the_pin; step 4's VERIFY clause reverted to the base hardcoded flake → the prompt test; foreign_sol_toolchain recognises nothing, folding a non-nix gating toolchain back into absent → killed by a_non_nix_gating_toolchain_is_never_reported_as_no_toolchain + absent_and_foreign_say_different_things + a_conflict_that_includes_a_foreign_toolchain_says_one_side_is_unreachable; the foreign verdict text replaced by the absent textabsent_and_foreign_say_different_things; WORKFLOW_TRIGGER_KEYS cut to ["on:"]a_quoted_or_boolean_on_key_is_still_the_trigger_block; strip_yaml_comment returns the line unchanged → a_comment_inside_the_trigger_block_is_not_a_trigger; is_nix()truea_conflict_that_includes_a_foreign_toolchain_says_one_side_is_unreachable; foundry_toolchain_version never reads a version → a_non_nix_gating_toolchain_is_never_reported_as_no_toolchain; step 4 stops teaching foreign → the prompt test. One earlier candidate (workflow_gates_a_push's redundant return false) proved EQUIVALENT — the assignment below it already carried the boundary — so the branch was deleted rather than reported as a coverage gap. The five CodeRabbit fixes are mutation-covered too: the block boundary reading the RAW line → a_column_zero_comment_does_not_close_the_trigger_block; the loop not stripping comments → a_commented_out_step_registers_no_toolchain; flake_ref_is_pinned always true → a_flake_ref_is_pinned_only_when_it_names_a_revision + an_unpinned_explicit_flake_still_gets_its_command_but_is_flagged_as_floating; the unreadable verdict falling through to the ordinary one → an_unreadable_checkout_is_not_absent; and a per-file read failure swallowed → a_single_unreadable_workflow_file_is_reported_not_dropped. That last mutant SURVIVED at first, because the branch sat in the impure directory walk where no test could reach it; collect_workflows moves the decision out and the re-run kills it.
  • Oracle: the org's live CI, not this code. Every classification was derived by reading the 45 repos' real workflows and rainix's real reusables over the GitHub API, and the fixtures are verbatim excerpts of cyclo.sol's rainix.yaml, a reusable consumer's rainix-sol.yaml, st0x.deploy's git-clean.yaml and rain.will-overflow's test.yml. The end-to-end check runs the built binary over all 45 real workflow sets; RAINIX_SHA (53e96a7d…) is the live value in rainix-sol-static.yaml@main, read independently of the subcommand.
  • Category check: issue asks (a) verify Solidity with the toolchain the repo's CI uses, (b) keep the hardcoded URL for the startup health check with the two purposes separate, (c) decide what happens when a repo has no usable dev shell, (d) settle whether rainprotocol/rainix and rainlanguage/rainix are the same thing. Covered a, b, d, and c in its full shape: absent, foreign, conflict and a flake-less repo-flake are four distinguished outcomes, none of them a silent fallback. Not covered, deliberately: whether a toolchain-mismatch CI failure should stop consuming the back-off attempt — the mismatch is removed at source here and a "doesn't count" carve-out needs a typed discriminant the producer does not have.

thedavidmeister and others added 4 commits August 4, 2026 18:15
The producer verified Solidity through a hardcoded
`github:rainlanguage/rainix#sol-shell` while the same clause named "the
repo's" toolchain for Rust and TS. That flake tracks rainix HEAD and no
repo's CI runs it, so a green local `forge fmt` was never a claim about
CI: cyclofinance/cyclo.sol#42 spent its one permitted back-off attempt
producing a diff the repo's own pinned `forge fmt --check` could not
accept.

`sol-toolchain <dir>` computes the answer per checkout instead, because
there is no org-wide one. Measured across the 45 foundry repos in the
pipeline's org scope: 33 gate Solidity on the rainix reusables and run at
the `RAINIX_SHA` the REUSABLE pins (their own flake.lock is never read by
those checks); 4 run a bare `nix develop -c rainix-sol-<task>` against
their own flake; 1 gates on two toolchains that can disagree; 7 gate no
Solidity check on a push at all. Exit 3 covers the last two and the
no-flake case — a fact for the run record rather than a silent fallback,
which is how the skew got reintroduced invisibly.

Steps 4 and 3d now take their prefix from it. Step 1's health check keeps
the unpinned URL: "can this box realise a rainix shell" is a different
question from "will this satisfy that repo's CI".

Closes #116

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The extra `return false` differed from the reassignment below it only for
a document with a second column-zero `on:` key, which is not YAML — an
equivalent mutant, and one a mutation pass reports as a coverage gap it
is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds sol-toolchain to detect repository-specific Solidity CI toolchains, resolve reusable pins, report unresolved states, and return exit code 3 when no single reproducible toolchain exists. Verification prompts now use this detection. A transitions table was reformatted.

Changes

Solidity toolchain verification

Layer / File(s) Summary
Toolchain detection and verdicts
pr-review-report-rs/src/main.rs
Adds workflow parsing, Nix and non-Nix toolchain detection, reusable pin resolution, verdict reporting, and comprehensive tests.
CLI command integration
pr-review-report-rs/src/main.rs
Adds the SolToolchain command and dispatches it to sol_toolchain_mode.
Verification workflow guidance
campaign-prompt.txt, pr-review-report-rs/src/main.rs
Updates conflict and new-issue verification to use detected toolchains, record unresolved states, and preserve the rainix shell for startup health checks.
Repository guidance formatting
CLAUDE.md
Widens the transitions table columns without changing its commands or descriptions.

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

Sequence Diagram(s)

sequenceDiagram
  participant CampaignWorkflow
  participant sol-toolchain
  participant RepositoryWorkflows
  participant GitHub
  CampaignWorkflow->>sol-toolchain: resolve checkout toolchain
  sol-toolchain->>RepositoryWorkflows: inspect push and PR workflows
  RepositoryWorkflows->>GitHub: resolve reusable RAINIX_SHA pins
  GitHub-->>sol-toolchain: return pin information
  sol-toolchain-->>CampaignWorkflow: return selected or unresolved verdict
  CampaignWorkflow->>CampaignWorkflow: run verification or record the unresolved state
Loading

Possibly related issues

  • rainlanguage/issue-pr-cron#116 — Replaces hardcoded rainix Solidity verification with each repository’s detected or pinned CI toolchain while retaining the startup health check.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: deriving the Solidity verification toolchain from each repository's CI configuration.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-08-04-issue-116-sol-toolchain-from-repo-ci

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

❤️ Share

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

thedavidmeister and others added 3 commits August 5, 2026 06:58
…sol-toolchain-from-repo-ci

# Conflicts:
#	CLAUDE.md
SolToolchain modelled three nix shapes, so a repo whose CI installs
Foundry WITHOUT nix matched none of them and fell into `absent` — "no CI
toolchain to match". That is false, and false in the worst place:
rain.will-overflow gates `forge fmt --check` on foundry NIGHTLY, which is
further from any shell the producer can open than the widest rainix pin
gap. The classifier was blind exactly where the skew is largest.

`Foreign(String)` carries the acquisition verbatim and reports
`mode: foreign` with its own text — there IS a toolchain here and it is
one you cannot enter, so name it and expect the mismatch. Recount over
all 45: 33 rainix-pin, 4 repo-flake, 4 foreign (rain.will-overflow,
rain.erc1820, view-quoter-v3, dvin.poc), 1 conflict, 3 absent.

Detection reads the ACQUISITION, not the invocation. A bare `forge` in a
`run:` proves nothing — st0x.deploy opens `nix develop --command bash -c`
and calls forge on the next line, so an invocation-based reading would
report a nix repo as foreign. A third way of getting forge is still a
blind spot; a test names that bound rather than leaving it implicit.

Also: the trigger block is recognised in all four spellings GitHub
accepts (YAML 1.1 reads bare `on` as true, so formatters emit `"on":`
and a 1.1 round trip can emit `true:`) — the failure was a silent
misclassification into the same `absent` bucket. And a comment inside
that block is stripped, so `# runs on push` is prose rather than a gate.

Refs #116

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

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

Inline comments:
In `@campaign-prompt.txt`:
- Line 72: Update the Solidity verification guidance around the sol-toolchain
exit-3 modes to define an explicit handoff and stop condition before mutation
testing or PR creation: require a non-Nix verification command from the
repository workflow for foreign toolchains, and direct conflict or missing
repo-flake cases to pr-review-report flag-design rather than permitting
fallback. Ensure absent also records the mode and stops appropriately, while
preserving the existing toolchain-selection rules.

In `@pr-review-report-rs/src/main.rs`:
- Around line 4817-4823: Strip YAML comments before parsing reusable and
toolchain declarations: update the loop around
sol_toolchains/sol_toolchain_lines to pass a strip_yaml_comment-processed line
to rainix_sol_reusable_ref, nix_develop_step, and foreign_sol_toolchain, or
apply it directly inside both rainix_sol_reusable_ref and nix_develop_step.
Ensure commented-out uses lines are ignored and do not create duplicate
toolchains or conflict mode.
- Around line 5097-5112: Update the workflow discovery logic around the read_dir
and read_to_string calls to report I/O failures instead of silently treating
them as absent workflows. Emit a distinct error message on the existing
error-reporting path for both unreadable workflow directories and individual
workflow files, while preserving the exit code of 3 and normal behavior for
successfully read entries.
- Around line 4800-4815: Update workflow_gates_a_push to strip YAML comments
before evaluating block boundaries, and skip comment-only or empty lines before
assigning in_on. Ensure column-zero comments do not close an active on: block,
while real column-zero keys still establish the correct boundary and trigger
detection remains unchanged.
- Line 5037: Update the SolToolchain::Explicit verdict handling to return exit 0
and an authoritative verify command only for references containing a pinned
revision; unpinned explicit flakes must not produce that successful verdict. Add
a regression test covering the single-Explicit HARDCODED_WF case, and document
explicit in the sol-toolchain CLI table in CLAUDE.md.
- Around line 24669-24672: Update sol_toolchain_mode to canonicalize the
provided dir into an absolute path before constructing SolToolchain and passing
it to sol_toolchain_lines. Preserve the existing command-reporting flow, and
emit a clear error if canonicalization fails.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f17ae671-19e8-4923-a743-ecc47a3e73c1

📥 Commits

Reviewing files that changed from the base of the PR and between 477ec7a and 654b4bb.

📒 Files selected for processing (3)
  • CLAUDE.md
  • campaign-prompt.txt
  • pr-review-report-rs/src/main.rs

Comment thread campaign-prompt.txt
Comment thread pr-review-report-rs/src/main.rs
Comment thread pr-review-report-rs/src/main.rs
Comment thread pr-review-report-rs/src/main.rs Outdated
Comment thread pr-review-report-rs/src/main.rs Outdated
Comment thread pr-review-report-rs/src/main.rs
thedavidmeister and others added 2 commits August 5, 2026 07:23
All five are CodeRabbit findings on this PR and all five are real. None
changes any of the 45 org repos' classification — verified by re-running
the binary over every one and diffing the result, which is identical —
so they are latent, and four of them land in the same `absent` bucket
this PR exists to stop being a dumping ground.

  A column-zero comment closed the `on:` block. The boundary test read
  the RAW line, so `on:` / `# gate every branch` / `  push:` reported
  `mode: absent` on a workflow that gates every branch. Strip first: a
  comment is not a key at any indentation.

  A commented-out step registered as a toolchain. Only
  `foreign_sol_toolchain` stripped comments, so a `# uses: …rainix-sol`
  above a live `nix develop -c` made a one-toolchain repo report
  `mode: conflict` — a disagreement invented out of a comment, refused
  with exit 3. Stripping moves into the loop, once, for all parsers.

  An unreadable checkout reported `absent`. A directory that cannot be
  read yields the same empty list a repo with no Solidity CI does, and
  knowing nothing is not knowing that nothing gates it. `mode:
  unreadable` says which, and a single unreadable FILE counts — that one
  could drop the only workflow that gates the repo.

  An unpinned explicit flake was handed over as authoritative. It is
  still the right command (it is what CI runs), so it is still printed;
  it now carries a `warn:` that it floats with a default branch and that
  matching text is not matching toolchain.

  A relative `dir` reached the printed command verbatim. That command is
  the whole deliverable and only reproduced CI from the directory it was
  typed in, while the doc comment claimed absolute. Canonicalized, with
  the failure reported rather than swallowed.

Refs #116

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-FILE read failure lived inside the impure directory walk, so a
mutant that swallowed it SURVIVED — the one gap in this PR's mutation
pass. `collect_workflows` takes already-taken reads and returns the
workflows plus the first failure, which is the same impure-edge /
pure-decision split the rest of this module already uses.

Refs #116

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

Copy link
Copy Markdown
Contributor Author

Reviewed 7ee1761: ready

Verified before merge: 20 SUCCESS / 1 skipped / 0 failures, MERGEABLE/CLEAN, 0 unresolved threads. 930 tests, 18/18 mutations killed.

I reviewed the first head and found a false negative: SolToolchain had three variants, all nix, so a repo whose CI installs Foundry WITHOUT nix matched none and fell into absent — which the PR described as "nothing is lost, there is no CI toolchain to match". rain.will-overflow runs forge fmt --check on foundry NIGHTLY as a push gate, so the classifier was blind precisely where the skew is widest: an unpinned nightly is further from the producer's shell than any pinned rainix rev.

The recount found FOUR, not the three I named — dvin.poc as well — and, more usefully, found why its own survey was wrong: foundryrepos.txt had no trailing newline, so while read silently dropped the last entry (view-quoter-v3), its fixture directory was never created, and the classifier was then "verified" against an empty directory and reported absent. The fixture set is now built with a count assertion. Final: 33 rainix-pin, 4 repo-flake, 4 foreign, 1 conflict, 3 absent.

The concern I had about foreign is answered in the text rather than left to the reader: it tells the producer to expect the mismatch INSTEAD OF SPENDING A BACK-OFF ATTEMPT DISCOVERING IT, which is the failure #116 opens with — one permitted attempt spent on a diff that could never satisfy the check. And the distinction lives in the type, not only the prose: is_matchable is !matches!(self, Foreign(_)), so the three nix modes are matchable and this one is not.

Three decisions I would keep. A fourth VARIANT rather than a fourth exit code, because 3 already means "no single verify: command" and there are now four ways to be there — one code per fact would be four codes for one question; the mode: line is the discriminant and the test asserts absent and foreign are DISJOINT rather than each non-empty. All four on: spellings handled rather than documented as a bound, since the failure was silent misclassification into absent, the exact class under repair. And comments stripped before the block-boundary test — I had called over-inclusion safe, which stopped being true once foreign existed, because over-inclusion can then manufacture a conflict.

CodeRabbit reviewed this head properly: five findings accepted, four of them landing in absent, and re-running the classifier over all 45 showed NONE changed a classification — latent, not regressions. Two rejected with reasons on the record, including routing exit-3 modes to flag-design: those are properties of the repo's CI rather than of the PR, so flagging would re-file one standing fact per PR, the shape #108 retired flag-blocked-infra for.

It also flagged the one place its first pass would have shipped a false all-killed: a mutant survived inside an impure directory walk, so it extracted collect_workflows to make the decision pure rather than recording the mutant as covered.

Remaining gap, unchanged from round one and correctly not smuggled in: nothing VERIFIES the producer used the shell it was told to. This is a prompt rule with the same enforceability as the one it replaces; a PreToolUse hook is feasible but needs to map an arbitrary command to its target clone.

@thedavidmeister
thedavidmeister merged commit 6e06280 into main Aug 5, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Producer verifies Solidity with a hardcoded rainix at HEAD, not the repo's pinned flake

1 participant