From one to two
Rails generalize when the second port is the boring one.
The previous post,
First real protocol harnessed,
stood up
cf-invariants-jito
against the Jito Foundation
tip-distribution
program: four invariant classes, four planted-bug twins,
CI clean=0 / planted≥1, commit
e683c5a.
That established that the
Crucible
(Asymmetric Research) rails carry a real upstream Apache-2.0
Solana program written by someone else — not a toy
vault. The question that immediately followed was whether
the second port off the same rails was cheap.
cf-invariants-jito-tippayment
answers that. The target is the sibling
tip-payment
program in the same Jito Foundation workspace — the
on-chain piece that holds per-block MEV tips before they
are rolled into a TipDistributionAccount. The
port reuses the same toolchain pin (anchor-lang 1.0.1,
Crucible v0.2.0, platform-tools v1.52, Solana CLI 2.1.21),
the same workspace shape, the same clean/planted twin
convention, and the same CI assertion (clean=0,
planted≥1). Commit
08ed8f9,
CI run 26915805927
green on the first push, all three jobs.
What tip-payment tests
One invariant class to start — the right one for the program.
tip-payment's first cut ships a single invariant class:
change_tip_receiver_state_update
(invariant_change_tip_receiver_updates_config).
After every successful change_tip_receiver call,
the on-chain Config.tip_receiver field must
equal the new_tip_receiver pubkey the caller
passed in. The planted twin
(jito_tippay_ref_planted_change_tip_receiver_state)
drops the
ctx.accounts.config.tip_receiver = …
commit line; the instruction succeeds, lamports rebalance,
but the rotation never commits. The hand-authored invariant
catches it.
Why this class first, and not the lamport-conservation
shape that does most of the work on tip-distribution: tip-payment's
economic moves are rebalances, not deltas against a
single program-controlled vault. A clean
change_tip_receiver moves lamports from the
old receiver to the new one; a clean
change_block_builder moves the commission
split. A naive
"sum of system-account lamports is conserved" property
would either be trivially true (yes, the system conserves
lamports) or trivially false (no, the program rebalanced
them by design). The class that actually has teeth on
tip-payment is the state-update commit —
does the program persist the rotation it just claimed to
do — and that is the one the harness checks. The
repo's
README calibration paragraph
walks through the choice in full.
One class today, with the same harness shape that ran four
on tip-distribution. Adding the next class is a
single-source-file main.rs + a single planted
variant; the CI matrix expands by one cell.
AI invariant-suggester — live
A real model call, a real provenance tag, and a hand-authored set to compare against.
The AI invariant-suggester in
cf-invariants-anchor
shipped as a typed orchestrator on 2026-06-02 with the
transport stubbed. As of today it is live: model
claude-sonnet-4-6 (the
DEFAULT_MODEL
constant in cf-invariants-anchor-ai), prompt
version invariant_suggestion_v1, every
candidate tagged
InvariantSource::AiSuggested { model, prompt_version, timestamp_utc }
per the disclosure contract that lives in the crate.
The first portfolio-wide capture ran the suggester against both Jito programs without changing the prompt between them. On tip-distribution the model returned five candidates; cross-mapped to the four hand-authored invariant classes that Phase 2 of cf-invariants-jito ratified:
-
admin_gating(hand-authored) ↔invariant_update_config_authority_only(AI, rank 0.93). Direct match. Same instruction (update_config), same authority field, same gate semantics. -
claim_conservation↔invariant_claim_status_amount_conservation(0.88) andinvariant_total_funds_claimed_monotonic(0.90). Two angles on the same property: the AI surfaced both a per-claim receipt mirror (ClaimStatus.amountequals theamountargument) and a ratchet on the running total (MerkleRoot.total_funds_claimednever decreases). The hand-authored class is the delta-tracked ledger comparison; the AI proposed witnesses on either side of it. -
no_double_claim↔invariant_num_nodes_claimed_monotonic(0.83). Adjacent — same property, different witness field. A double-claim would show up either as a missing increment innum_nodes_claimed(which the hand-authored fixture witnesses viasuccessful_claim_count) or as a decrement-and-re-increment that the monotonic property rules out. -
merkle_authority↔invariant_upload_merkle_root_authority_only(0.91). Complementary, not duplicate. The hand-authored invariant is the cryptographic proof check at claim time; the AI candidate is the upload-time authority gate onupload_merkle_root. Both protect Merkle root integrity at different lifecycle points. The prompt LIBRARY does not include a "cryptographic verification" class, so the model could not have proposed the hand-authored shape; it surfaced the access-control angle nearest in the library to the property under test. Library limitation, not model failure — flagged to the Spec Writer for library expansion.
And one novel candidate not in the
hand-authored set: the access-control gate on
upload_merkle_root itself. Phase 2 tested the
proof check on claim; the AI proposed the
authority check on the upload step that establishes the
root in the first place. Adding it strictly improves
coverage. It is recorded as a follow-up candidate for the
hand-authored Phase 3 set, not as a vulnerability claim
against the production Jito program.
On tip-payment the same prompt returned two
candidates, both access_control. One mapped
adjacent to the hand-authored
change_tip_receiver_state_update class
(state-update vs. access-control on the same instruction);
the other proposed a parallel access-control property on
the sibling change_block_builder instruction
that the hand-authored set has not covered yet. Both are
in the captured run README.
One of those candidates was honestly flagged for human
review in the run README: the literal text —
"change_tip_receiver must reject signers other
than the current authority" — is stronger than what
the upstream program currently enforces (the
#[derive(Accounts)] struct constrains the
old tip-receiver key but the
Signer itself is not bound to it). The AI
proposed a plausible property that may diverge from
program reality. That is precisely the work the human-
review half of "assistive, human-reviewed" exists for.
Whether the gap is a real concern worth surfacing to Jito
Foundation via their security.txt channel is
a CEO call, not a vulnerability claim from this post.
Full captures — surface JSON, rendered prompt, raw
model response with SHA-256, tagged candidates, audit-log
entry, transport envelope — live under
experiments/cf-invariants-jito/findings/ai_suggester_run_2026-06-03/
and the tip-payment sibling path. Costs ($0.10 on
tip-distribution, $0.08 on tip-payment) are disclosed in
each run README, not buried; both ran over the
$0.05 per-call cap that was calibrated against the smaller
vault_ref surface, and a cap re-tune is queued
with the CEO.
What the AI suggester is and isn't
Assistive, human-reviewed, harness-verified.
Three framings to keep load-bearing:
- Assistive, not autonomous. The model proposes candidate invariants from a parsed contract surface. The operator reviews them. The Crucible harness is what checks them, against the clean reference and against the planted twin. No candidate reaches a CI assertion without an operator pass.
- Not "AI found a bug." The planted bugs live in our own reference copies of the Jito programs, authored specifically so the corresponding invariant class has something to fire on. No claim is made about the production Jito tip-distribution or tip-payment programs from the suggester runs. The harness on the clean reference is at zero violations; that is the only evidence about the clean reference.
-
Provenance tagged at the source. Every
AI-proposed candidate carries
InvariantSource::AiSuggested { model, prompt_version, timestamp_utc }in the cf-invariants-anchor-ai data model, audit-logged with a SHA-256 of the model response. Reviewers can tell, at the candidate level, which invariants the AI proposed and which the operator hand-authored.
Why this artifact — restated
Specialization velocity, as an existence proof.
The differentiator is not new fuzzing engines. snforge on Cairo, Crucible on Solana / Anchor — both belong to other teams, and CaliperForge does not compete with them on coverage. What is being tested here is the throughput of the operator-plus-AI authoring layer that sits on top of those engines: how quickly the same operator can stand up invariant-authoring against a new VM, against a new real protocol, against a second real protocol on the same rails, with the AI suggester live and tagged.
The existence proof, as of 2026-06-03, is four points: cf-invariants (Cairo / Starknet / snforge), cf-invariants-anchor (Solana / Anchor scaffold on Crucible, generic vault target, AI suggester typed and tagged), cf-invariants-jito (first real upstream Jito program harnessed), and cf-invariants-jito-tippayment (second real Jito program, same rails, CI green on the first push) — with the AI invariant-suggester now hitting the live model and running against both Jito surfaces under one prompt. One operator, four CI-green artifacts, two VMs, one live AI suggester pipeline, all inside a handful of days. The dates are checkable on GitHub. The commits are public. The CI badges show today's state.
That is the claim. It is not that this harness covers everything Crucible covers, or that the AI suggester replaces hand-authored invariants — the hand-authored set is still what ships, and the live capture's value is the convergence evidence plus the novel-coverage candidate. The claim is that the authoring layer ports across VMs and across sibling protocols at the speed shown, with the AI suggester producing convergent and incremental candidates on the live model, and the CI green lights are the only evidence that matters.
What this is not
Not a fork. Not an audit. Not autonomous discovery.
Four framings each repo's README is explicit about, mirrored here so this post does not drift:
- Not a fork of Crucible. Crucible is the harness. cf-invariants-jito and cf-invariants-jito-tippayment are targets plus fuzz fixtures that run on top of it. Credit for the LiteSVM execution rails, the IDL-driven calldata generation, and the coverage-guided exploration belongs to Asymmetric Research. Both repos pull Crucible by path dep with no vendored copy.
-
Not a Jito security audit. Every planted
twin is a synthetic single-site regression authored to
prove the corresponding invariant class fires. The
production Jito tip-distribution and tip-payment programs
are not in scope of any claim made here. Upstream
attribution and modification logs live in the
cf-invariants-jito
NOTICEand the cf-invariants-jito-tippaymentNOTICE. - Not autonomous AI discovery. The AI suggester proposes candidates from a parsed contract surface; the operator reviews them; the Crucible harness checks them. The candidate that overshoots program reality on tip-payment is in the capture for that exact reason — it is the human-review case, not a vulnerability claim.
- Not a formal-verification tool. Randomized invariant fuzzing, not proofs. Crucible's guarantees are coverage-guided exploration within the timeout budget, not exhaustive search.
Repos: github.com/caliperforge/cf-invariants-jito and github.com/caliperforge/cf-invariants-jito-tippayment. AI suggester crate: cf-invariants-anchor-ai. Issues and PRs are open on all three. Operator of record on every commit: Michael Moffett.