verified-rewards-loop · interactive companion everything on this page runs in this file — no server, no network

Verified rewards from a model checker

Candidate implementations are checked against a suite of temporal-logic properties; verdicts aggregate into a reward; every violation returns a counterexample trace. The two placements of that signal — training time and inference time — share one currency: the counterexample.

in-page checker · self-test
running…
00the primer · start here if model checking is new to you

What this page is trying to show

Language models write code now, and the standard recipe for making them better at it is reinforcement learning from verifiable rewards (RLVR): the model generates a candidate, a mechanical checker scores it, and the score trains the model. For code, the usual checker is a test suite. Tests have three structural limits: they certify behavior only on the cases someone thought to write; when they fail, they say “failed” and little else; and a model under training pressure learns to game them — pass the cases, miss the point.

The claim of this page, made runnable: for stateful systems in adversarial settings — here, a smart contract — a model checker over temporal-logic properties is a stronger reward oracle. It decides each property over every reachable state of a model of the system, and when a property fails it returns the failing behavior itself, a counterexample: structured training material that a bare pass/fail cannot supply.

The signal plugs in at two places. At training time, verdicts aggregate into a scalar reward that updates the model, and versioning the property suite makes capability drift measurable across model generations — re-checking is a re-run. At inference time, the counterexample steers the repair of one artifact (or ranks a best-of-n batch under a frozen model). The trade between the two is the classic one: inference-time placement works with any frozen model and guarantees each artifact, but the checking bill recurs per sample and the model itself learns nothing.

Model checking, from zero

The recipe has three moves. One — model the system: describe it as a finite set of states and the moves between them. The contract on this page boils down to five variables, which gives 128 possible states; every require() in the source becomes a rule about which moves are allowed, and a forbidden call simply changes nothing. Two — state the requirement as a property: a formula about how states may unfold over time, such as “an upgrade only ever executes after the delay has elapsed”. Three — check: an algorithm visits every reachable state, exhaustively, and returns either holds everywhere or a concrete run that breaks the property.

Two things are worth holding onto. The check is a decision procedure — a yes/no algorithm that always terminates, with no human in the loop and no “maybe”. And the negative answer is a behavior: a step-by-step run you can watch, replay and learn from. On this page the whole world fits in 128 states, so the exhaustive visit costs under a millisecond — the badge above ran the full battery when the page loaded.

two verification cultures · what each class of tool yields natively
Auto-active program verifiers & theorem proving
(Dafny-style contracts, proof assistants)
Model checking
(this page)
Object checkedthe code itself, over unbounded statea finite model of the system
Native guaranteecompositional proofs about the program — the strongest form of assurance software haseach property decided over every reachable state of the model
Automationproof search, guided by annotations the author writes (pre/postconditions, invariants)push-button decision procedure
Failure artifactan unproven obligation — where it fails, rarely whya counterexample: a concrete, executable trace to the violation
Under changeproofs break and need maintenancere-checking is a re-run; regression tracking comes for free
Standing costwriting specifications and invariantswriting the model — fidelity to the code is the gap to manage (see the colophon)
the concession — and the scoping rule for every comparison on this page
On their home ground, auto-active verifiers are stronger than anything this page does: they prove the code itself, over unbounded state, compositionally. The two families are complementary, and a rich enough proof system can encode almost anything — so every comparison here is scoped to what each class of tool yields natively, with a decision procedure attached, at push-button cost. That scoping is what the property ladder makes precise, one runnable file per rung.

Temporal logic — the six words you need

AG φ
on every path, at every step, φ holds. The workhorse of safety: “nothing bad, ever”.
→ P1, P2, P3
EF φ
some continuation reaches φ: a statement about possibility, quantifying over the tree of futures.
→ inside P5, the property that catches the frozen contract
G φ · F φ
always / eventually, along one run. F is how liveness is written: “the good thing does happen”.
→ rung 4, the fairness lab
O φ
“once”: at some earlier step of this run, φ held. Past-time logic — history in one operator.
→ P4, execution provenance
fairness
assumptions of the form “infinitely often” that make liveness honest: time keeps flowing, actors keep trying.
→ rung 4’s three premises
vacuous
proven only because the event the property constrains never happens. A contract that does nothing violates no safety property.
→ the frozen exhibit

One logic is never enough: EF (“a continuation exists”) has no equivalent in linear-time logic, which speaks about single runs; the fairness patterns G F / F G have no equivalent in branching-time logic. CTL and LTL are incomparable, the suite on this page deliberately uses both, and the same split lifts one level up to the probabilistic logics (PCTL, CSL) at the ladder’s top rung.

The cast: one contract, five candidates, an adversarial world

The running example is a timelock: a contract whose owner may propose an upgrade, must then let a delay elapse in public view, and only afterwards may execute it (or cancel). The delay is the whole point — it is the community’s window to react before a malicious upgrade goes live. The model’s world is adversarial by construction: at every step, anyone (owner or attacker) may attempt any call, and the checker explores all of it. Alongside the correct implementation stand four flawed candidates, each one line apart — a dropped delay check, dropped authorization, a dropped guard, and a copy-paste slip that quietly freezes the contract forever. Four bugs of the kind review misses, because every individual call still “works”.

Why this oracle sits well inside a reward channel

how to read this page
  • 01 model — the contract, its five candidates, and the state space drawn as a live map; drive it yourself, one call per step.
  • 02 suite — the five properties and the property lab: pick a candidate and a property, watch the verdict and the counterexample.
  • 03 reward — verdicts become a training signal; recompose the suite and watch the ranking react.
  • 04 frozen — the exhibit: reward hacking, caught by one progress property.
  • 05 ladder — one runnable example per property class, up to dense real time.
  • 06 colophon — what this demonstration simplifies, engines and licensing, and how every number on this page was checked.

Built for two speeds: the primer above assumes no background; if you verify software for a living, the three cards under the self-test badge jump straight to the exhibits.

The two placements share one currency, and it is the counterexample: the densest feedback a generator can receive at inference time, and the cheapest labeled negative at training time. Model checking manufactures that artifact natively, at push-button cost — the rest of this page is that sentence, runnable.
01the system under check

A timelock, its five candidates, and the world they run in

The artifact under verification is a timelock upgrade contract: the owner proposes an upgrade, a delay must elapse, then the owner may execute it (or cancel). The repository carries one correct implementation and four flawed candidates — each a one-line deviation of the kind code review misses precisely because every individual call still “works”.

Each candidate is modeled as a finite transition system in SMV. The translation is mechanical and the environment is adversarial by construction: caller and action are left unconstrained, so the checker explores every interleaving of calls by every party. Each Solidity require() becomes a transition guard; a call whose guard fails changes nothing — a stuttering step, mirroring revert semantics.

the five candidates · one line apart
How a contract becomes a model. One transition is one observable step of the contract’s world: a call (propose / execute / cancel) from some sender, or the passage of one time unit (wait). Time is discrete and the counter saturates: with DELAY = 2, the cap at 3 preserves the only fact the properties need — whether the delay has elapsed. Five state variables, 128 states, 8 successors per state. The state space is deliberately tiny; the argument it carries is the loop, and the modeling gap it accepts is stated honestly in the colophon.
state-space explorer 16 configurations × 8 free inputs

Drive mode

Pick a call and a sender. Accepted calls move the configuration; reverting calls flash and stay put. The five suite properties are monitored live on your run — including “execution still reachable from here”, which is the progress property P5 seen from the driver’s seat.

02the property suite

Five properties, three logics, weights that sum to one

The suite is a versioned data object (properties.json), separate from models and code: it is the reward’s specification. Each property carries a severity weight; the weights sum to 1, so the reward of a candidate is the weighted fraction of proven properties. The suite deliberately spans property classes and both logic families (the primer’s glossary says why one is never enough) — that choice is what the frozen exhibit and the ladder are about.

property lab · pick a candidate and a property verdicts computed live in this page
03from verdicts to reward

The verdict matrix, and the studio that turns it into a signal

Five candidates × five properties. Every verdict in this table is computed in your browser by the portal’s checker, then compared against the repository’s committed expected matrix — the same 35 instances its CI re-checks on every push.

the 35-instance verdict matrix
reward studio · compose the suite, watch the ranking react
Reward = Σ weights of proven properties over the enabled suite, renormalized so the enabled weights sum to 1 (with the full suite this is exactly the repository’s formula). Hatched segments are the weight a candidate lost — kept at full width so the price of each violation stays visible. ◐ marks a property that holds vacuously on that candidate. Ties break alphabetically, as in src/loop.py.

What the trainer consumes

The scalar is the policy-gradient signal. Alongside it travels a per-property signature and, per violation, a labeled negative: the counterexample trace packaged with the property it violates, its class, and its weight — material for counterexample-conditioned repair, preference pairs against a proven sibling, hard-negative curricula. Below, the committed reward record for one candidate, exactly as src/loop.py emits it.

04the frozen exhibit

The contract that does nothing, and the property that catches it

beat 1 · the bug

A copy-paste slip in executeUpgrade(): the guard demands upgrade_executed where it meant upgrade_proposed. Since executed starts false and only this function could ever set it, execution is unreachable — the contract accepts proposals and then holds them forever.

candidates/timelock_correct.smv
candidates/timelock_frozen.smv
beat 2 · the score

Under the full suite the frozen candidate scores 0.85 and ranks second — tied with no_proposal_guard, which fails a different property for a structurally different reason. The tie is informative rather than embarrassing: the scalar alone cannot tell a bricked contract from a provenance leak, and that is the argument for the per-property signature travelling with the scalar.

