Layer 0 substrate round 5: Tactic/EqContract.lean + barrel + Ω-call fixes
Some checks are pending
Lean Action CI / build (push) Waiting to run

Per THEORY.md §0.10 — the user-facing tactic surface that operates on
the topos-internal contracts.  Five exports:

  · tactic via_eq_contract — translates Path-existence goal to Eq goal
    via pathEqEquiv; CubicalSetC synthesised from registry, residual
    contract obligation surfaces as a subgoal if synthesis fails.
  · tactic find_contract_path — BFS over registered contracts and
    entailment morphisms (currently CDecidableEq → CubicalSetC); on
    exhaustion throws a precise diagnostic listing what was tried.
  · tactic lift_via_topos t — runs via_eq_contract then user-supplied
    tactic on the translated Eq goal.
  · command #contract — lists registered contracts + entailment edges.
  · command #whichContract <T> — synthesises every contract against T,
    reports those that succeed.

Also fixes ℓ-implicit synthesis at four Ω-call sites that the universe-
stratification cascade had left under-annotated (Contract.and / .implies
and Sub.inter / .implies / Ω_internal_logic_sound's 8 nested .and / .implies
calls).  These were only exposed when the Layer 0 modules became
reachable from the root barrel — the cubical-test:exe target's import
closure had previously hidden them.

Barrel additions: Truncation, Decidable, Reify, Omega, Category,
Modality, Subobject, SIP, Bridge.Set, Contract, Reflect,
Tactic.EqContract.  All Layer 0 substrate now reaches the root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maximus Gorog 2026-05-05 15:19:35 -06:00
parent 2f343b0980
commit 294e96633d
4 changed files with 771 additions and 12 deletions

View file

@ -25,3 +25,15 @@ import CubicalTransport.Inductive
import CubicalTransport.Bridge
import CubicalTransport.Question
import CubicalTransport.PropertyTest
import CubicalTransport.Truncation
import CubicalTransport.Decidable
import CubicalTransport.Reify
import CubicalTransport.Omega
import CubicalTransport.Category
import CubicalTransport.Modality
import CubicalTransport.Subobject
import CubicalTransport.SIP
import CubicalTransport.Bridge.Set
import CubicalTransport.Contract
import CubicalTransport.Reflect
import CubicalTransport.Tactic.EqContract

View file

@ -479,7 +479,7 @@ def Contract.empty_ ( : ULevel) : Contract := fun _ =>
Substantively T-dependent: the body applies both `C` and `D` to
T, so the result mentions T through both subcontract values. -/
def Contract.and { : ULevel} (C D : Contract ) : Contract := fun T =>
Ω.and (C T) (D T)
Ω.and ( := ) (C T) (D T)
/-- Disjunction of two contracts. At each input T, evaluates both
contracts and combines their values via `Ω.or` (the
@ -491,7 +491,7 @@ def Contract.or { : ULevel} (C D : Contract ) : Contract := fun T =>
contracts and combines their values via `Ω.implies` (the
Ω-internal arrow type). -/
def Contract.implies { : ULevel} (C D : Contract ) : Contract := fun T =>
Ω.implies (C T) (D T)
Ω.implies ( := ) (C T) (D T)
-- ── §5. Theorems ───────────────────────────────────────────────────────────

View file

@ -98,8 +98,8 @@ def total { : ULevel} : CTerm :=
Real `.lam` over a real binder; references to `$x` are scoped
inside the same expression. -/
def inter (P Q : CTerm) : CTerm :=
.lam "$x" (Ω.and (.app P (.var "$x")) (.app Q (.var "$x")))
def inter { : ULevel} (P Q : CTerm) : CTerm :=
.lam "$x" (Ω.and ( := ) (.app P (.var "$x")) (.app Q (.var "$x")))
/-- Pointwise union: holds at `x` iff at least one of `P`, `Q` holds.
@ -113,8 +113,8 @@ def union { : ULevel} (P Q : CTerm) : CTerm :=
Encoding: `.lam "$x" (Ω.implies (.app P (.var "$x")) (.app Q (.var "$x")))`.
The body uses Ω's internal-arrow `Ω.implies`. -/
def implies (P Q : CTerm) : CTerm :=
.lam "$x" (Ω.implies (.app P (.var "$x")) (.app Q (.var "$x")))
def implies { : ULevel} (P Q : CTerm) : CTerm :=
.lam "$x" (Ω.implies ( := ) (.app P (.var "$x")) (.app Q (.var "$x")))
/-- Pointwise complement: the predicate `¬P`, holding at `x` iff
`P x` is false in the internal logic.
@ -280,23 +280,23 @@ theorem Ω_internal_logic_sound { : ULevel} :
-- (1) Idempotence of ∧: P ∧ P ≡ P
(∀ (P : CTerm), HasType [] P (Ω ) →
∃ (pf : CTerm),
HasType [] pf (CType.path (Ω ) (Ω.and P P) P)) ∧
HasType [] pf (CType.path (Ω ) (Ω.and ( := ) P P) P)) ∧
-- (2) Commutativity of ∧: P ∧ Q ≡ Q ∧ P
(∀ (P Q : CTerm), HasType [] P (Ω ) → HasType [] Q (Ω ) →
∃ (pf : CTerm),
HasType [] pf (CType.path (Ω ) (Ω.and P Q) (Ω.and Q P))) ∧
HasType [] pf (CType.path (Ω ) (Ω.and ( := ) P Q) (Ω.and ( := ) Q P))) ∧
-- (3) Modus ponens validity: P ∧ (P → Q) ≡ P ∧ Q
(∀ (P Q : CTerm), HasType [] P (Ω ) → HasType [] Q (Ω ) →
∃ (pf : CTerm),
HasType [] pf (CType.path (Ω )
(Ω.and P (Ω.implies P Q))
(Ω.and P Q))) ∧
(Ω.and ( := ) P (Ω.implies ( := ) P Q))
(Ω.and ( := ) P Q))) ∧
-- (4) Implication absorption: P → (P → Q) ≡ P → Q
(∀ (P Q : CTerm), HasType [] P (Ω ) → HasType [] Q (Ω ) →
∃ (pf : CTerm),
HasType [] pf (CType.path (Ω )
(Ω.implies P (Ω.implies P Q))
(Ω.implies P Q))) := by
(Ω.implies ( := ) P (Ω.implies ( := ) P Q))
(Ω.implies ( := ) P Q))) := by
-- waits on: prop-univalence packaged from `Soundness.transp_ua`
-- (the same dependency as `OmegaIsProp` in `Omega.lean`). Each of
-- the four Heyting laws is a Path-equality at Ω, and the cubical

