CallElimCorrect: lift _terminal to polymorphic {f : Bool}#1340
Merged
PROgram52bc merged 31 commits intoJun 8, 2026
Conversation
Forward-looking scaffolding for polymorphic-f generalization of callElimStatementCorrect. Build clean, sorry-free, axiom-free, but none of the new helpers have callsites yet — the consumer-side wiring requires extracting shared call-arm plumbing first (see Stage 1+ plan). Changes: - StatementSemantics.lean: Change A — collapsed two conditional Hupdate premises in EvalCommandContract.call_sem into a single unconditional UpdateStates premise. Failure path no longer pins σ' = σ. - WF.lean: added boolTyped clause to WFPrePostProp giving the bool totality of pre/post conditions when their free vars are defined. Zero migration cost (no live construction sites). - CoreTransformProps.lean: 3 polymorphic-f helpers (singleCmdToStmts_poly, singletonAssumeEval_poly, H_havocs_poly). - SubstProps.lean: 4 polymorphic-f helpers (H_check_block_zip_poly, H_assumes_zip_poly, singletonAssertFailEval, singletonAssertEval_poly) plus the substantive new walker H_asserts_zip_fail (L4 flag-flip from false to true via failing-pre witness) and EvalCallElim_glue_fail (composes L1-L6 with L4 flag-flip). - StatementSemanticsProps.lean: consumer update for Change A. These 7 polymorphic helpers + glue_fail form the failure-arm toolkit. They will be wired into callElimStatementCorrect_terminal_fail after Stages 1-5 extract shared plumbing from the existing _terminal proof.
Lift the per-index δ-eval bridge for mkOld old-variable fvars at the
post-havoc store σO out of callElimStatementCorrect_terminal's call arm
into a private theorem.
Same proof body as the inline; promoted to a private theorem with 11
explicit hypotheses (Hwf2_univ, Hinitout, HσAO_reads_outs, Hevalouts,
hCallArgsLhs, HoutAlign, HoldVars_sub_{outs,lhs,callLhs}, HoldVals,
HoldValsLen). The inline block in _terminal becomes a one-line call.
Net diff: +114/-61 in CallElimCorrect.lean. The signature/docstring
overhead is intentional — Stage 6 will share this helper between the
success and failure arms once polymorphic-f wiring lands.
Build clean (490 jobs). Sorry count unchanged at 18. No new axioms.
Lift the per-fvar bridge for createOldVarsSubst's codomain at the L6 intermediate stores σ_R1/σO out of callElimStatementCorrect_terminal's call arm into a private theorem. Same proof body as the inline; promoted to a private theorem with 6 hypotheses (Hwfvars, three length facts, σ_R1_read_olds, HoldEval_bridge). The inline block becomes a one-line call. δ_fvar_eq is re-derived from Hwfvars inside the helper rather than threaded as a parameter. Net diff: +82/-29 in CallElimCorrect.lean. Build clean (490 jobs). Sorry count unchanged at 18. No new axioms. HinputSubBridge (the larger sibling) is deferred — its ~25 dependencies include several let-bound abbreviations (σ_R1, σO_eq_σAO_off_outs, σR1_eq_σ_for_notTouched, etc.) that are tangled with the Hsubst construction. It will be folded into Stage 4 instead.
Lift the two class-(b) substitution-decomposition witnesses out of callElimStatementCorrect_terminal's call arm into private theorems. b1_var_witness_at_oldSubst: when a Map.find? lands in the createOldVarsSubst segment of oldSubst_L6, the codomain entry is a fresh-old createFvar gen and the witness var ∈ getVars w forces var = genOldIdents[i] for the same positional i. b2_var_witness_at_oldSubst: when the lookup misses createOldVarsSubst and hits callElim_inputOnlyOldSubst, the codomain is a positional inArgs element and var appears in flatMap getVars inArgs. Same proof bodies as the inline blocks. Both helpers take proc' and args directly so callElim_inputOnlyOldSubst can be referenced by name (needed for the find?_append_none_elim unification at the consumer). Net diff: +123/-77 in CallElimCorrect.lean. Build clean (490 jobs). Sorry count unchanged at 18. No new axioms. Both helpers have 4 callsites in _terminal (Hinv and Hpred_disj for inv, plus their post-side variants). Once Stage 6 lands, the failure arm reuses the same callsites with identical arguments.
Lift callElimStatementCorrect_terminal's signature from f=false→false
to {f : Bool}, with Heval and conclusion both at flag f. Non-call cases
close via nc_close polymorphically (Heval passes through unchanged for
identity sts). Call branch dispatches:
cases f with
| false => existing 2200-LoC body verbatim (f=false case)
| true => exact callElimStatementCorrect_terminal_call_arm_fail ...
The _terminal_call_arm_fail helper is declared with full signature
(~25 hypotheses from the cases Hcc destructure) and a sorry body.
The body will be filled by a follow-up commit using the polymorphic
helpers shipped in the infra baseline (H_asserts_zip_fail, H_havocs_poly,
H_assumes_zip_poly, EvalCallElim_glue_fail) plus boolTyped extraction.
Build clean (490 jobs) at this checkpoint with one expected sorry
(line 1654). Sorry count: 18 → 19 (temporary; will revert to 18
when the helper body lands).
This commit is the Stage 6 scaffold; the next commit fills in the
failure-arm body.
…ions
Aligns the source-side failure semantics with callElimCmd's filtering.
Free preconditions ('free requires') are assumed by the implementation
but not checked at call sites (Procedure.lean §92), so they don't
contribute to caller-side failure. Previously the iff ranged over ALL
preconditions, which made the polymorphic-f failure-arm proof
unattainable: a witness 'pre with eval ≠ tt at σAO' could fall in the
Free segment, giving no L4 entry to flip the flag.
Changes:
- StatementSemantics.lean: call_sem rule's Hpre_def and Hpre_iff now
range over `getCheckExprs (preconditions.filter (·.attr ≠ .Free))`.
- StatementSemanticsProps.lean: EvalCallBodyRefinesContract /
EvalCommandRefinesContract / StepStmt[Star]_refines_contract /
EvalStatement[s]RefinesContract gain `[LawfulBEq Expression.Expr]`
instances (needed for the contains↔mem bridge in the new
presFiltered_subset derivation that powers the rule constructor).
- CallElimCorrect.lean: success arm's Hpre_evalTt/Hpre re-synthesis
now ranges over filtered preconditions (matches the new iff).
HpreFiltered no longer needs filterCheck_in_getCheckExprs — direct
membership in presFiltered transports straight to the filtered
contains-form.
- CallElimCorrect.lean: failure-arm helper signature
(callElimStatementCorrect_terminal_call_arm_fail) updated to take
Hpre_def/Hpre_iff at the new filtered shape.
Build clean (490 jobs) with the one expected sorry at line 1654
(failure-arm body, to be filled by next commit). No new axioms.
Replaces the sorry-stubbed callElimStatementCorrect_terminal_call_arm_fail with a complete proof body (~2,148 LoC) that mirrors the success arm's L1-L3 + L5/L6 plumbing while flag-flipping at L4: - L1, L2, L3: identical to success (init segments produce f=false→false) - L4 (asserts): H_asserts_zip_fail with failing-pre witness - L5 (havocs): H_havocs_poly at f := true - L6 (assumes): H_assumes_zip_poly at f := true - Glue: EvalCallElim_glue_fail composing false→false→false→false→true Failing-pre witness derivation now unblocked by Path 1 (commit fdf2fc4): 1. Hpre_iff at flag=true: Classical.em on "all preconditions eval to tt" → contrapositive gives ∃ pre ∈ presFiltered, δ σAO pre ≠ some tt. 2. Bool-totality from new WFCallSiteProp.preBoolTyped field (mirrors the boolTyped clause on WFPrePostProp): δ σAO pre = some tt ∨ ff. 3. Combine → ∃ pre, δ σAO pre = some HasBool.ff. 4. Transport σAO ↦ σ_old via subst_fvars_correct against Hsubst_L4. WF additions: - WFCallSiteProp.preBoolTyped (line ~1156): per-precondition bool-totality. - WFCallSiteSpec.preBoolTyped (line ~1207): per-call specialization. - WFCallSiteProp.specialize forwards the new field. Public signature lift: callElimStatementCorrect's terminal arm gains {f : Bool} polymorphism — source-fail traces map to target-fail traces, source-success traces map to target-success traces. Exit arm unchanged (call statements never produce .exiting). Build clean (490 jobs). Sorry count: 18 (back to baseline; the temporary sorry from Stage 6 scaffold is now resolved). No new axioms. Helpers EvalCallElim_glue_fail, H_asserts_zip_fail, H_havocs_poly, H_assumes_zip_poly all genuinely consumed (not dormant scaffolding).
…es of _poly variants Both non-poly forms now delegate to their polymorphic-f siblings at f := false, removing two near-duplicate ~70-line proof bodies. H_assumes_zip_poly is moved above H_assumes_zip to satisfy forward-reference order. H_asserts_zip continues to call H_check_block_zip unchanged (now a 1-line corollary).
Generalize EvalCallElim_glue_fail to be polymorphic in the post-asserts hasFailure flag `f` (was hard-coded to `true`). With `f` inferred from HL4's output env, the passing arm (`f = false`) becomes the same proof as the failing arm (`f = true`). EvalCallElim_glue is now a one-line abbrev delegating to EvalCallElim_glue_fail; both names remain resolvable and call sites are unchanged. Saves 17 LoC by collapsing the two identical 5-step EvalStatementsContractApp chains into one.
Three byte-identical δ-fvar lookup derivations in CallElimCorrect were collapsed into a single file-scope private theorem `delta_fvar_eq_of_wfvars`. Each call site now obtains the ∀ σ v, δ σ (fvar v) = σ v lemma via a one-line application instead of re-deriving it from `Imperative.WellFormedSemanticEvalVar`. Net -8 LoC. The instructions projected -14 with a `LawfulBEq` bound on the helper, but that constraint is not synthesizable at the call sites and is not actually needed by the proof — `HasFvar.getFvar` on `Lambda.LExpr.fvar` reduces by the instance, not by `BEq`. Dropping it both fixes the build and matches the existing usage pattern. Build: 490 jobs clean. Sorry/admit count unchanged (20 in Strata/, all pre-existing).
Hoist the per-entry decide-reduction proof bridging the boolean (!=) and propositional (≠) filter forms over Procedure.CheckAttr.Free into a single private theorem. Replaces three inline copies in the fail-arm of callElimStatementCorrect_terminal_call_arm_fail and one in the success-arm asserts-zip step.
…n + by_cases Refactor HexFail derivation in callElimStatementCorrect_terminal_call_arm_fail from two stacked Classical.em rcases blocks into a single Classical.byContradiction + intro + apply Hnot_all + by_cases pattern. Same axiom dependency (Classical.choice, via byContradiction) — but the structure is more idiomatic Lean 4: byContradiction gives the negated conclusion directly, and by_cases handles the per-entry split without an explicit absurd dance. -6 LoC. Build clean (490 jobs). Sorry count: 0. Axioms unchanged: [propext, Classical.choice, Quot.sound].
Drops 6 contract-refinement theorems with no external callers (audit-confirmed via repo-wide grep, single closed dead chain): EvalCallBodyRefinesContract EvalCommandRefinesContract StepStmt_refines_contract (private) StepStmtStar_refines_contract EvalStatementsRefinesContract EvalStatementRefinesContract Also removes the prefatory NOTE/docstring block specific to EvalCallBodyRefinesContract. Helpers core_step_preserves_wfExprCongr / core_wfExprCongr_preserved are retained — the latter is consumed by Strata/Transform/ProcBodyVerifyCorrect. Build: lake build green (490/490). Axioms unchanged ([propext, Classical.choice, Quot.sound]) for both core_wfExprCongr_preserved and CallElimCorrect.callElimStatementCorrect. Net: -319 LoC.
The boolTyped field was added by the polymorphic-f branch but never constructed anywhere. The failure arm now uses WFCallSiteProp.preBoolTyped (in CallElimCorrect.lean) instead. Also drop the StatementSemantics import that was added solely to elaborate the dead field's body.
After Path 1 + Cleanup R1-5 (filter_bne_eq_filter_ne), the .contains-form bridge filterCheck_in_getCheckExprs has zero callsites. Only the membership-form filterCheck_mem_getCheckExprs remains in use. -12 LoC; axioms unchanged ([propext, Classical.choice, Quot.sound]).
Both call-elim arms (success and failure) previously rebuilt the same seven `have`-blocks (Hwfgenargs/outs/olds, Hgenargs/outs/olds, HgenApp, Hgennd') from scratch. Extract the combined-Nodup derivation into a single private helper `genTrips_combined_nodup` and call it from each arm; downstream consumers only need `Hgennd'` (already piped into `fresh_triple_σ_facts`) plus the `Forall isTempIdent`/`isOldTempIdent` witnesses, which remain as one-line `have`s. Net: -28 LoC. Build clean (490 jobs); axioms unchanged ([propext, Classical.choice, Quot.sound]); zero new sorries/admits.
Both arms of callElimStatementCorrect_terminal's call branch derived the same four facts (Hwf2_univ, HσAO_reads_outs, HoldVars_sub_callLhs, HoldVars_sub_outs) before invoking HoldEval_bridge_at_σO. Bundle them into one private theorem and obtain at each call site. Net: -20 LoC. Axioms unchanged: [propext, Classical.choice, Quot.sound].
The Hinv proofs at the four per-entry payload sites in callElimStatementCorrect each open the recurring pattern (k1, k2) ∈ ((getVars expr).removeAll ((l₁ ++ l₂) ++ (l₃ ++ l₄))).zip ... into k1 = k2 plus six leaf facts via a manual `zip_self_eq` + `simp [List.removeAll, List.mem_filter, ...]` + `notin_append4` cascade. Bundle that decomposition into a single `zip_removeAll4_decompose` helper next to `notMem_of_Forall_neg` so each call site collapses to one `obtain`. Net: -19 LoC, axioms unchanged ([propext, Classical.choice, Quot.sound]).
Both call-arm sites in `_terminal_call_arm_fail` and `_terminal` re-derived `proc' = proc` from `Hp`+`Hfind`+`lkup` and built the postcondition-membership lift `proc' ↦ proc`. Factor into a single private theorem `procEq_and_postExprs_bridge` returning the pair, and replace the two ~15-line blocks with one-line `obtain ⟨HprocEq, c_in_postExprs_of_proc'⟩ := ...` calls. Net: -6 LoC (helper costs ~20 lines, sites save ~25 combined). Axioms unchanged; lake build clean (490 jobs).
createAsserts_ok and createAssumes_ok shared a byte-identical inductive proof body, differing only in the Statement constructor (assert vs assume). Extract createCheckStmts_ok parameterized by the constructor mkStmt, and rewrite both _ok lemmas as one-line specializations (unfold + exact). Single-source the proof body; both call sites preserve their public spec shape. Net -3 LoC; primary win is structural (eliminate duplicated induction proof, ~36 lines collapsed into one helper at the cost of two thin specialization wrappers).
In subst_fvars_eval_bridge, the abs/quant/app/eq cases each unfolded a 4-block surv/sub witness pair. The ite case already used a compact HmkLeft/HmkMid/HmkRight position-lifting style. Apply the same style to the other four cases. Each non-ite case now defines 1-2 small position-lift lemmas (Hmk / HmkL / HmkR) and inlines the surv/sub call as fun-form arguments to the IH. abs: 24 → 11 lines quant: 42 → 21 lines app: 42 → 21 lines eq: 42 → 21 lines Build clean (490 jobs); axioms unchanged (propext, Quot.sound). LoC delta: -79.
Promote the per-fvar bridge for callElim_inputOnlyOldSubst's codomain into a private theorem (parallel to HoldSubBridge_at_σO). The ~140-LoC derivation in callElimStatementCorrect's call-arm success and failure branches collapses to a single 8-line application. Net: -84 LoC (helper +220 / per-arm savings 2 × ~152).
Collapse 48 instances of `rw [hEq]; exact h` (and `rw [← hEq]; exact h`) into single-line term-mode transports `exact hEq ▸ h` (resp. `exact hEq.symm ▸ h`). Pure cosmetic refactor; build clean, axioms unchanged ([propext, Classical.choice, Quot.sound]). Net: -49 LoC.
The HwfL term in HoldSubBridge_at_σO already had δ_fvar_eq in scope. The wrapping show/exact tactic block was a definitional unfold of Core.Transform.createFvar (which reduces to Lambda.LExpr.fvar () _ none), so the term-mode application of δ_fvar_eq closes the goal directly.
Where 'apply F ?_ Y' followed by 'exact Z' in have-blocks could be written as a direct term 'F Z Y', collapse to term mode. Skipped sites where the apply relied on goal-driven unification of implicits (genTrips_combined_nodup) or had multi-bullet remaining goals.
Replace 'by intro h; exact f h …' tactic blocks with the corresponding term-mode lambda 'fun h => f h …' across several local 'have' bindings in callElimStatementCorrect_terminal_call_arm_fail and callElimStatementCorrect_terminal. The bodies were already pure applications, so the tactic wrapper was pure boilerplate. LoC: -11 lines. Build: clean (490/490). Sorries: 0. Axioms: [propext, Classical.choice, Quot.sound].
This was referenced Jun 8, 2026
PROgram52bc
added a commit
that referenced
this pull request
Jun 9, 2026
## Summary Round 3 tier-1 cleanups for the polymorphic-`f` callElim work merged in #1340. These commits were authored on top of #1340's head before the squash merge but were not included; this PR brings them onto the parent branch. Net delta: **-287 LoC** (5 files, +181/-468) across 19 small, atomic commits. No new sorries, no new axioms, no behavioral change. ## Highlights | Commit | Description | LoC | |---|---|---| | 2bb66b3 | R3-1: collapse `WFCallSiteProp` into `WFCallSiteSpec` via def-wrapper | -64 | | ca67ea7 | R3-2: drop f=false corollaries; rely on poly versions | -121 | | 680e092 | R3-12: hoist `π/φ/δ` binders into file-scope `variable` block | -10 | | 26bf9ae | R3-10: extract `havocVars_3layer_lift` helper | -23 | | ed3725d | R3-8: add `Procedure.Spec.checkedPreconditions` abbrev | -12 | | 7b6fbb3 | R3-19: simplify HentryFail mem-zip pattern in fail arm | -10 | | 8e3bc3f | R3-32: flatten nested mem_append cases via rcases | -14 | Plus 12 smaller items: bool-totality cleanup, redundant hypothesis drops, helper extractions, doc fixes (stale "seven" counts, removed references to deleted `WFPrePostProp.boolTyped`, fixed inverted section header hierarchy, dropped legacy `Hwfgenst` references). ## Soundness - **Build clean**: 490 jobs, no warnings or errors - **Sorries**: 0 in modified files - **Axioms**: `callElimStatementCorrect` depends only on `[propext, Classical.choice, Quot.sound]` — Lean stdlib only - **No behavioral change**: each commit is a refactor verified by `lake build` ## Test plan - [ ] CI green - [ ] No sorry/axiom regressions vs base
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lifts
callElimStatementCorrect_terminalfrom the parent branch'sf = false → falseshape (PR #1306, success-only refinement) to fully polymorphic{f : Bool}: a source trace ending in failure maps to a target trace ending in failure, and a source success trace maps to a target success trace, in one statement. Downstream consumers no longer case-split on the failure flag at the call site.This is a foundational refactor + generalization, not a feature. Reviewers should follow the proof structure: Stages 1-3 are pure extractions (success theorem unchanged), Path 1 narrows a public iff for semantic alignment, Stage 6 (split into a scaffold and a fill-in) lands the actual polymorphic-f result.
Base branch
This PR sits on top of
htd/callelim-smallstep-on-main2(PR #1306, currently open). Merge into that branch, or rebase ontomainonce #1306 lands.Why this matters
The parent's success-only theorem suffices to argue that call-elimination preserves the absence of bugs, but it leaves a dual gap: it does not say anything about preserving the presence of bugs. Without the failure arm, callElim could in principle turn a buggy program into one that verifies. Lifting
_terminalto{f : Bool}makes that argument compositional — the same lemma covers both directions, and any future transform layered on top of call-elimination inherits the polymorphic guarantee for free.The failure arm also exercises corners of the contract semantics the success arm never touches: the assert-zip on call-site preconditions, havoc of out-parameters in the failure branch, and the assume-zip on call-site postconditions all have to compose correctly under
hasFailure = true. Closing it flushed out a real semantic mismatch inEvalCommandContract.call_sem(Path 1 below) that was invisible from the success-only side.Commit-by-commit walkthrough
Read the seven commits in order:
1. 193dee2 — WIP: polymorphic-f infrastructure (no live consumers)
Forward-looking scaffolding, kept as a separate commit so that the consumer-side proof changes in later commits aren't buried under unrelated infrastructure. Lands in:
Strata/Languages/Core/StatementSemantics.lean— Change A: collapses the two conditionalHupdatepremises ofEvalCommandContract.call_seminto a single unconditionalUpdateStatespremise. The failure path no longer pinsσ' = σ, which is what makes a polymorphic-f rule shape coherent.Strata/Languages/Core/WF.lean—WFPrePostProp.boolTypedclause added (zero migration cost, no live construction sites yet).Strata/Transform/CoreTransformProps.leanandStrata/Transform/SubstProps.lean— new helperssingleCmdToStmts_poly,singletonAssumeEval_poly,H_havocs_poly,H_check_block_zip_poly,H_assumes_zip_poly,singletonAssertFailEval,singletonAssertEval_poly, plus the substantive new walkerH_asserts_zip_fail(L4 flag-flip fromfalsetotruevia a failing-pre witness) andEvalCallElim_glue_fail(composes L1-L6 with the L4 flip).Build clean, sorry-free, axiom-free at this commit.
2. 580ef2d — Stage 1: extract
HoldEval_bridge_at_σOPure refactor. Lifts the per-index δ-eval bridge for
mkOldold-variable fvars at the post-havoc storeσOout ofcallElimStatementCorrect_terminal's call arm into a private theorem with 11 explicit hypotheses. Net diff inStrata/Transform/CallElimCorrect.lean: +114 / -61. Same proof body as the inline version; the signature overhead is intentional because Stage 6 will share this helper between the success and failure arms. Sorry count and axiom set unchanged.3. 5406b08 — Stage 2a: extract
HoldSubBridge_at_σOPure refactor in
Strata/Transform/CallElimCorrect.lean. Lifts the per-fvar bridge forcreateOldVarsSubst's codomain at the L6 intermediate storesσ_R1/σOinto a private theorem with 6 hypotheses; the inline block becomes a one-line call. Net diff: +82 / -29. Sorry count and axiom set unchanged.HinputSubBridge(the larger sibling, originally targeted for Stage 4) is deferred, then ultimately made redundant by the flag-generalization approach (see "Stages 4 & 5: skipped" below).4. cf10129 — Stage 3: extract
b1/b2_var_witness_at_oldSubstPure refactor in
Strata/Transform/CallElimCorrect.lean. Lifts the two class-(b) substitution-decomposition witnesses out of the call arm:b1_var_witness_at_oldSubst: when aMap.find?lands in thecreateOldVarsSubstsegment ofoldSubst_L6, the codomain entry is a fresh-oldcreateFvar genand the witnessvar ∈ getVars wforcesvar = genOldIdents[i].b2_var_witness_at_oldSubst: when the lookup missescreateOldVarsSubstand hitscallElim_inputOnlyOldSubst, the codomain is a positionalinArgselement.Both helpers take
proc'andargsdirectly socallElim_inputOnlyOldSubstcan be referenced by name (needed forfind?_append_none_elimunification at the consumer). 4 callsites each in_terminal(inv + post variants); Stage 6's failure arm reuses the same callsites with identical arguments. Net diff: +123 / -77.Stages 4 & 5: skipped
Originally planned to extract
HinputSubBridgeand a final shared-plumbing block. The flag-generalization approach made these unnecessary — the call branch in_terminalcasesonf, and thefalsearm reuses its existing 2200-LoC body verbatim, so we never needed shared sub-helpers between the two arms. Keeping the inline block on thefalseside was strictly cheaper than extracting it.5. fdf2fc4 — Path 1: narrow
EvalCommandContract.call_semiff to non-Free preconditionsThis is the semantic alignment that makes the failure arm provable.
callElimCmdonly emits asserts for non-Freepreconditions (Free requires are assumed by the implementation, not checked at call sites). The previouscall_semiff ranged over all preconditions, which made the polymorphic-f failure arm unattainable: a witnessprewitheval ≠ ttatσAOcould fall in the Free segment, giving no L4 entry to flip the flag.That mismatch was harmless at
f = false(the success arm never relies on the iff direction that exposed it) but a hard blocker on the failure arm. Restricting the iff to non-Free preconditions aligned the two semantics without changing any existing proof:Strata/Languages/Core/StatementSemantics.lean:call_semrule'sHpre_defandHpre_iffnow range overgetCheckExprs (preconditions.filter (·.attr ≠ .Free)).Strata/Languages/Core/StatementSemanticsProps.lean:EvalCallBodyRefinesContract/EvalCommandRefinesContract/StepStmt[Star]_refines_contract/EvalStatement[s]RefinesContractgain[LawfulBEq Expression.Expr]instances (needed for the contains↔mem bridge in the newpresFiltered_subsetderivation that powers the rule constructor).Strata/Transform/CallElimCorrect.lean: success arm'sHpre_evalTt/Hprere-synthesis ranges over filtered preconditions;HpreFilteredno longer needsfilterCheck_in_getCheckExprs— direct membership inpresFilteredtransports straight to the filtered contains-form. Failure-arm helper signature updated to takeHpre_def/Hpre_iffat the new filtered shape.Build clean (490 jobs) with one expected sorry at line 1654 (failure-arm body, filled by the next commit).
6. 45db77d — Stage 6 scaffold: lift
_terminalto{f : Bool}, delegate failure armShape change without filling in the body. In
Strata/Transform/CallElimCorrect.lean,callElimStatementCorrect_terminal's signature lifts fromf = false → falseto{f : Bool}, withHevaland the conclusion both at flagf. Non-call cases close polymorphically vianc_close(Hevalpasses through unchanged for identity statements). The call branch dispatches:The
_terminal_call_arm_failhelper is declared with full signature (~25 hypotheses from thecases Hccdestructure) and asorrybody, ready for the final commit. Splitting the signature lift from the proof close means the lifted public signature is reviewable and buildable on its own, and the failure-arm proof body lands against an already-stable interface. Sorry count: 18 → 19 (temporary).7. be35c7f — Stage 6: close polymorphic-f failure arm + lift public signature
Replaces the sorry-stubbed
callElimStatementCorrect_terminal_call_arm_failinStrata/Transform/CallElimCorrect.leanwith the full proof body (~2,148 LoC) that mirrors the success arm's L1-L3 + L5/L6 plumbing while flag-flipping at L4:f = false → false).H_asserts_zip_failwith failing-pre witness.H_havocs_polyatf := true.H_assumes_zip_polyatf := true.EvalCallElim_glue_failcomposingfalse → false → false → false → true.Failing-pre witness derivation (now unblocked by Path 1):
Hpre_iffatflag = true:Classical.emon "all preconditions eval tott" → contrapositive gives∃ pre ∈ presFiltered, δ σAO pre ≠ some tt.WFCallSiteProp.preBoolTypedfield (mirrors theboolTypedclause onWFPrePostProp):δ σAO pre = some tt ∨ ff.∃ pre, δ σAO pre = some HasBool.ff.σAO ↦ σ_oldviasubst_fvars_correctagainstHsubst_L4.WF additions in
Strata/Languages/Core/WF.lean:WFCallSiteProp.preBoolTyped(~line 1156): per-precondition bool-totality.WFCallSiteSpec.preBoolTyped(~line 1207): per-call specialization.WFCallSiteProp.specializeforwards the new field.The exit arm is unchanged — call statements never produce
.exiting. Sorry count: 19 → 18 (back to baseline). Build clean, no new axioms.Polymorphic helpers — all genuinely consumed
EvalCallElim_glue_failStrata/Transform/CoreTransformProps.leanH_asserts_zip_failStrata/Transform/SubstProps.leanH_havocs_polyStrata/Transform/CoreTransformProps.leanH_assumes_zip_polyStrata/Transform/CoreTransformProps.leanWFPrePostProp.preBoolTypedStrata/Languages/Core/WF.leanHelpers are deliberately single-callsite for now: the polymorphic-f infrastructure is staged to be reusable, but until a second consumer materializes the helpers stay scoped to this proof and we avoid speculative API design. No dormant scaffolding.
Soundness
Strata/Languages/Core/WF.leanare pre-existing and unrelated to this work)#print axioms callElimStatementCorrect_terminal:[propext, Classical.choice, Quot.sound]— Lean stdlib only, no project-local axiomspreBoolTypedhave ≥ 1 live callsite (verified above)call_semiff matchescallElimCmd's actual emission rule (asserts only for non-Free precondition components); success-arm reproof is mechanicalCommit list
Test plan
lake buildclean across all 490 jobsStrata/Languages/Core/WF.leanpreserved)#print axioms callElimStatementCorrect_terminal→ Lean stdlib only ([propext, Classical.choice, Quot.sound])EvalCallElim_glue_fail,H_asserts_zip_fail,H_havocs_poly,H_assumes_zip_poly,WFPrePostProp.preBoolTyped— 1 callsite each)Hpre_evalTtre-synthesis over filtered preconditions is the only consumer-side change in the success armUpdateStatespremise oncall_sem) preserves the parent branch'sfalse → falsesemantics_terminal(none onhtd/callelim-smallstep-on-main2today; re-check after rebase if CallElimCorrect: rewrite using small-step semantics #1306 lands first)