Skip to content

Engine: ask each question of the components of a position it touches - #193

Merged
deanberris merged 11 commits into
mainfrom
claude/pruned-cells
Nov 4, 2026
Merged

deanberris merged 11 commits into
mainfrom
claude/pruned-cells

Conversation

@deanberris

@deanberris deanberris commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Built on #192, now merged. It is option 1 of the plan, pruning inside defeat cells with proofs, and the foundation for composing arguments. When several solas are joined into one dispute, whole positions share nothing, so most questions touch only a fraction of what a party holds.

What changes

  • Components. A position falls into parts that share no claim. prepare_parts d for D writes out each party's clauses, and their components, found by union-find over the claims each clause mentions. It proves once for each party, checked by the kernel:
    • d.spec: the clauses are what conversion gives;
    • d.valid: the components partition them (Horn.Parts.valid). Each clause's claims lie in its component's mask, and no two masks meet.
  • Pruned questions. Hint.prune sel inner asks only the components sel selects (Horn.askPruned), after one check that the question shares no claim with the rest. A question that touches every component is asked of the whole position, since pruning would drop nothing and only add its checks. Asked of a whole position, a pruned hint answers nothing, so the ordinary check stays a plain case split.
  • Soundness. Horn.askPruned_sound proves the pruned answers are the whole position's, given its consistency, which every dispute node already proves (Dispute.consistent). Models of parts that share no claim combine. The proof is constructive and uses no axiom beyond propext and Quot.sound.
  • Question oracles. To accept that, a defeat cell's theorems are now stated over any oracle that answers truly (Horn.AskSound, Horn.defeatsAsk?). defeatsWith? and defeatsBy? are instances of it, so every existing theorem and table stands unchanged.
  • Proving a cell. Horn.defeats_iff_of_pruned proves a cell, and horn_certify finds the pruned hints (Horn.hintsPruned).
  • Ported: sola fide's defeat table (both halves).

Measured

scripts/bench-ab.sh against #192's head, five interleaved rounds. The pruned tables exist only on this side, so they are compared with the prepared ones in the same run:

Table Prepared Pruned Change p Peak memory
Sola fide, first half, 72 cells 5.95 s 5.38 s −9.5% 0.008 60 → 36 MiB
Septuagint, 36 cells 1.05 s 1.26 s +21% 0.15, not significant 12 → 4 MiB

So pruning pays on a large position and not on a small one, whose questions touch most of each party. Only sola fide's table is pruned; the smaller tables stay prepared. Ledger round 11 records both results, and docs/src/logic.md explains the method.

A regression CI caught, fixed

In the first version, a whole-position check recursed through a pruned hint's inner hint. That made the checker structural recursion over hints, which the kernel unfolds through brecOn on every question. CI's benchmark gate found the prepared Septuagint table 10% slower (p = 0.032).

The pruned hint now answers nothing when asked of a whole position. Seven interleaved rounds against #192's head then showed:

  • the prepared Septuagint table +1.0% (p = 0.90);
  • the certified one −3.5% (p = 0.26).

The first version had measured +4.7% and +8.2%.

Pins

The citation checks' pins in arguments/TestimonyArguments/Checks.lean move by exactly the new definitions: 2,338 → 2,370 and 1,633 → 1,634.

Gates

All run locally on this tree and all pass:

  • library: lake build, lake build TestimonyTools, lake lint, lake exe axiom-audit, scripts/kernel-replay.sh;
  • arguments: build, lint, axiom audit, kernel replay, and bibgen/argtex/statusgen/argdoc --check;
  • examples: build, lint, axiom audit, kernel replay, exampletex --check;
  • testimony_lint.py and mdbook build.

The last merge from main brings in the squashed #192 and changes no file of this branch. No generated page changed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q

A certified defeat cell converted both parties' positions to clauses, so a
party in a row and a column of cells was converted once for every cell.
`prepare_clauses` converts each party's position once, writes the clauses
out, and has the kernel check, once per party, that they are what the
conversion gives; every cell reads them. `Horn.defeatsWith?` decides a cell
from given clauses, `Horn.defeatsBy?` is it with the clauses converted, and
`Horn.defeats_iff_of_prepared` takes each party's proof. Every whole-dispute
defeat table is ported: eleven in the arguments, two in the grits example,
and the library's fixture.

