Repository navigation
Challenge mode: open statements, solutions, and comparator verification - #288
Conversation
Adds the other half of the pool: `Challenge/` holds open *statements* — theorems written in Mathlib vocabulary and left as `sorry` — and `Solution/` holds answers to them. Both libraries ship empty; the first challenges land in a follow-up PR. `sorry` is allowed only in `Challenge/`, and only as the whole proof body of a declaration registered in `Challenge/challenges.yml`. The gates keep the board honest: every other declaration in a statement file is closed (checked with `#print axioms`, so scaffolding cannot hide behind an open statement), registered statements really are open, imports are Mathlib-only, and the generated card — which carries the informal statement the Lean is judged against — matches the registry. A solution restates its challenge's statement under the same name and proves it, and may not import the challenge module: `leanprover/comparator` exports the two environments separately and checks that the statements agree, which is what makes the verdict independent of the statement file. `challenge-verify.yml` runs comparator over every solved challenge with landrun, lean4export, and comparator pinned to exact commits (the pins leanprover/lean-eval uses). PR review now recognizes four kinds. A challenge is judged on significance, faithfulness of the Lean to the prose, source fidelity, vacuity/gameability, and the estimated size of a solution (`CHALLENGE_REVIEW_RULES.md`). A solution gets a short review (`SOLUTION_REVIEW_RULES.md`) — correctness belongs to the kernel, not the model — and no model call at all when it touches nothing but the answer and its registry entry and the challenge left no definition hole. The PR guard treats challenge statements and solutions as content, and rejects a PR that edits a statement and a solution together: the merged statement is the text comparator judges against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`grep` exits 1 when it matches nothing, and under `set -o pipefail` the unguarded first stage killed the script on the quietest possible build: an empty board emits no warnings, so there was nothing for the warning scan to find. Guard both stages — no warnings at all and no warnings beyond the sorry notices are both the clean case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Greptile SummaryThis PR introduces challenge mode — the "other half of the pool" — shipping two new Lean libraries (
Confidence Score: 5/5Safe to merge — all correctness-critical gates (axiom audit, sorry policy, import restrictions, comparator verification) are implemented correctly and the previously identified open-declaration proof-without-sorry gap has been closed. The axiom-audit fix from the prior review thread is in place: Files Needing Attention: No files require special attention.
|
| Filename | Overview |
|---|---|
| python/lean_pool/challenge.py | New module: challenge registry schema, card generation, and comparator JSON configuration. Well-structured with clear separation between domain logic and enforcement gates. |
| python/lean_pool/quality.py | Extends quality gates to Challenge and Solution libraries: sorry-whole-body-only textual check, Mathlib-only import enforcement, open-declaration axiom audit with correct sorryAx presence/absence checking, and two-pass environment audit to avoid name collisions between Challenge and Solution. |
| python/lean_pool/review.py | Adds challenge/solution PR kinds to the classifier and review pipeline; introduces solution_needs_llm_review skip logic and four-mode rendering infrastructure. |
| .github/workflows/challenge-verify.yml | New workflow: builds comparator, lean4export, and landrun at pinned commits, then replays every solved challenge through the Lean kernel. Tools are cached by commit hash + toolchain. |
| .github/workflows/content-pr-guard.yml | Extends PR guard to cover Challenge/Solution content files and adds the mixed-statement-and-solution rejection; the unreachable success-message branch (line 157) noted in a prior outside-diff comment remains. |
| scripts/challenge/verify-solution.sh | New script: drives comparator locally and in CI with landrun sandboxing. Minor naming convention violation: shell variable config should be configuration. |
| python/lean_pool/stats.py | Adds open_challenges counter to Stats; render_stats omits the challenge line when the board is empty. |
Sequence Diagram
sequenceDiagram
participant PR as Solution PR
participant Guard as content-pr-guard.yml
participant LeanCI as lean_action_ci.yml
participant Verify as challenge-verify.yml
participant Review as LLM review
PR->>Guard: push / PR opened
Guard->>Guard: reject if Challenge statement modified + Solution added
Guard->>Guard: reject if content + non-content mixed
PR->>LeanCI: quality checks
LeanCI->>LeanCI: sorry/axiom audit (Challenge library)
LeanCI->>LeanCI: sorry forbidden in Solution
LeanCI->>LeanCI: solution must not import Challenge
PR->>Verify: challenge-verify.yml
Verify->>Verify: lake build Challenge
Verify->>Verify: lean4export (challenge env)
Verify->>Verify: comparator: statements match?
Verify->>Verify: kernel replay: no sorryAx?
PR->>Review: classify_pr
Review->>Review: solution_needs_llm_review?
alt nothing beyond answer + registry
Review-->>PR: skip comment (no model call)
else definition hole or extra files
Review->>Review: SOLUTION_REVIEW_RULES.md
Review-->>PR: LLM comment
end
Reviews (2): Last reviewed commit: "Close the axiom-gate hole a challenge pr..." | Re-trigger Greptile
🤖 LLM review (
|
| Aspect | Value |
|---|---|
| Fit | 🟡 borderline |
| Level | undergraduate |
| Branch | repository infrastructure |
| Mode | mixed |
| Obscure problem | no |
| Code quality | 3 / 5 |
The contribution is a challenge-board and verified-solution workflow, not a formalized mathematical theorem.
Findings (3)
- definition-hole-skip —
python/lean_pool/review.py:522
solution_needs_llm_reviewreadsChallenge/challenges.ymlfrom the base checkout, but the review workflow does not apply the PR diff. A normal solution PR flips the registry to addsolution.moduleonly in the PR, so this function will usually see no solution module and will skip the model even when the base challenge hasdefinitions:. Either inspect the PR-head registry/diff, or make definition-hole detection use the base challenge entry independently of the PR's newsolutionblock. - audit-scope —
python/lean_pool/quality.py:786
_run_option_auditalways passes_open_declarations(root)into_parse_option_audit_output, including for the separateSolutionaudit. Since solutions deliberately declare the same fully qualified theorem names as challenges, this suppresses puresorryAxenvironment-audit findings for solution declarations with registered challenge names. Pass the tolerated open-declaration set only for the Challenge audit and an empty set for Solution. - docs-build —
.github/workflows/docs.yml:164
The docs workflow now runslake build LeanPool:docs Challenge:docsinsidedocbuild, but the diff does not add aChallengelibrary to the docbuild Lake workspace. Unless that target already exists on base, this makes the docs job fail; add the corresponding docbuild library configuration or do not requestChallenge:docsyet.
Tokens: 55,839 in / 6,229 out · Tier: flex · Cost: $0.2330
Automated review against .github/REVIEW_RULES.md. Disagree? Reply on the PR; rules can be updated in a PR of their own.
Review found that the "registered statements must stay open" gate only rejected axioms *outside* the allowlist. A challenge proved in place with an ordinary classical proof depends on `Classical.choice` and nothing worse, so the extra-axiom check had nothing to say — and because `#print axioms` did report the declaration, the post-loop "never reported" check did not fire either. Only a proof using no axioms at all was caught. Demand `sorryAx` outright for a registered open declaration, with one message for both paths and a regression test for the classical one. Also renames `comparator_config` and its locals to `configuration`: CONTRIBUTING asks for full words, not abbreviations. The CLI subcommand stays `config` — it is a user-visible interface name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The other half of the pool:
Challenge/holds open statements — theorems written in Mathlib vocabulary and left assorry— andSolution/holds answers to them. Both libraries ship empty here; the first challenges land in the stacked PR that follows, so the machinery gets exercised by a real content PR.The board
sorryis allowed only inChallenge/, and only as the whole proof body of a declaration registered inChallenge/challenges.yml. The gates keep the board honest:#print axioms, so scaffolding can't hide behind an open statement;Mathlib.*, so reading a challenge never means auditing pool code;set_option, axioms, the option-backdoor audit and size caps apply exactly as they do to pooled projects.Solutions
A solution restates its challenge's statement under the same name and proves it, and may not import the challenge module:
leanprover/comparatorexports the two environments separately and checks that the statements agree, which is what makes the verdict independent of the statement file.challenge-verify.ymlruns comparator over every solved challenge, withlandrun,lean4export(pinned to the tag matching our toolchain) and comparator pinned to exact commits — the same pinsleanprover/lean-evaluses. Locally,make comparator+make verify-challenge C=<slug>.Verified end to end on a throwaway fixture before this PR was split out: a correct solution gives
Your solution is okay!; a weakened statement givesChallenge and solution theorem statement do not match; a solution still usingsorrygivesIllegal axiom detected: 'sorryAx'.Review
classify_prnow recognizes four kinds — project, refactor, challenge, solution:A solution PR gets no model call at all when it touches nothing but the answer, the generated index, and its registry entry, and the challenge left no definition hole: everything about it is machine-checked, so the workflow posts a short note saying so.
The PR guard treats statements and solutions as content, and rejects a PR that edits a statement and a solution together — that is the one way to fake a solution past comparator.
Challenge.leanandSolution.leanare neutral: generated indexes with no mathematics in them.Checks run locally
lake exe mk_all --check,lake build Challenge Solution,runLinter/lint-styleon both new libraries, the quality checker (static + the Lean-backed axiom audits), 238 pytest tests, ruff, actionlint, shellcheck.🤖 Generated with Claude Code