View file

@ -0,0 +1,747 @@
/-
CubicalTransport.Tactic.EqContract
==================================
User-facing tactic surface that operates on the topos-internal
contracts (THEORY.md §0.10 / §∞.3).
## What this module exports
Three tactics and two commands:
· `tactic via_eq_contract` — translates a cubical Path-equality
existence goal to a Lean Eq goal using `pathEqEquiv`, gated by
`CubicalSetC` synthesis from the contract registry. After the
tactic runs, the goal is the Eq-side; the user discharges it
with mathlib (or any other Lean reasoning). When the contract
cannot be discharged automatically, the residual `CubicalSetC T`
obligation is left as an additional subgoal alongside the Eq.
· `tactic find_contract_path` — synthesis: given a goal of shape
expressing "find me a contract for T", BFS the contract
registry combined with the entailment-morphism table to
discover a contract value. Closes the goal with the
discovered pair, or fails with a precise error.
· `tactic lift_via_topos t` — bundled: takes a tactic argument
`t` (as a `tacticSeq`), runs `via_eq_contract` to translate the
goal, then applies `t` on the translated goal. One-shot
transport from cubical-side to mathlib-side.
· `command #contract` — displays the topos of contracts: lists
every registered Contract by name (from
`Reflect.Contract.allRegistered`), alongside the known
entailment morphisms.
· `command #whichContract <CType>` — given a CType expression,
attempts contract synthesis for every registered contract and
lists the ones that succeed.
## Design
All five user-facing items share four internal helpers:
· `parsePathGoal` — given the goal Expr, peels `Exists`,
`HasType`, and `CType.path` to extract the four pieces
`(α_expr, embed_expr, T_expr, a_value_expr, b_value_expr)`
needed to apply `pathEqEquiv`.
· `entailmentRegistry` — the hardcoded table of known entailment
morphisms `(fromContractName, toContractName, lemmaName)`.
Currently houses only the canonical `CDecidableEq → CubicalSetC`
morphism via `CubicalSetC_of_CDecidableEq`; additional
entailments land here as Hedberg / J-rule discharges unlock
further Set-level promotions.
· `synthCubicalSetC` — BFS over the entailment table to attempt
to construct a Lean-Prop witness of `CubicalSetC T` from the
registry. Falls back to leaving the obligation as an mvar
when no closed chain succeeds.
· `attemptSynthesis` — for `#whichContract`: given a contract
name and a CType, try the same BFS to construct a satisfaction
witness, returning whether it succeeded.
## Implementation discipline
· No `sorry` is emitted by any tactic body. When a tactic cannot
construct a proof, it throws a precise `throwError` with
diagnostic context (the goal, the expected shape, the registry
contents, the entailment chain attempted).
· The BFS in `find_contract_path` and `synthCubicalSetC` is real:
a worklist over `(currentName, derivationChain)` pairs,
expanded by entailment morphisms, with a visited-set to prevent
cycles. When the worklist is exhausted, a precise error fires
that lists what was tried.
· Pattern matching on the goal Expr is precise: the
`parsePathGoal` helper reduces (`whnf`) at every layer and
matches each constructor name explicitly; mismatches throw
diagnostic errors pointing at the actual vs. expected shape.
· The Lean metaprogramming API used is fixed-set: `MVarId`,
`getMainGoal`, `withMainContext`, `replaceMainGoal`,
`liftMetaTactic`, `evalTactic`, `Lean.Meta.mkFreshExprMVar`,
`MVarId.apply`, `Lean.Meta.whnf`, `Expr.getAppFnArgs`. Each
has been verified against the Lean 4.30.0-rc2 source.
-/
import CubicalTransport.Reflect
import CubicalTransport.Bridge.Set
namespace CubicalTransport.Tactic.EqContract
open Lean Lean.Meta Lean.Elab Lean.Elab.Tactic Lean.Elab.Command
open CubicalTransport.Reflect
open CubicalTransport.Bridge.Set
-- ── §1. Entailment morphism registry ──────────────────────────────────────
/-- A single entailment morphism from one named contract to another,
discharged by a named lemma whose signature is
`fromContract T → toContract T`
(or, in the `CubicalSetC ← CDecidableEq` case, with the source
expressed as the corresponding closed-cubical-existential
statement).
Stored as a triple of `Lean.Name`s for cheap registry
inspection; the lemma is applied by name via `MVarId.apply`
on a fresh-mvar expression of the lemma's constant. -/
structure EntailmentMorphism where
/-- The source contract's name (the contract a witness is needed for). -/
fromContract : Lean.Name
/-- The target contract's name (the contract this morphism produces). -/
toContract : Lean.Name
/-- The Lean lemma's `Name` that discharges the entailment. -/
lemmaName : Lean.Name
deriving Repr
/-- The hardcoded entailment registry. Each entry is read by the
`synthCubicalSetC` BFS and by the `#contract` command.
The sole entry currently formalised is
`CDecidableEq → CubicalSetC` via `CubicalSetC_of_CDecidableEq`
(Bridge/Set.lean §1). Additional entailments land here as
Hedberg (`Decidable.lean`) and the J-rule combinator from
`Soundness.transp_ua` discharge further Set-level promotions:
· `CGroupC → CubicalSetC` once the group-on-a-Set lemma lands;
· `CCoxeterC → CGroupC` once the Coxeter-is-group inclusion lands;
· `CSheafC → CSiteC` once the sheaf-on-site projection lands.
Each entry's `lemmaName` is a real Lean constant — the BFS tries
`MVarId.apply` on a fresh-level-instantiated `mkConst lemmaName`
expression. -/
def entailmentRegistry : List EntailmentMorphism := [
{ fromContract := ``CubicalTransport.Decidable.CDecidableEq
toContract := ``CubicalTransport.Bridge.Set.CubicalSetC
lemmaName := ``CubicalTransport.Bridge.Set.CubicalSetC_of_CDecidableEq }
]
-- ── §2. Parsing helpers for the via_eq_contract goal shape ────────────────
/-- The five pieces extracted from a Path-existence goal. Used by
`via_eq_contract` and `lift_via_topos` to construct the
`Iff.mpr (pathEqEquiv ...)` term that flips the goal from the
Path-side to the Eq-side.
· `αExpr` — the Lean type `α : Type` whose elements are being
equated (the `α` of `[CubicalEmbed α]`).
· `embedExpr` — the `CubicalEmbed α` typeclass instance.
· `tExpr` — the carrier CType `T : CType `, equal to
`@CubicalEmbed.ctype α embedExpr`.
· `aExpr` — the left endpoint value `a : α`.
· `bExpr` — the right endpoint value `b : α`. -/
structure PathGoalParts where
αExpr : Expr
embedExpr : Expr
tExpr : Expr
aExpr : Expr
bExpr : Expr
/-- Strip a chain of metadata wrappers and instantiate metavariables
to expose the underlying expression head, but do NOT unfold any
constants (typeclass projections, definitions, etc.). Used at
every layer of `parsePathGoal` to peel through the elaborated
encoding without losing the symbolic structure (which `whnf`
with full transparency would erase via β/δ-reduction of
typeclass projections like `CubicalEmbed.ctype` and
`CubicalEmbed.toCTerm`).
Implementation: `instantiateMVars` followed by `whnf` at
`.reducible` transparency, which only reduces `[reducible]`
declarations (not typeclass projections nor definitions). -/
private def reduce (e : Expr) : MetaM Expr := do
let e ← instantiateMVars e
withTransparency .reducible (whnf e)
/-- Try to extract the underlying value `a : α` from an
`@CubicalTransport.Bridge.CubicalEmbed.toCTerm α inst aValue`
application. Returns the third explicit argument when matched.
The encoding produced by `CubicalEmbed.toCTerm a` elaborates to
the three-explicit-argument form `@toCTerm α inst a`. We match
by constant name and pull the value off the args array. -/
private def extractToCTermValue (e : Expr) : MetaM (Option Expr) := do
let e ← reduce e
let (fn, args) := e.getAppFnArgs
if fn == ``CubicalTransport.Bridge.CubicalEmbed.toCTerm then
-- @CubicalEmbed.toCTerm α inst a — three args. The value
-- lives in the last position.
if h : args.size ≥ 3 then
return some (args[args.size - 1]'(by omega))
else
return none
else
return none
/-- Try to extract the `α` and `inst` from a
`@CubicalTransport.Bridge.CubicalEmbed.ctype α inst` application.
Returns the pair `(α, inst)` when matched. Used by
`parsePathGoal` to peel the carrier CType layer. -/
private def extractCubicalEmbedCarrier (e : Expr) :
MetaM (Option (Expr × Expr)) := do
let e ← reduce e
let (fn, args) := e.getAppFnArgs
if fn == ``CubicalTransport.Bridge.CubicalEmbed.ctype then
-- @CubicalEmbed.ctype α inst — two arguments.
if h : args.size ≥ 2 then
let α := args[0]'(by omega)
let inst := args[1]'(by omega)
return some (α, inst)
else
return none
else
return none
/-- Parse a goal expression of the shape
`∃ (t : CTerm), HasType [] t
(.path (CubicalEmbed.ctype (α := α))
(CubicalEmbed.toCTerm a)
(CubicalEmbed.toCTerm b))`
into the five pieces `(α, inst, T, a, b)` needed to invoke
`pathEqEquiv`.
Returns `none` if the goal does not have this exact shape; the
caller then throws a precise diagnostic error.
Algorithm:
1. `whnf` the goal to expose the `Exists` head.
2. Match `Exists` with two args: `[CTerm_type, predicate_λ]`.
The predicate is `fun t => HasType [] t (.path T a_emb b_emb)`.
3. Strip the lambda to get the body, with `t` as `bvar 0`.
4. `whnf` the body to expose `HasType`.
5. Match `HasType` with its full arg list and pull the LAST
argument (the type).
6. `whnf` the type to expose `.path`.
7. Match `.path` with four args: `[, T_carrier, a_emb, b_emb]`.
8. `whnf` `T_carrier` to expose `CubicalEmbed.ctype α inst`;
extract `(α, inst)`.
9. `whnf` `a_emb` and `b_emb` to expose `CubicalEmbed.toCTerm`
applications; extract the underlying values. -/
def parsePathGoal (goalType : Expr) :
MetaM (Option PathGoalParts) := do
let goalType ← reduce goalType
-- Step 1-2: peel Exists.
let (existsFn, existsArgs) := goalType.getAppFnArgs
if existsFn != ``Exists then
return none
if existsArgs.size < 2 then
return none
let predicate := existsArgs[1]!
-- Step 3: strip the lambda to get the body. The body has the
-- bound `t` as `bvar 0`.
if !predicate.isLambda then
return none
let body := predicate.bindingBody!
let body ← reduce body
-- Step 4-5: peel HasType. The encoding is
-- @HasType ctx t A
-- — four explicit (or some implicit) arguments. We match by
-- constant name and pull the LAST arg as the type expression
-- (T_expr).
let (hasTypeFn, hasTypeArgs) := body.getAppFnArgs
if hasTypeFn != ``HasType then
return none
if hasTypeArgs.size < 4 then
return none
-- Last arg is the CType.
let tExpr := hasTypeArgs[hasTypeArgs.size - 1]!
-- Step 6-7: peel CType.path.
let tExpr ← reduce tExpr
let (pathFn, pathArgs) := tExpr.getAppFnArgs
if pathFn != ``CType.path then
return none
if pathArgs.size < 4 then
return none
-- Args: [, T_carrier, a_emb, b_emb].
let tCarrier := pathArgs[1]!
let aEmb := pathArgs[2]!
let bEmb := pathArgs[3]!
-- Step 8: extract α and inst from T_carrier.
let some (α, inst) ← extractCubicalEmbedCarrier tCarrier | return none
-- Step 9: extract a and b values from the toCTerm forms.
let some aVal ← extractToCTermValue aEmb | return none
let some bVal ← extractToCTermValue bEmb | return none
return some {
αExpr := α
embedExpr := inst
tExpr := tCarrier
aExpr := aVal
bExpr := bVal
}
-- ── §3. Universe-level extraction helper ──────────────────────────────────
/-- Extract the universe-level argument from a CType expression's
type. For `T : CType `, `inferType T` yields `CType `, and
we want `` as an Expr. Used by `synthCubicalSetC` and
`via_eq_contract` to fill in the `` argument to
`CubicalSetC T`. -/
def extractCTypeLevel (T : Expr) : MetaM Expr := do
let tType ← inferType T
let tType ← whnf tType
let (_, args) := tType.getAppFnArgs
if args.size ≥ 1 then
return args[0]!
else
throwError "extractCTypeLevel: cannot extract universe level from {← ppExpr tType} (expected `CType `-shaped)"
-- ── §4. CubicalSetC synthesis (BFS over the entailment registry) ──────────
/-- Configuration cap on the BFS recursion depth, to keep the
search bounded. Five layers is more than enough for the
current entailment graph (which has only one edge); leaves
headroom for future entailments. -/
private def synthDepthCap : Nat := 5
/-- BFS over the entailment registry to attempt construction of a
closed `Expr` that discharges `goalMVar`.
The implementation runs as follows:
· For each entailment morphism whose `toContract` matches
the goal's head constant, try `MVarId.apply` with the
morphism's lemma.
· The resulting subgoals (the morphism's hypotheses) are
each fed back to `bfsSynth` recursively.
· Stop when no remaining subgoals (success), the depth cap
is exceeded (failure), or no morphism applies (failure).
Returns `true` on success, `false` on failure. On success,
`goalMVar` is fully assigned (and so are any subgoals
introduced along the way). On failure, the caller should run
this in a `withSavedState` block to roll back partial
assignments. -/
partial def bfsSynth (goalMVar : MVarId) (depth : Nat := synthDepthCap) :
MetaM Bool := do
if depth == 0 then
return false
goalMVar.withContext do
let goalType ← goalMVar.getType
let goalType ← whnf goalType
let (headFn, _) := goalType.getAppFnArgs
-- For each entailment morphism whose `toContract` matches the
-- head, try the application.
let candidates := entailmentRegistry.filter fun m => m.toContract == headFn
for morphism in candidates do
let lemmaConst ← mkConstWithFreshMVarLevels morphism.lemmaName
let savedState ← saveState
let attemptResult : MetaM (Option (List MVarId)) := do
try
let r ← goalMVar.apply lemmaConst
return some r
catch _ =>
return none
match ← attemptResult with
| none =>
restoreState savedState
continue
| some newGoals =>
-- Recursively try to discharge each new goal.
let mut allDischarged := true
for ng in newGoals do
if !(← bfsSynth ng (depth - 1)) then
allDischarged := false
break
if allDischarged then
return true
else
-- Roll back the partial application and try the next
-- candidate morphism.
restoreState savedState
continue
-- No morphism worked: synthesis failure.
return false
/-- Synthesize a closed `Expr` of type `CubicalSetC T_expr` by BFS
over the entailment registry. Returns `some witnessExpr` if
the synthesis succeeds, `none` otherwise.
The returned expression has type `CubicalSetC T_expr` and can
be passed directly as the `c : CubicalSetC ...` argument to
`pathEqEquiv` (the lemma's signature is exactly
`pathEqEquiv c a b : ... ↔ a = b`).
On failure, the caller (typically `via_eq_contract`) reports a
precise error or leaves the obligation as a residual subgoal. -/
def synthCubicalSetC (T_expr : Expr) :
MetaM (Option Expr) := do
-- The goal type is `CubicalSetC T_expr`. Build the mvar and
-- run BFS.
let levelExpr ← extractCTypeLevel T_expr
let cubicalSetCTy := mkAppN
(mkConst ``CubicalTransport.Bridge.Set.CubicalSetC)
#[levelExpr, T_expr]
let savedState ← saveState
let goalMVar ← mkFreshExprMVar cubicalSetCTy MetavarKind.synthetic
if (← bfsSynth goalMVar.mvarId!) then
let result ← instantiateMVars goalMVar
-- Verify that the result is fully closed (no remaining mvars).
if (← getMVars result).isEmpty then
return some result
else
-- Partial discharge: roll back and report failure.
restoreState savedState
return none
else
-- BFS failed: roll back and report failure.
restoreState savedState
return none
-- ── §5. via_eq_contract ───────────────────────────────────────────────────
/-- The `via_eq_contract` tactic. Translates a cubical Path-side
existence goal to a Lean Eq goal via `pathEqEquiv`'s `mpr`
direction.
Expected goal shape:
`⊢ ∃ (t : CTerm), HasType [] t
(.path (CubicalEmbed.ctype (α := α))
(CubicalEmbed.toCTerm a)
(CubicalEmbed.toCTerm b))`
Behavior:
· Inspect the goal; throw a precise error if it doesn't
match this shape.
· Synthesize `CubicalSetC T` from the entailment registry
via `synthCubicalSetC`. If synthesis succeeds, the
contract argument is filled in automatically. If not, the
`CubicalSetC T` obligation is left as an additional
subgoal alongside `a = b`.
· Apply `Iff.mpr (pathEqEquiv c a b)` to the goal, replacing
it with `a = b` (plus the residual `CubicalSetC T` if
unsolved).
-/
syntax "via_eq_contract" : tactic
elab_rules : tactic
| `(tactic| via_eq_contract) => do
let goal ← getMainGoal
goal.withContext do
let goalType ← goal.getType
let goalType ← instantiateMVars goalType
-- Step 1: parse the Path-existence shape.
let some parts ← parsePathGoal goalType
| throwError "via_eq_contract: goal is not a cubical Path-existence shape.\n\
Expected: ∃ t, HasType [] t (.path (CubicalEmbed.ctype) (CubicalEmbed.toCTerm a) (CubicalEmbed.toCTerm b))\n\
Got: {← ppExpr goalType}\n\
Hint: the goal's outer head must be ∃, with the body asserting a typed-Path existence."
-- Step 2: attempt to synthesize CubicalSetC T from the
-- registry. Record success/failure for the application
-- step that follows.
let synthesizedC ← synthCubicalSetC parts.tExpr
-- Step 3: build the application `Iff.mpr (pathEqEquiv ?c a b)`.
-- We use a metavariable for the contract argument when
-- synthesis failed; otherwise we use the synthesized term.
let cArg ← match synthesizedC with
| some witness => pure witness
| none =>
-- Make a fresh mvar of the appropriate type, to be left
-- as an additional subgoal.
let levelExpr ← extractCTypeLevel parts.tExpr
let cubicalSetCTy := mkAppN
(mkConst ``CubicalTransport.Bridge.Set.CubicalSetC)
#[levelExpr, parts.tExpr]
mkFreshExprMVar cubicalSetCTy MetavarKind.syntheticOpaque
-- Build the pathEqEquiv application:
-- `@pathEqEquiv α inst c a b`. Use `mkAppOptM` so the
-- implicit `α` and `[CubicalEmbed α]` instance arguments are
-- filled in correctly (we supply them as `some`-options
-- explicitly to override implicit-search).
let equivApp ← mkAppOptM
``CubicalTransport.Bridge.Set.pathEqEquiv
#[some parts.αExpr, some parts.embedExpr, some cArg,
some parts.aExpr, some parts.bExpr]
-- Apply `Iff.mpr` to flip the direction. `Iff.mpr` has the
-- signature `{a b : Prop} → (a ↔ b) → b → a`, so applying it
-- to `equivApp : (∃...) ↔ (a = b)` yields a function of type
-- `(a = b) → (∃...)`. Use `mkAppM` so the implicit
-- propositional arguments get filled in from the type of
-- `equivApp`.
let appliedTerm ← mkAppM ``Iff.mpr #[equivApp]
-- Apply to the main goal. `MVarId.apply` will produce new
-- subgoals for any unsolved arguments — the `a = b` goal
-- and (if synthesis failed) the `CubicalSetC T` goal.
let newGoals ← goal.apply appliedTerm
replaceMainGoal newGoals
-- ── §6. find_contract_path ────────────────────────────────────────────────
/-- The `find_contract_path` tactic. Synthesis: given a goal,
BFS the contract registry combined with the entailment-
morphism table to discover a contract value or chain that
closes the goal.
Goal shape (chosen interpretation, documented below):
`⊢ <some-shape> involving a registered contract`
The tactic tries each registered contract as a closed lemma
via `MVarId.applyConst`-style application. When a direct
application doesn't close the goal, the BFS expands the
frontier by adding contracts reachable via entailment
morphisms whose `fromContract` is the current contract.
Why this shape: THEORY.md §0.10 specifies
`find_contract_path` as "given a goal, walks the contract DAG
to find a sequence of contract entailments that resolve the
goal." The most natural interpretation is "try each
registered contract; if direct application fails, follow
entailment edges."
Behavior:
· Get the registry of all registered contract names.
· For each name, look up the entry; try
`MVarId.applyConst` of the contract's defining constant.
· BFS-expand by entailment morphisms.
· On exhaustion, throw an error listing the registered
contracts, the entailment morphisms, and the chains
attempted.
-/
syntax "find_contract_path" : tactic
elab_rules : tactic
| `(tactic| find_contract_path) => do
let goal ← getMainGoal
goal.withContext do
let goalType ← goal.getType
let goalType ← instantiateMVars goalType
-- Get the registered contracts.
let registered ← Contract.allRegistered
if registered.isEmpty && entailmentRegistry.isEmpty then
throwError "find_contract_path: the contract registry is empty AND \
there are no entailment morphisms.\n\
No contracts have been registered via `Contract.register` in any \
module's `initialize` block.\n\
Goal was: {← ppExpr goalType}"
-- BFS worklist: each entry is a contract name and a list of
-- entailments traversed to reach it. Start with all
-- registered contracts as seeds.
let mut visited : Std.HashSet Lean.Name := ∅
let mut frontier : List (Lean.Name × List Lean.Name) :=
registered.map fun n => (n, [n])
let mut attemptedChains : List (List Lean.Name) := []
let mut closed := false
while !frontier.isEmpty do
match frontier with
| [] =>
-- Unreachable: while-guard forbids empty frontier; we
-- include this arm to satisfy the exhaustiveness
-- check.
break
| (n, chain) :: rest =>
frontier := rest
if visited.contains n then
continue
visited := visited.insert n
attemptedChains := chain :: attemptedChains
let entry? ← Contract.lookupByName n
match entry? with
| none =>
-- A name in `allRegistered` should always resolve;
-- defensively skip and continue if it doesn't.
continue
| some _entry =>
-- Try to close the goal using the contract's defining
-- constant. `applyConst` instantiates fresh universe
-- mvars and unifies the conclusion with the goal.
let savedState ← saveState
let attemptResult : MetaM (Option (List MVarId)) := do
try
let result ← goal.applyConst n
return some result
catch _ =>
return none
match ← attemptResult with
| some [] =>
-- All subgoals discharged: success.
replaceMainGoal []
closed := true
break
| _ =>
-- Direct application didn't close cleanly. Roll back
-- and expand frontier by entailments from n.
restoreState savedState
for morphism in entailmentRegistry do
if morphism.fromContract == n && !visited.contains morphism.toContract then
frontier := frontier ++ [(morphism.toContract, morphism.toContract :: chain)]
continue
if closed then return
-- BFS exhausted without closing.
let registeredStr := registered.map fun n => s!"{n}"
let entailmentStr := entailmentRegistry.map fun m =>
s!"{m.fromContract} → {m.toContract} (via {m.lemmaName})"
let attemptedStr := attemptedChains.map fun c =>
String.intercalate " → " (c.map fun n => s!"{n}")
throwError "find_contract_path: contract synthesis failed.\n\
Goal: {← ppExpr goalType}\n\
Registered contracts ({registered.length}): {registeredStr}\n\
Entailment morphisms ({entailmentRegistry.length}): {entailmentStr}\n\
Chains attempted ({attemptedChains.length}): {attemptedStr}"
-- ── §7. lift_via_topos ────────────────────────────────────────────────────
/-- The `lift_via_topos t` tactic. Bundled one-shot transport from
cubical-side to mathlib-side.
Behavior:
1. Run `via_eq_contract` to translate the goal from the
Path-existence shape to the Eq-shape `a = b`.
2. Run the user-supplied tactic `t` on the translated goal.
Effectively: `lift_via_topos t ≡ via_eq_contract; t`. -/
syntax "lift_via_topos" tacticSeq : tactic
elab_rules : tactic
| `(tactic| lift_via_topos $t:tacticSeq) => do
evalTactic (← `(tactic| via_eq_contract))
evalTactic t
-- ── §8. #contract command ─────────────────────────────────────────────────
/-- The `#contract` command. Displays the topos of contracts:
lists every registered Contract by name (read from
`Reflect.Contract.allRegistered`), alongside the known
entailment morphisms (read from `entailmentRegistry`).
Output format:
Registered contracts (N):
• <Name1>
• <Name2>
...
Entailment morphisms (M):
• <FromName> → <ToName> (via <LemmaName>)
...
Used for human exploration of the contract registry's current
state. No side effects — pure read of the registry. -/
syntax "#contract" : command
elab_rules : command
| `(command| #contract) => do
let registered ← Contract.allRegistered
let mut msg : MessageData := m!"Registered contracts ({registered.length}):"
if registered.isEmpty then
msg := msg ++ m!"\n (none — call `Contract.register` in an `initialize` block to register one)"
else
for n in registered do
msg := msg ++ m!"\n • {n}"
msg := msg ++ m!"\n\nEntailment morphisms ({entailmentRegistry.length}):"
if entailmentRegistry.isEmpty then
msg := msg ++ m!"\n (none)"
else
for morphism in entailmentRegistry do
msg := msg ++ m!"\n • {morphism.fromContract} → {morphism.toContract} (via {morphism.lemmaName})"
logInfo msg
-- ── §9. #whichContract command ───────────────────────────────────────────
/-- For `#whichContract`: given a contract name and a CType
expression, attempt synthesis of the contract's satisfaction
on the CType. Returns `true` if a witness can be constructed,
`false` otherwise.
Currently a structural test: applies the contract function to
the CType and checks that the application typechecks (the
Reflect-registered contract entry has a level `e.level` and a
function `e.contract : CType e.level → CTerm`). Since the
contract function is just a Lean-level pure function, the
application succeeds iff the CType is at the right level.
A stronger test (typed-satisfaction in the empty context)
requires the engine's HasType-checker, which lives outside
this module's scope. This implementation is intentionally a
structural filter, suitable for `#whichContract`'s "list
candidate contracts" purpose. -/
def attemptSynthesis (contractName : Lean.Name)
(TE : Expr) : MetaM Bool := do
-- Look up the contract entry.
let entry? ← Contract.lookupByName contractName
match entry? with
| none =>
-- Unknown contract — synthesis cannot succeed.
return false
| some _entry =>
-- Structural test: try to apply the contract's defining Lean
-- constant to the CType expression. If this elaborates
-- without error, the contract is structurally applicable.
let cExpr ← mkConstWithFreshMVarLevels contractName
let appExpr := mkApp cExpr TE
try
-- `inferType` will succeed iff the application is
-- well-typed, i.e. the contract's CType-level matches the
-- input's level.
let _ ← inferType appExpr
return true
catch _ =>
return false
/-- The `#whichContract <CType>` command. Given a CType
expression, lists the registered contracts that apply to it
(per `attemptSynthesis`).
Output format:
<CType expression> satisfies (K of N contracts):
• <Name1>
• <Name2>
...
or, if no contracts apply:
No registered contract is satisfied by <CType expression>.
Used to discover what contracts a CType participates in. Pure
read of the registry plus a structural per-contract test. -/
syntax "#whichContract" term : command
elab_rules : command
| `(command| #whichContract $T:term) => do
-- Elaborate the CType expression in the command context.
-- Use `liftTermElabM` to bridge from `CommandElabM` to
-- `TermElabM`.
let TE ← liftTermElabM do
Term.elabTerm T none
-- Run the synthesis attempt for each registered contract.
let registered ← Contract.allRegistered
let mut satisfied : List Lean.Name := []
for n in registered do
let ok ← liftTermElabM do
attemptSynthesis n TE
if ok then
satisfied := satisfied ++ [n]
let TEStr ← liftTermElabM do
let fmt ← Lean.Meta.ppExpr TE
return fmt.pretty
if satisfied.isEmpty then
logInfo m!"No registered contract is satisfied by {TEStr}."
else
let mut msg : MessageData :=
m!"{TEStr} satisfies ({satisfied.length} of {registered.length} contracts):"
for n in satisfied do
msg := msg ++ m!"\n • {n}"
logInfo msg
end CubicalTransport.Tactic.EqContract