Open and answer this issue on GitHub.
ready · opened 2026-10-17 · updated 2026-10-17 · 2 comments
The owner’s second example program:
fn main(a: int) {
assert(a > 0)
assert(a <= 10)
let mut i = 0
let mut total = 0
while i < a {
i = i + 1
total = total + 10
}
let b = 4 * example(a) + total
assert(b > 40)
assert(b < 181)
...
}
Today both claims about b are reported checked at run time: b is not a whole number the prover reads. The cause:
total is changed by the loop;The same program also showed a checker hole, [0..181] printed as a range. That is #638.
Checked before deciding:
nikaia.proofs;The owner decided this in four rounds. ADR-361 gives the reasons; Part III 13.2, 13.3 and 14.2 give the rule.
HORN is used at the edge. break, continue and return are each a way out of their own.assert in a loop is a claim by the flow and by the names it reads, not by its place in the body. Once proved, it is a fact the search may use.nikaia.proofs together with its origin. The origin never decides whether it is believed.[prove] proposers, for example an LLM, a learner from test runs, or another solver.
nikaia prove --propose, never in a build.protoc plugins and language servers work. HTTP and MCP were rejected; ADR-361 §3 gives the reasons.--asserts names the invariant and its origin, or the loop and the names it changes..nika files for a loop problem:
Add SMT-LIB HORN export next to logic_smtlib.nika. Tests: the problem of §1 written out, and its SMT-LIB text.
nikaia.proofs entry. Tests:
while or a for whose body changes names that an open claim reads into the loop problem. break, continue and return become exits. Ask only for a claim left open, as ADR-359 D2 does.start + k·n, with a fresh n >= 0. A counter with condition i < a and step +1 ends at i == a when a >= start, and otherwise stays at its start. Tests:
b > 40 and b < 181 proved;break where the end value must not be claimed;while x < 100 { x = x + 3 } gives x <= 102.--asserts names the invariant and its origin for a claim proved through a loop, and the loop and the names it changes for one left open. Keep the red/light-blue/green groups from 0.0.990.lower-std and the standalone files; the limit is at most +5 %. Check every new refutation by hand.[prove] proposers = ["cmd arg", …] in the manifest, nikaia prove --propose, and --propose=CMD for a single run. The exchange:
{"version": 1, "id": …, "problem": …, "source": …, "names": […]}. problem is SMT-LIB HORN, source is the loop’s source text, names are the names it reads. Close stdin after the last line.{"id": …, "candidates": […]}. Each candidate is an SMT-LIB term over the names.nikaia.proofs, with the proposer as their origin.Tests, with a stub proposer written in Nikaia and run with nikaia run:
version is refused with a message;nikaia build never starts a proposer;--locked reads the written entry.Steps 1-7 close the owner’s program. Step 8 is the LLM interface. Step 9 stays open after that.
Status after d3d27e2 (steps 1-2) and 8bfd1a0 (steps 3-6, while only). The owner’s program has assert(b > 40) and assert(b < 181) proved through the loop, with the invariant i >= 0 && total >= 0 && 10 * i == total && i < a + 1 found by closed forms.
Still missing for steps 1-7:
for loops. Only while is translated today (prover_loops.nika, loop_record). prover_for only forgets what its body changes.nikaia.proofs. The three queries are recorded as ordinary entries. A line KEY invariant origins;smt-term (an Entry::Invariant in proofs.rs, the invariant kind accepted by pb_read, with ProverState.invariants_noted filled in through_loops and handed over in prove.rs) was written and then taken out unbuilt. Rebuild it from that description and add a test with a project build.i == 10 is not proved: it needs parity, and Fourier-Motzkin tightening does not find it.benches/, examples/) with the new binary: 3 844.1 M instructions. No baseline was counted.lower-std was not counted.let mut now gives facts until its next assignment, so new NK1207s are possible. lower-std raised none, but the corpus has not been checked yet.NIKAIA_PROVER_COUNTS prints loop claims and loop queries. For the owner’s program it printed 64 / 192: the invariant is found again on every fresh ProverState, that is each prove() round and each check. It could be cached across rounds.docs/lowering-defects.md.Generated by Claude Code
State after 0.0.1002:
while (8bfd1a0, package.rs regenerated in 184e2fb) and for over a range (fbfeef9) read through the invariant. Verified on develop 02363f6: prove_loops, logic_horn, prove, assert, main_parameters pass; the owner’s program proves b > 40 and b < 181 “through the loop”, and nikaia run main.nika -- 5 prints Hello, Nikaia! 110 1 110.for over a list or map stays open by design (ADR-361 “not decided here”: quantified facts).let mut followed through every assignment outside loops is #642 (ADR-361 D9).Still open here:
nikaia.proofs (with the strategy that found it); today only the three queries are recorded.lower-std before (409af5d) and after; the new binary alone was 3 844.1 M on 26 files. The invariant is found again for every fresh ProverState (64 loop claims, 192 loop queries on the owner’s program): a cache may be needed for the 5 % limit.Generated by Claude Code