beat 3 · the trap
⚠ indistinguishable from the correct implementation

A contract that does nothing violates no safety property: under a safety-only suite the frozen candidate is the degenerate optimum a policy trained on an under-specified verified reward would discover. It is caught here by a property from a class that assertional reasoning does not natively speak — P5, AG (proposed → EF executed), an existential over continuations. Vacuous satisfaction has a two-decade literature as a specification-quality problem (Beer, Ben-David, Eisner and Rodeh, Formal Methods in System Design, 2001); placed inside a reward loop it stops being a lint and becomes a safety property of the training signal itself.

05the property ladder

One runnable example per property class

The baseline and the concession are in the primer: auto-active program verifiers prove the code itself, over unbounded state, compositionally — stronger on that ground than anything in this repository. The ladder’s criterion is what each class of tool yields natively, with a decision procedure attached, at push-button cost. Each rung up asks a kind of question the assertional frame does not natively speak, and each has a runnable file here.

rung 4 · fairness lab

Naive liveness — G (proposal_accepted → F (executes_now ∨ cancel_accepted)) — is false on the correct model, and the counterexample is a design discovery: the owner can re-propose forever, each proposal resetting the delay clock, an upgrade kept permanently pending without violating a single safety property (proposal churn). The formulation that survives carries three explicit fairness premises. Toggle them: the portal’s checker re-decides liveness live, and every lasso it returns names the assumption you dropped.

rung 5 · dense real time

“48 hours” is what deployment means, and dense time is a different algorithmic family: continuous clocks are first-class state, transitions interleave with time-elapse steps of arbitrary real duration, and the checker decides the property over that infinite-state system symbolically (SMT-based algorithms in nuXmv’s timed mode). The trace below is replayed, and structurally validated, by the portal — it is decided by nuXmv: an in-page explicit-state checker cannot enumerate a continuum, which is precisely what this rung adds.

06colophon & provenance

What this demonstration simplifies, and how this page was checked

Stated simplifications

  • Models are hand-written. The extraction stage — deriving the checked model from code — is replaced by hand modeling; the guarantee attaches to the model, and fidelity to the code is the standing cost of the approach.
  • State spaces are tiny. A four-valued action, two senders, a saturating counter. At training throughput the honest phrasing is bounded checking, small models, aggressive caching — and the demonstration makes no scaling claim.
  • The suite is given, and it is fixed. Property synthesis is the scarce resource; a fixed suite under adversarial pressure is exactly the setting in which specifications rot.
  • The policy is absent. The verification half runs for real; whether property-based rewards beat test-based rewards for a real generator is an empirical claim requiring the RL experiment.
  • Authorship, stated plainly. This repository and this page exist to exemplify the shape of a line of work and research. The code was written by Claude (Anthropic) under human direction, and it is deliberately unoptimized: no attention has been paid to state-of-the-art algorithms or data structures beyond what the demonstration needs. The verdicts, however, are not a matter of authorship — every number on this page is cross-checked against the engines as described below.

Engines

  • NuSMV — open source (LGPL v2.1), the reference symbolic model checker. Everything untimed on this page cross-checks against it; the repository’s CI runs the full matrix on a clean install. nusmv.fbk.eu
  • nuXmv — NuSMV’s successor from FBK, adding SMT-based algorithms and the timed mode used on rung 5. It is free for non-commercial research and educational use, closed source, and its license does not permit redistribution — the repository drives it as an external binary and never ships it. nuxmv.fbk.eu
  • The repository is MIT-licensed, self-contained, and runs on a clean NuSMV with stock Python. It rebuilds the verification loop on freely available engines in place of a production pipeline that is private.
provenance of every number on this page
  • Verdicts are computed live by an in-page explicit-state checker — an independent second implementation of the loop’s semantics — and verified on load against the repository’s committed expected matrix (35 instances), its committed engine traces (replayed step-for-step against the transition relation), and eight real NuSMV runs of the fairness-premise variants.
  • Counterexamples shown as “portal checker” are this page’s own shortest witnesses; counterexamples are never unique, so the engine’s committed traces sit in their own tab, unedited. The rung-5 timed trace is nuXmv’s, replayed.
  • Portal additions, beyond what the repository computes: vacuity is decided here (antecedent reachability — the repository states it in prose), the safety-only reading renormalizes the remaining weights, and the three named lassos in the fairness lab are built by the portal and re-validated against the transition relation on every load.
run it yourself
# 1. one candidate, all five properties, the counterexample in full
python3 src/verify.py models/timelock_bypass.smv

# 2. the loop: five candidates in, rewards and ranking out
python3 src/loop.py

# 3. the whole expected-verdict matrix, as CI runs it
python3 tests/check.py

Repository: daditalicus/verified-rewards-loop (public release pending at the time this page was generated). One binary — NuSMV — and stock Python 3.9+; no Python dependencies.