Five interleaved rounds, certified against prepared: the Septuagint's table
1.52 s to 0.93 s (-39%, p = 0.008), half of sola fide's 6.31 s to 5.53 s
(-12%, p = 0.016); preparing sola fide's parties costs 0.35 s, once. Ledger
round 10, with the measurement that set component pruning after a faster
checker; the citation checks' pins move by the new definitions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q
# Conflicts:
#	arguments/TestimonyArguments/Checks.lean
#	docs/src/performance.md
… off the pages

`Horn.defeatChecks` and `Horn.defeatsWith?` no longer take the attacking
package, which a cell with given clauses never reads; `horn_certify` finds a
prepared cell's hints from the clauses themselves (`Horn.hintsWith`). The
argument pages skip a declaration with no place in the source: it was
generated by a command, `prepare_clauses`, as machinery for the kernel, not
written for a reader, and every generated page stays as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q
# Conflicts:
#	Testimony/Logic/Certificate.lean
#	arguments/TestimonyArguments/Checks.lean
#	arguments/bench/Engine.lean
#	docs/src/performance.md
A position falls into components that share no claim. `prepare_parts` writes
out each party's clauses with their components, found by union-find over the
claims each clause mentions, and has the kernel check once for each party
that they partition the clauses (`Horn.Parts.valid`). A pruned question
(`Hint.prune`) is asked of the components it touches only (`Horn.askPruned`),
after one check that it shares no claim with the rest; a question touching
them all is asked of the whole position.

`Horn.askPruned_sound` proves the answers the whole position's, given its
consistency, which every dispute node proves: models of parts that share no
claim combine. To take it, a defeat cell's theorems are now stated over any
question oracle that answers truly (`Horn.AskSound`, `Horn.defeatsAsk?`);
`defeatsWith?` and `defeatsBy?` are instances, and every existing theorem
stands. `Horn.defeats_iff_of_pruned` proves a cell; `horn_certify` finds the
pruned hints (`Horn.hintsPruned`).

Five interleaved rounds, prepared against pruned: half of sola fide's table
5.95 s to 5.38 s (-9.5%, p = 0.008), 60 to 36 MiB; the Septuagint's 1.05 s
to 1.26 s (+21%, p = 0.15, not significant), 12 to 4 MiB. Sola fide's table
is asked by components; the smaller tables stay prepared. Ledger round 11;
the citation pins move by the new definitions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q
# Conflicts:
#	CLAUDE.md
#	arguments/bench/Engine.lean
#	docs/src/performance.md
@deanberris
deanberris marked this pull request as ready for review November 3, 2026 15:37
# Conflicts:
#	CLAUDE.md
#	arguments/TestimonyArguments/Checks.lean
#	arguments/bench/Engine.lean
`clausesSatisfiableBy?` recursed through a pruned hint's inner one, which made
it structural recursion over hints: the kernel unfolded it through `brecOn` on
every question, pruned or not, and CI's benchmark gate found the prepared
Septuagint table 10% slower (p = 0.032). A pruned hint is only meaningful to
`askPruned`, which interprets it itself; asked of a whole position it now
answers `none`, which proves nothing, and the check is a plain case split.

Seven interleaved rounds against #192's head: the prepared Septuagint table
+1.0% (p = 0.90) and the certified one -3.5% (p = 0.26), where the first
version measured +4.7% and +8.2%. Recorded in ledger round 11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BC49XsbtehCbmJMavF1K5Q
@deanberris
deanberris changed the base branch from claude/prepared-clauses to main November 4, 2026 10:14
# Conflicts:
#	CLAUDE.md
#	Testimony/Logic/Certificate.lean
#	Testimony/Logic/Horn.lean
#	arguments/TestimonyArguments/Checks.lean
#	arguments/TestimonyArguments/SolaFide/Dispute.lean
#	arguments/bench/Engine.lean
#	docs/src/logic.md
#	docs/src/performance.md
@deanberris
deanberris merged commit ffee816 into main Nov 4, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants