Issue #390: The default of --optimization, and whether its proofs belong in nikaia.proofs

Open and answer this issue on GitHub.

compiler, ready · opened 2026-10-03 · updated 2026-10-07 · 7 comments

Two questions ADR-271 §6 leaves open:

1. The default

2. The proof file

Comments

keywan-ghadami, 2026-10-04

State after the owner’s round (2026-10-04)

1. nikaia.proofs - decided: the walk’s proofs go into it. This does not wait for the solver’s search to become free. ADR-306 §6’s “when the solver’s search is free” is superseded.

2. The default - not decidable yet. The owner’s view: aggressive is dangerous today. A proved index is emitted through proven-index’s get_unchecked, so a wrong proof means undefined behaviour, where a wrong overflow proof only means a wrong value. The certificate checker (ADR-270 D5) covers the solver’s arithmetic, not whether the query the walk built says what the program does. The questions are now:

A decision basis is being prepared first:


Generated by Claude Code

keywan-ghadami, 2026-10-04

Budget, as the owner decided in another session: the prover’s effort is a step budget, never time. Only the expensive steps are counted. That makes counting nearly free, and in any case cheaper than reading the clock. nikaia-logic’s Budget already has this shape: cases, where each case is one elimination, and bounds, the bounds one elimination may hold. It is not counted per operation.

What remains open for #390:


Generated by Claude Code

keywan-ghadami, 2026-10-04

What makes aggressive dangerous (owner, 2026-10-04). A wrong proof that removes a run-time check turns a defect into a bomb. Where the program would have stopped cleanly, it reads out of bounds through get_unchecked (undefined behaviour) or computes a silently wrong value through wrapping_*. A proof used to find errors is not dangerous: a wrong refutation is a visible build error, and --refuted-claims warn bypasses it.

So the decision basis treats the prover’s two uses separately:

  1. Finding errors (refutation, ADR-269 D8): harmless if wrong, and it gains from more budget. It is a candidate for on by default.
  2. Removing checks (ADR-306): needs its own decision, and ways to defuse the bomb:
    • a test build that keeps every proved check as an assertion reporting the compiler’s proof was wrong, so wrong proofs surface in CI;
    • index and overflow set separately (undefined behaviour against a wrong value);
    • checking the translation from program to query (translation validation, or differential runs at both levels).
  3. Budget: counted in expensive steps only (decided). Open is how a build sets it.
  4. Opt-in or opt-out for each use.
  5. Measurements owed: compile time, and the sites proved and refuted per budget.

Generated by Claude Code

keywan-ghadami, 2026-10-04

Decision (owner, 2026-10-04)

Today’s problem: aggressive is one switch for two things. It means search harder and remove what was proved at once, and nothing lets a build set the budget. Checked in the code:

Two switches for two things

1. How hard the compiler proves, e.g. --proving=LEVEL / [build] proving.

2. What happens to a proved check: --optimization=remove-bounds-checks and remove-overflow-checks, now on/off.

A check nobody writes that is shown to fail is an error

Example: an index shown outside the list for a value the program reaches. The run-time check would only stop the program; it would not rescue it. So this is reported as a refused claim is, an error, with --refuted-claims warn as the bypass (ADR-269 D8).

To build

  1. The proving option: command line and [build], levels mapped to a Budget. Pass it to prove.rs and to the walk. Add it to the cache key and to the nikaia.proofs key.
  2. Run bounds.rs at every build, independent of --optimization, and record its answers in nikaia.proofs.
  3. Reduce remove-bounds-checks / remove-overflow-checks to on/off. basic either becomes a proving level or goes; decide when building, and say which. Removal reads the recorded proofs.
  4. A refuted implicit check is an error: a new code (or NK1202’s family), naming the value that fails, and honouring --refuted-claims warn.
  5. Update ADR-306 and ADR-269 D8, and Part III 13.3’s [build] table.
  6. Tests:
    • a provable index out of bounds is refused, and builds with warn;
    • an unknown index builds silently;
    • a higher proving level proves a site the default does not;
    • toggling removal re-proves nothing (no new nikaia.proofs entries);
    • removal off emits every check even where it is proved.
  7. Measure: compile time over the whole corpus with std per proving level, and the sites proved and refuted per level. These numbers pick the default proving level.

Generated by Claude Code

keywan-ghadami, 2026-10-04

Written into the records in 8fd9b3f (0.0.451):

Step 5 of To build is done.

Still open: proving’s level names, budgets and default, which wait for step 7’s measurements, and whether D3’s walk without a solver stays a level (ADR-306 §6). Part III gets the proving option once those are decided.


Generated by Claude Code

keywan-ghadami, 2026-10-04

Steps 2 and 3 of To build are built in 97b883c (0.0.460).

Still open:

  1. The proving option and its budget. Levels and default wait for the measurements.
  2. Recording the walk’s answers in nikaia.proofs.
  3. A refuted implicit check becomes an error, with a model checked by evaluation.
  4. Tests 1, 3 and 4 of step 6, which depend on the three items above.
  5. The measurements of step 7.

Generated by Claude Code

keywan-ghadami, 2026-10-07

Step 7: sites proved and refuted per budget (measured on 0.0.634)

Method

Results

cases (bounds = 4×) queries proved refuted unknown: budget (cases / bounds) unknown: no contradiction unknown: overflow
1 984 52 18 484 / 419 2 9
4 989 489 287 63 / 122 19 9
16 995 664 303 0 / 0 19 9
64 995 664 303 0 / 0 19 9
256 995 664 303 0 / 0 19 9
1024 (today’s default) 995 664 303 0 / 0 19 9
4096 995 664 303 0 / 0 19 9
16384 995 664 303 0 / 0 19 9

There are 472 distinct queries; the same query is asked from several places. At budgets 1 and 4 the walk asks fewer follow-up queries, because earlier ones end unknown.

What it says

For the decision

The measurements do not separate any levels above 16, so they cannot choose a default among them. There are two honest readings:

  1. No user-facing levels for now: keep Budget::default() and let nikaia.proofs carry the answers. A proving option then only matters once a program exists that reaches the limit.
  2. Levels as caps for pathological programs: a low and a high cap. The default stays far above 16, because there is no measured cost to it.

Not measured: queries from programs that do not exist yet. The corpus has 995 queries; its hardest is answered within 16 cases.

The 303 refuted queries are what step 4 (a refuted implicit check is an error) would read. Most are the walk asking whether a check is needed in a case it then rules out another way. They are not 303 defects. Step 4 still has to tell a refutation the program reaches from one under incomplete facts, as the decision says.

The status stays ready: steps 1 and 4 remain to build. A level design waits for a program that reaches the budget, or for the owner to choose reading 1 or 2.


Generated by Claude Code

Back to Open Issues