Idea
Code review from facts about the compiled program
Ask concrete questions about a Bosatsu program: which inputs reach a result, which fields go unread, and what changed after an edit? We built tools that extract facts from the compiled program and test a reviewer’s claims against them.
Code & run Change a price calculation
Each result distinguishes a static fact from a bounded observation of seeded runs. Start with the pricing example below, or run the checks yourself. The tools analyze Bosatsu programs.
Worked example: four facts from a pricing module
Here is a small pricing module (it ships in the repository at
demos/pricing,
with the commands to reproduce these findings below). It contains deliberate
input-ignoring examples and a function that stays constant in the sampled runs.
struct Order(id: Int, qty: Int, discount_code: Int)
def quote(o: Order) -> Int:
_ = o # the order is thrown away...
42 # ...and the "computed" price is hard-coded
def shipping_fee(subtotal: Int, region: Int) -> Int:
match cmp_Int(region, 0):
case GT: add(subtotal, 5)
case _: add(subtotal, 5) # both branches identical: region decides nothing
def surge_bonus(x: Int) -> Int:
match cmp_Int(x, 1000):
case GT: x # looks input-dependent...
case _: 7 # ...the sampled inputs all take this branch
One command, yichus explore --agenda --limit 500 demos/pricing/*.bosatsu
(the “agenda” is the tool’s list of noteworthy facts,
one card each), produces the excerpts below. The seeded observations use
type-derived inputs for this pricing module; they do not establish behavior
for every input. Read the complete captured agenda.
quote:
“takes arguments, none of whose data reaches the produced value; every
value the result can take originates in literals”
The price is fake. The tool offers no opinion; the fact is checkable.
Order.discount_code:
“no compiled extraction of the field exists in the supplied Bosatsu IR (reads: []); no declared API wire consumer was established; other engine or host consumers are outside this evidence; extraction absence alone does not justify removing the field”
The supplied Bosatsu never extracts the discount code. Check engine and host consumers before removing a field.
shipping_fee(region):
“the parameter only selects among branches; its data never reaches the
produced value”
Region picks a branch, but both branches compute the same thing.
surge_bonus:
“across 8 seeded runs (seed 6321458022832390279),
Demo/Pricing/surge_bonus always produced 7; bounded observation, not
proof; parameter(s) x statically reach the produced value and no
literal-result fact applies — the constancy is not statically
explained”
This static analysis does not establish the constancy, so seeded runs provide additional evidence.
Every fact is located and machine-checkable; the run-based ones carry the seed their inputs were generated from. None of them says “bug”. You decide which of these count as bugs. The tool provides a reproducible analysis result; its correctness still depends on the analyzer and its modeled semantics.
Agenda taxonomy: thirteen fact card types
There are thirteen kinds of fact. Each one comes with real code, or with
a direct route to the checker that emits it.
The taxonomy separates structural findings from
seeded observations and declared-flow conformance. The first eight are computed from compiled structure. The next three summarize seeded, type-derived runs; absence or constancy observations are labeled “bounded observation, not proof.” The final two classify each mutation named by a
FlowSpec as a structural instance or a located divergence.
Run the taxonomy with the
explore --agenda command below.
The static and flow card emitters are in
ExplorerAgenda.scala;
seeded observation cards come from
ExecutionEvidence.scala.
Dataflow semantics: “reaches”, “influences”, and negative guarantees
The static facts are computed on the compiled program’s
dataflow structure: a value reaches a result if there is a
chain of data edges from one to the other, and influences
adds control edges (picking a branch counts). Structure
over-approximates real influence, within the analyzer’s modeled operations. Negative results must retain their scope: “never reaches” excludes a data path to the result, while “influences nothing” also excludes a control path. A value can select a branch without contributing data to its result. A bare “reaches” only says a path
exists on paper. So constant-despite-reach gets its own
fact type: the structure says the input could matter, the observed
runs say it didn’t, and the fact records that disagreement.
dead-field a field with no attributed extraction in the supplied Bosatsu IR
struct Order(id: Int, qty: Int, discount_code: Int) # id and qty are read by accessors; discount_code is read by nothing
This raw fact does not justify removing the field. The overview and agenda also show declared API request/result consumers from the engine’s checked schema resolver. A response can be serialized without any Bosatsu field extraction. Empty consumer evidence does not exclude storage, archive or other host use.
No compiled field read was found in the analyzed program. Check the field’s intended use and any external consumers before removing it.
unused-parameter a parameter that influences nothing in its function
def quote(o: Order) -> Int: _ = o 42
The function does not use this parameter. Whether that violates the intended behavior depends on its specification.
guard-only-parameter a parameter that picks branches but never reaches the result
def shipping_fee(subtotal: Int, region: Int) -> Int:
match cmp_Int(region, 0):
case GT: add(subtotal, 5)
case _: add(subtotal, 5)
This is legitimate for a dispatch helper. It is suspicious when the spec says the value should flow into the answer.
discarded-argument a call that passes a value into a parameter the callee throws away
def summary(o: Order) -> Int: quote(o) # quote discards o entirely
The caller passes data the callee never uses, and the fact points at the exact call.
literal-result a function whose every possible result is built from constants
def quote(o: Order) -> Int: _ = o 42
This can expose a placeholder computation, including one routed through helpers. Predicates, lookup tables, and intentional constants can produce the same finding.
detached-read a state read whose value influences nothing
refresh: IO[Int] = ( c <- cell.flat_map() _ <- c.read().flat_map() # read the cell... pure(7) # ...ignore it, return 7 )
A state cell is a mutable variable managed by the runtime. This is code that looks like it consults state and doesn’t.
blind-write a state write with no read of that cell in the same code
reset: IO[Unit] = ( c <- cell.flat_map() c.write(9) # overwrites whatever was there, unconditionally )
This is fine for a reset. It is a data-loss hazard in a read-modify-write that forgot the read.
package-reach one package can transitively reach another, with the import path
package Demo/Report from Demo/Pricing import Order, quote
These facts power layering rules like “the domain layer must never reach the notification layer”. When a rule is broken, you get the exact import chain that breaks it.
run-evidence what happened when the code ran on seeded inputs
def quote(o: Order) -> Int: _ = o 42
The runner selects pure, monomorphic bindings whose input types it can generate, and reports skipped bindings. Run-based facts carry their input seed. A varying result supplies a concrete input-dependence witness: “runs 0 and 1 (seed 6321458022832390279) of Demo/Pricing/line_total produced 28 and -39 — the result depends on its inputs.”
unexecuted-reference a static reference whose target value was not forced in the sampled runs
def pick_quote(x: Int) -> Int:
match cmp_Int(x, 999):
case GT: fallback_quote(x) # sampled inputs may miss this branch
case _: x
The runner records global values when evaluation forces them. A static referrer may have been evaluated only to a function value: registering a handler does not run its body. This card compares that value-forcing evidence with the typed reference graph. It does not report function-call coverage or prove dead code.
The stable card name and legacy executedStaticReferrers field remain available; referenceBasis: global-value-forcing states what they measure. Other inputs or an explicit handler invocation may force the missing values.
constant-despite-reach inputs reach the result on paper; observed constant in practice
def surge_bonus(x: Int) -> Int:
match cmp_Int(x, 1000):
case GT: x
case _: 7
The static story and the observed story disagree, which is the kind of divergence worth a human look.
flow-instance a declared mutation structurally matches its flow factory
The card names the flow, factory, mutation location, and every
extracted part with its verified argument wiring. For example,
Demo/Stock/restock is checked as an instance of
retry-stock; the complete report and parts manifest are
explained in the flow tutorial.
flow-divergence a declared mutation differs from its flow factory at a located structural path
The card records the first mismatch instead of treating all
type-compatible mutations as conforming. The tutorial’s
incorrectly wired error example
shows the checker locating current where the flow requires
deficit. Flow cards cover declared mutations only;
an undeclared look-alike is outside this classification.
wire-result-field a field no Bosatsu code reads that an API route sends in its result
struct Quote(total: Int, currency: String) def price(o: Order) -> IO[Quote]: ... # served at /price; nothing in Bosatsu reads currency
The engine reads the field when it writes the response, so it is
not dead. It gets its own card type, drained after every other card,
so the fields nothing reads are not buried under a service’s
response shapes. A request field the handler ignores stays a
dead-field card: the client sends it and nothing uses it.
Claim verification: supported, refuted, and not-provable verdicts
A review (yours or an AI’s) is submitted as claims, and the verifier answers from the facts. Three claims about the module above:
{"claims": [
{"kind": "literal-result", "package": "Demo/Pricing", "binding": "quote"},
{"kind": "no-detached-read", "package": "Demo/Report"},
{"kind": "dead-field", "package": "Demo/Pricing", "struct": "Order", "field": "qty"}
]}
Save the JSON above as claims.json. Running
yichus verify-claims claims.json demos/pricing/*.bosatsu produces these verdict excerpts
(claims,
complete output):
The exit code is 1 because one claim was refuted, so a wrong review
fails the same way a failing test does. Look at the second verdict:
the absence of detached reads in the stated scope is backed by an enumerated count
of the read sites examined. Claim vocabulary is intentionally narrower
than the card taxonomy. There is no generic
no-<kind> constructor. Selected static facts have direct claims; five
card families have general absence claims, package reach has its own
negative claim, and flow conformance has flow-instance plus
no-flow-divergence. Use a drained claim when a
universal statement depends on processing every card in another family.
The full vocabulary is in the
agent tooling guide.
Fact diff: what changed between two versions
yichus fact-diff compares extracted facts for two source sets,
reporting introduced and removed findings and downstream consumers. Replacing a
computation with a literal can introduce a literal-result finding and leave callers
passing arguments that no longer affect the result.
For a reproducible before/after example, use the fixtures in FactDiffCommandTest.scala. The command recipe below accepts your own two source versions.
Benchmark: fact-tool review versus source review on planted defects
The experiment generates Bosatsu codebases with planted defects
(“needles”). Each round is a matched pair: one AI reviewer gets the fact
tooling, one reads source, and both are scored against a hidden key. Every round’s
raw record is committed under eval/haystack/results/ and
rendered on the scoreboard,
which defines the columns, the contract-accuracy gate, and the rounds the
tools lost. The project builds the tools, generates the challenges, and
scores the runs. Each cell is one run. Model labels are harness tiers, not
exact model IDs. Round 008’s nominal 60k caps failed to bind.
Recorded outcomes on that scoreboard:
- On a 46,922-line codebase with 26 planted defects (round 008), nominal 60k caps failed to bind and both reviewers spent about 100k tokens. The fact-using AI reviewer found 25 of 26; the source-reading reviewer found 23 of 26. Its misses were the defects whose evidence was spread across files it could not afford to open.
- On a 15,417-line round (005b), both reviewers found all 10 planted defects. The fact-using reviewer got there reading 0.4% of the source and spending 23% fewer tokens (142.0k vs 184.7k).
- The rounds vary in program size, defect set, and tool version. They do not isolate the effect of codebase size on review cost.
- Wrong claims get caught. In round 003 a weak (haiku-tier) reviewer
filed six false positives. Two of them called a field
dead that the tool’s own reads list showed being read
(
CrewShift.crew_idatcrews.bosatsu:149andFuelOrder.flight_codeatfueling.bosatsu:110). The other four were a different shape. It declared the layering clean when the boundaries facts showed an inversion. It reported the declared checker predicates as fabricated computations. It said a parameter never influenced a result when its own text said the opposite. It filed seven “fabrications” that were all label tables or predicates, and missed the one real fabrication. Round 004 made machine-checked claims mandatory, and every checkable claim the same tier filed came back supported (32 of 32, 0 refuted). Three false positives of a different shape survived. All three were universal negatives such as “handler state reads are never detached”, which no claim kind covers. The round recorded that as an open gap.
Build the CLI and run the pricing checks
Needs git, a JVM (17+), and
sbt;
sbt assembly writes yichus.jar under
target/. The pricing module above ships in the repo, so
this works end to end:
# one-time setup git clone https://github.com/snoble/yichus && cd yichus sbt assembly alias yichus="java -jar $(pwd)/target/scala-*/yichus.jar" yichus explore --agenda --limit 500 demos/pricing/*.bosatsu # up to 500 fact cards yichus explore --agenda --agenda-type dead-field \ demos/pricing/*.bosatsu # one fact type # save the three-claim JSON from the section above as claims.json: yichus verify-claims claims.json demos/pricing/*.bosatsu # fact-diff wants a "before": copy the module, then edit # old/pricing.bosatsu to give quote a real computation. # Compare that edited version with the shipped constant version: cp -r demos/pricing old yichus fact-diff --before old/pricing.bosatsu \ --after demos/pricing/pricing.bosatsu
There is also yichus spec, which checks declared Bosatsu system properties
with Yichus’s native bounded and structural methods and records results with counterexample
traces. Use --output verdict.json to save its legacy 2.2 JSON artifact. Add
--require-holds invariant-name when a named invariant must hold conclusively
for the command to succeed. An inconclusive result otherwise remains visible in the artifact;
TLC is not run by this command yet.
The check chooser compares the available tools. See the
spec-first
verification demo.
All the benchmark data, including the losses →
Explore the facts live in the playground →
Early UI fixtures and experiments live in the archive.