mirror of
https://github.com/saymrwulf/dalek-ed25519-verified.git
synced 2026-09-03 20:13:48 +00:00
Phase 2b asks the kernel whether any AXIOM is declared under Proofs/. Phase 3
pins the cones of the named certificates. Between them sat every other
declaration in the corpus — around three thousand of them — and a helper lemma
quietly acquiring a hash oracle in its cone moved nothing either phase looked
at.
Phase 2c closes that. Ported from ltl-accumulator-verified, where a nine-attack
self-test proved a source-regex enumerator evadable by attributed, private,
indented and `instance` declarations and by a nested-namespace basename
collision. Reading the compiled environment sees what the kernel saw; no name
shape hides. Every constant contributes module, name, kind and full axiom cone,
and the observed set must equal inventory-allowlist.txt exactly in BOTH
directions, with a count trailer so a truncated run cannot pass as an empty
diff.
FOUR THINGS THIS BUILD GOT WRONG, each caught by a check rather than by review:
- The number of inventory drivers is a per-repo FACT, not an assumption.
dalek and anza cannot import their corpus as one environment (Proofs.Basic
and Proofs.ConstSpecs both declare CurveFieldProofs.zero_spec); risc0 and
betrusted have no Proofs.Basic at all. Determined by compiling a probe.
check.sh now DISCOVERS its drivers from the filesystem instead of naming
two, and the generator refuses to split out a module the repo lacks.
- The split let one real declaration hide behind another's entry. Keyed on
name alone, the two zero_specs produced byte-identical records, so 3022
declarations were covered by 3021 allowlist entries. Caught by the count
trailer. Every record now carries its originating module.
- The gate's success line said "single sanctioned axiom", inherited from the
accumulator's policy. This corpus permits NONE. A success message
describing a different rule is how an assertion stops meaning anything.
- selftest-axgate.sh lifted Phase 2b with a range ending at "Phase 3", so
inserting Phase 2c between them made it swallow the new phase and die on
variables only check.sh defines — surfacing as the BASELINE case failing,
a self-test blaming a gate for its own extraction bug. Both self-tests now
stop at the next phase marker whatever it is called, and refuse to run if
they capture more than one phase. The guard is the fix; the range was the
symptom.
WHAT THIS IS NOT, recorded in TRUSTED-BASE.md at the same length as the claim:
- No independent cone walker. The accumulator cross-checks collectAxioms
against a hand-written walker. Ported here it was wrong in BOTH directions
on mathlib's inductive shapes: EdPoint gave [] against the kernel's three
axioms, and once extended, ProjPoint gave three against the kernel's none.
Two implementations disagreeing both ways are a second wrong answer, not a
check. These cones rest on collectAxioms alone.
- Thirteen Proofs/Scalar* modules are inventoried by nothing — the
second-button seam, still open. Phase 2c names every uncovered module on
every run so the omission is visible rather than inferred.
selftest-inventory.sh exercises the shipping gate with six cases, each
asserting a specific diagnostic, including the one that matters: a cone
widened by one oracle while name, module and kind stay put. Negative-tested by
disabling the gate's diff, which turns two cases red including one for the
wrong reason, correctly reported as such.
Verified green: 20 runs across the four repositories (four buttons, four
harness, four inventory, four axgate, four binding self-tests), zero red. The
four check-scalar.sh greens from the preceding sweep stand: that script neither
reads the pin file nor changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
5.8 KiB
Text
110 lines
5.8 KiB
Text
/- ──────────────────────────────────────────────────────────────────────────
|
|
Proofs/InventoryCore.lean — shared machinery for the declaration inventory.
|
|
|
|
PORTED, NOT REINVENTED. This is the ltl-accumulator-verified design
|
|
(Proofs/Inventory.lean there), which survived a nine-attack self-test that
|
|
defeated a source-regex enumerator: attributed, private, indented and
|
|
`instance` declarations were all invisible to the regex, and a nested
|
|
`namespace Hidden theorem MTH` collided with the basename of an audited
|
|
declaration. Reading the compiled ENVIRONMENT sees exactly what the kernel
|
|
saw, and there is no name shape that can hide from it.
|
|
|
|
WHY TWO DRIVERS IMPORT THIS. Unlike the accumulator, this corpus cannot be
|
|
imported as one environment: `Proofs.Basic` and `Proofs.ConstSpecs` both
|
|
declare `CurveFieldProofs.zero_spec`. That is deliberate and documented —
|
|
Basic.lean is compiled by check.sh but imported by nothing, so the reuse is
|
|
harmless — but it makes a single whole-corpus import impossible. The corpus
|
|
therefore splits into the main chain and Basic, one driver each, and
|
|
check.sh concatenates their output before gating. The split is asserted in
|
|
check.sh against the compile manifest, so a module cannot fall between the
|
|
two drivers unnoticed.
|
|
|
|
The corpus module list lives in each DRIVER, not here, and is checked
|
|
textually against check.sh's manifest in both directions. A listed module
|
|
that is not actually imported is an elaboration error, not a silent skip.
|
|
────────────────────────────────────────────────────────────────────────── -/
|
|
import Lean
|
|
|
|
open Lean
|
|
|
|
namespace Ed25519Inventory
|
|
|
|
def kindOf : ConstantInfo → String
|
|
| .axiomInfo _ => "axiom"
|
|
| .defnInfo _ => "def"
|
|
| .thmInfo _ => "theorem"
|
|
| .opaqueInfo _ => "opaque"
|
|
| .quotInfo _ => "quot"
|
|
| .inductInfo _ => "inductive"
|
|
| .ctorInfo _ => "ctor"
|
|
| .recInfo _ => "recursor"
|
|
|
|
/-- Axiom cone of `n`, from the kernel's own collector — the same machinery
|
|
`#print axioms` uses.
|
|
|
|
NO INDEPENDENT SECOND WALKER HERE, and that is a deliberate REDUCTION in
|
|
strength against the ltl-accumulator design this is ported from. There, a
|
|
hand-written closure walker runs alongside `collectAxioms` and every
|
|
constant must get the same answer from both, so the two implementations
|
|
check each other. Porting that walker to this corpus was tried on
|
|
2026-07-29 and abandoned on evidence:
|
|
|
|
· without traversing inductive families it UNDER-approximated —
|
|
`CurveFieldProofs.EdPoint`: walker [] vs kernel [Classical.choice,
|
|
Quot.sound, propext];
|
|
· adding constructors, recursor rules and `all` groups made it
|
|
OVER-approximate — `CurveFieldProofs.ProjPoint`: walker
|
|
[Classical.choice, Quot.sound, propext] vs kernel [].
|
|
|
|
Disagreeing in BOTH directions means the second implementation is not an
|
|
independent check, it is a second wrong answer. Matching the kernel's
|
|
traversal exactly over mathlib's inductive shapes is a Lean-internals
|
|
project, not a gate, and shipping a walker that is wrong in two directions
|
|
would be worse than shipping none: it would fail builds for reasons that
|
|
are the checker's fault and teach everyone to ignore it.
|
|
|
|
CONSEQUENCE, stated so nobody assumes otherwise: on this corpus the cone
|
|
figures rest on `collectAxioms` alone. The accumulator's corpus is
|
|
mathlib-free, its walker agrees there, and it KEEPS the cross-check. This
|
|
is recorded in TRUSTED-BASE.md. -/
|
|
def axiomCone (n : Name) : MetaM (Array Name) := do
|
|
let cone ← collectAxioms n
|
|
return cone.qsort (fun a b => a.toString < b.toString)
|
|
|
|
/-- Emit `INV|name|kind|cone` for every constant originating in `corpus`.
|
|
EVERY constant is emitted — fully qualified, NO filtering. Compiler-
|
|
generated auxiliaries (equation lemmas, match/eq/induct helpers, private
|
|
manglings) are emitted too and pinned in the allowlist, so anything new,
|
|
renamed, removed, or with a changed cone shows up as a diff. -/
|
|
def emitInventory (corpus : Array Name) : MetaM Unit := do
|
|
let env ← getEnv
|
|
let mut idxs : Array Nat := #[]
|
|
for m in corpus do
|
|
match env.getModuleIdx? m with
|
|
| some i => idxs := idxs.push i
|
|
| none => throwError "INVENTORY ERROR: corpus module {m} is not imported"
|
|
let mut lines : Array String := #[]
|
|
for (n, ci) in env.constants.toList do
|
|
if let some i := env.getModuleIdxFor? n then
|
|
if idxs.contains i then
|
|
let cone ← axiomCone n
|
|
let coneStr := ",".intercalate (cone.toList.map (·.toString))
|
|
-- The ORIGINATING MODULE is part of the record, unlike the accumulator's
|
|
-- format. It has to be: this corpus contains two distinct declarations
|
|
-- both named `CurveFieldProofs.zero_spec` (Proofs.Basic and
|
|
-- Proofs.ConstSpecs), inventoried by different drivers. Keyed on name
|
|
-- alone their records were byte-identical, so the merged allowlist held
|
|
-- 3021 entries for 3022 declarations and one real declaration was
|
|
-- covered by an entry describing a different one. The count trailer
|
|
-- caught it; the module field is what fixes it.
|
|
let mdl := env.header.moduleNames[i]!
|
|
lines := lines.push s!"INV|{mdl}|{n}|{kindOf ci}|{coneStr}"
|
|
let sorted := lines.qsort (· < ·)
|
|
for l in sorted do
|
|
IO.println l
|
|
-- Output-integrity trailer: a truncated or crashed run must never pass as an
|
|
-- empty diff. inventory_gate.sh compares this against the lines it actually
|
|
-- received, in both directions.
|
|
IO.println s!"INV-COUNT|{sorted.size}"
|
|
|
|
end Ed25519Inventory
|