{- Byzantine Fault Tolerant Consensus Verification in Agda, version 0.9.

   Copyright (c) 2020 Oracle and/or its affiliates.
   Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.oracle.com/licenses/upl
-}
open import Optics.All
open import LibraBFT.Prelude
open import LibraBFT.Hash
open import LibraBFT.Lemmas
open import LibraBFT.Base.KVMap
open import LibraBFT.Base.PKCS

open import LibraBFT.Abstract.Types
open EpochConfig

open import LibraBFT.Impl.NetworkMsg
open import LibraBFT.Impl.Consensus.Types
open import LibraBFT.Impl.Util.Crypto
open import LibraBFT.Impl.Handle sha256 sha256-cr

open import LibraBFT.Concrete.System.Parameters

open import LibraBFT.Yasm.Base
open import LibraBFT.Yasm.AvailableEpochs using (AvailableEpochs ; lookup'; lookup'')
open import LibraBFT.Yasm.System     ConcSysParms
open import LibraBFT.Yasm.Properties ConcSysParms

-- In this module, we define two "implementation obligations"
-- (ImplObligationᵢ for i ∈ {1 , 2}), which are predicates over
-- reachable states of a system defined by
-- 'LibraBFT.Concrete.System.Parameters'.  These two properties relate
-- votes sent by the same sender, ensuring that if they are for the
-- same epoch and round, then they vote for the same blockID; the
-- first relates a vote output by the handler to a vote sent
-- previously, and the second relates two votes both sent by the
-- handler.
--
-- We then prove that, if an implementation satisfies these two
-- semantic obligations, along with a structural one about messages
-- sent by honest peers in the implementation, then the implemenation
-- satisfies the LibraBFT.Abstract.Properties.VotesOnce invariant.
module LibraBFT.Concrete.Properties.VotesOnce where

 -- TODO-3: This may not be the best way to state the implementation obligation.  Why not reduce
 -- this as much as possible before giving the obligation to the implementation?  For example, this
 -- will still require the implementation to deal with hash collisons (v and v' could be different,
 -- but yield the same bytestring and therefore same signature).  Also, avoid the need for the
 -- implementation to reason about messages sent by step-cheat, or give it something to make this
 -- case easy to eliminate.

 ImplObligation₁ : Set₁
 ImplObligation₁ =
   ∀{e pid sndr s' outs pk}{pre : SystemState e}
   → ReachableSystemState pre
   -- For any honest call to /handle/ or /init/,
   → StepPeerState pid (availEpochs pre) (msgPool pre) (Map-lookup pid (peerStates pre)) s' outs
   → ∀{v m v' m'} → Meta-Honest-PK pk
   -- For signed every vote v of every outputted message
   → v  ⊂Msg m  → m ∈ outs → (sig : WithVerSig pk v)
   -- If v is really new and valid
     -- Note that this does not directly exclude possibility of previous message with
     -- same signature, but sent by someone else.  We could prove it implies it though.
   → ¬ (MsgWithSig∈ pk (ver-signature sig) (msgPool pre)) → ValidPartForPK (availEpochs pre) v pk
   -- And if there exists another v' that has been sent before
   → v' ⊂Msg m' → (sndr , m') ∈ (msgPool pre) → WithVerSig pk v'
   -- If v and v' share the same epoch and round
   → (v ^∙ vEpoch) ≡ (v' ^∙ vEpoch)
   → (v ^∙ vProposed ∙ biRound) ≡ (v' ^∙ vProposed ∙ biRound)
   ----------------------------------------------------------
   -- Then an honest implemenation promises v and v' vote for the same blockId.
   → (v ^∙ vProposed ∙ biId) ≡ (v' ^∙ vProposed ∙ biId)

 ImplObligation₂ : Set₁
 ImplObligation₂ =
   ∀{e pid s' outs pk}{pre : SystemState e}
   → ReachableSystemState pre
   -- For any honest call to /handle/ or /init/,
   → StepPeerState pid (availEpochs pre) (msgPool pre) (Map-lookup pid (peerStates pre)) s' outs
   → ∀{v m v' m'} → Meta-Honest-PK pk
   -- For every vote v represented in a message output by the call
   → v  ⊂Msg m  → m ∈ outs → (sig : WithVerSig pk v)
   -- If v is really new and valid
   → ¬ (MsgWithSig∈ pk (ver-signature sig) (msgPool pre)) → ValidPartForPK (availEpochs pre) v pk

   -- And if there exists another v' that is also new and valid
   → v' ⊂Msg m'  → m' ∈ outs → (sig' : WithVerSig pk v')
   → ¬ (MsgWithSig∈ pk (ver-signature sig') (msgPool pre)) → ValidPartForPK (availEpochs pre) v' pk

   -- If v and v' share the same epoch and round
   → (v ^∙ vEpoch) ≡ (v' ^∙ vEpoch)
   → (v ^∙ vProposed ∙ biRound) ≡ (v' ^∙ vProposed ∙ biRound)
   ----------------------------------------------------------
   -- Then, an honest implemenation promises v and v' vote for the same blockId.
   → (v ^∙ vProposed ∙ biId) ≡ (v' ^∙ vProposed ∙ biId)

 -- Next, we prove that, given the necessary obligations,
 module Proof
   (sps-corr : StepPeerState-AllValidParts)
   (Impl-VO1 : ImplObligation₁)
   (Impl-VO2 : ImplObligation₂)
   where

  -- Any reachable state satisfies the VO rule for any epoch in the system.
  module _ {e}(st : SystemState e)(r : ReachableSystemState st)(eid : Fin e) where
   -- Bring in 'unwind', 'ext-unforgeability' and friends
   open Structural sps-corr

   -- Bring in ConcSystemState
   open import LibraBFT.Concrete.System sps-corr
   open PerState st r
   open PerEpoch eid

   open import LibraBFT.Abstract.Obligations.VotesOnce 𝓔 Hash _≟Hash_ (ConcreteVoteEvidence 𝓔) as VO

   -- The VO proof is done by induction on the execution trace leading to 'st'. In
   -- Agda, this is 'r : RechableSystemState st' above. We will use induction to
   -- construct a predicate Pred'' below, which holds for every state on the trace.

   private
    -- First we specify the predicate we need: it relates two votes verified
    -- by the same public key, such that both are elements of the same message pool
    Pred'' : PK → Vote → Vote → SentMessages → Set
    Pred'' pk v v' pool
      = Meta-Honest-PK pk
      → (ver  : WithVerSig pk v)  → MsgWithSig∈ pk (ver-signature ver) pool
      → (ver' : WithVerSig pk v') → MsgWithSig∈ pk (ver-signature ver') pool
      → v ^∙ vEpoch ≡ v' ^∙ vEpoch
      → v ^∙ vRound ≡ v' ^∙ vRound
      → v ^∙ vProposedId ≡ v' ^∙ vProposedId

    -- Usually, we want to universally quantify Pred'' over arbitrary votes and pks
    Pred' : SentMessages → Set
    Pred' pool = ∀{pk}{v v' : Vote} → Pred'' pk v v' pool

    -- Finally, we state Pred' in terms of SystemSate
    Pred : ∀{e} → SystemState e → Set
    Pred = Pred' ∘ msgPool

    -------------------
    -- * Base Case * --
    -------------------

    -- Pred above is trivially true for the initial state: there are no messages in the pool
    Pred₀ : Pred initialState
    Pred₀ _ _ ()

    --------------------------------------------------
    -- * Inductive Case: New Epochs in the System * --
    --------------------------------------------------

    -- Because pushEpoch does not alter the msgPool, the proof is trivial.
    Pred𝓔 : ∀{e}{st : SystemState e}(𝓔 : EpochConfigFor e) → Pred st → Pred (pushEpoch 𝓔 st)
    Pred𝓔 𝓔 p = p

    ----------------------------------------------
    -- * Inductive Case: Transition by a Peer * --
    ----------------------------------------------

    -- From this point onwards, it might be easier to read this proof starting at 'voo'
    -- at the end of the file. Next, we provide an overview the proof.
    --
    -- We wish to prove that, for any two votes v and v' cast by an honest α in the message pool of
    -- a state st, if v and v' have equal rounds and epochs, then they vote for the same block. As
    -- we have seen above, the base case and the case for a new epoch in the system are
    -- trivial. Next, we look at the PeerStep case.
    --
    -- The induction hypothesis tells us that the property holds in the pre-state.  Next, we reason
    -- about the post-state.  We start by analyzing whether v and v' have been sent as outputs of
    -- the PeerStep under scrutiny or were already in the pool before (captured by the PredStep
    -- function).  There are four possibilities:
    --
    --   i) v and v' were aleady present in the msgPool before: use induction hypothesis.
    --  ii) v and v' are both in the output produced by the PeerStep under scrutiny.
    -- iii) v was present before, but v' is new.
    --  iv) v' was present before, but v is new.
    --
    -- Case (i) is trivial; cases (iii) and (iv) are symmetric and reduce to an implementation
    -- obligation (Impl-VO1) and case (ii) reduces to a different implementation obligation (Impl-VO2).
    --
    -- The proofs of cases (iii) and (iv) are in PredStep-wlog-ht and PredStep-wlog-ht'.  The 'ht'
    -- suffix refers to 'Here-There' as in one vote is "here" and the other is old, or "there".  We
    -- first analyze whether the new vote is really new or a replay; sps-cor provides us this
    -- information.  If the new vote is, in fact, a replay of an old message, we have two old
    -- messages and can call the induction hypothesis. If it is really new, we must rely on the
    -- implementation obligation. But to do so, we must prove that the old vote was also sent by
    -- the same peer.  We can see that is the case by reasoning about PK-inj and IsValidEpochMember.
    --
    -- Finally, the proof of case (ii) also branches on whether either of the "new" votes
    -- are replays or are really new. In case at least one is a replay we fallback to cases (iii) and (iv)
    -- or just call the induction hypothesis when both are replays.
    -- When both votes are in fact new, we rely on Impl-VO2 to conclude.
    --
    -- In both PredSetp-wlog-ht and PredStep-wlog-hh, we must eliminate the possibility of
    -- either vote being produced by a cheat step. This is easy because we received
    -- a proof that the PK in question is honest, hence, it must be the case that a cheat
    -- step is at most replaying these votes, not producing them. Producing them would
    -- require the cheater to forge a signature. This is the purpose of the isCheat constraint.

    PredStep-wlog-ht' : ∀{e pid pid' s' outs pk}{pre : SystemState e}
            → ReachableSystemState pre
            → Pred pre
            → StepPeerState pid (availEpochs pre) (msgPool pre) (Map-lookup pid (peerStates pre)) s' outs
            → ∀{v m v' m'}
            → v  ⊂Msg m  → m ∈ outs
            → v' ⊂Msg m' → (pid' , m') ∈ msgPool pre
            → WithVerSig pk v → WithVerSig pk v' → Meta-Honest-PK pk
            → (v ^∙ vEpoch) ≡ (v' ^∙ vEpoch)
            → (v ^∙ vProposed ∙ biRound) ≡ (v' ^∙ vProposed ∙ biRound)
            → (v ^∙ vProposed ∙ biId) ≡ (v' ^∙ vProposed ∙ biId)
    PredStep-wlog-ht' {pre = pre} preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈pool ver ver' hpk eids≡ r≡
    -- (1) The first step is branching on whether 'v' above is a /new/ vote or not.
    -- (1.1) If it's new:
      with sps-corr preach hpk ps m∈outs v⊂m ver
    ...| inj₁ (vValid , vNew)
      with honestPartValid preach hpk v'⊂m' m'∈pool ver'
    ...| v'Old , vOldValid
      with sameHonestSig⇒sameVoteData hpk ver' (msgSigned v'Old) (sym (msgSameSig v'Old))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl = Impl-VO1 preach ps hpk v⊂m m∈outs ver vNew vValid
                       (msg⊆ v'Old) (msg∈pool v'Old)
                       (msgSigned v'Old) eids≡ r≡
    -- (1.1) If 'v' is not new, then there exists a msg sent with the
    -- same signature.
    PredStep-wlog-ht' preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈pool ver ver' hpk e≡ r≡
       | inj₂ vOld
      with honestPartValid preach hpk v'⊂m' m'∈pool ver'
    ...| sv' , _ = hip hpk ver vOld ver' sv' e≡ r≡

    -- Here we prove a modified version of Pred'' where we assume w.l.o.g that
    -- one vote is sent by "pstep" and another was present in the prestate.
    PredStep-wlog-ht
      : ∀{e pid st' outs}{pre : SystemState e}
      → ReachableSystemState pre
      → (pstep : StepPeer pre pid st' outs)
      → Pred pre
      → ∀{pk v v'}
      -- Below is a inline expansion of "Pred'' pk v v' (msgPool (StepPeer-post pstep))",
      -- but with the added information that one vote (v) was sent by pstep whereas the
      -- other (v') was in the pool of the prestate.
      → let pool = msgPool (StepPeer-post pstep)
         in Meta-Honest-PK pk
          → (ver  : WithVerSig pk v )(sv  : MsgWithSig∈ pk (ver-signature ver ) pool)
          → (msgSender sv , msgWhole sv) ∈ List-map (pid ,_) outs
          → (ver' : WithVerSig pk v')(sv' : MsgWithSig∈ pk (ver-signature ver') pool)
          → (msgSender sv' , msgWhole sv') ∈ msgPool pre
          → v ^∙ vEpoch ≡ v' ^∙ vEpoch
          → v ^∙ vRound ≡ v' ^∙ vRound
          → v ^∙ vProposedId ≡ v' ^∙ vProposedId
    PredStep-wlog-ht preach (step-cheat fm isCheat) hip hpk ver sv (here refl) ver' sv' furtherBack' epoch≡ r≡
      with isCheat (msg⊆ sv) (msgSigned sv)
    ...| inj₁ abs    = ⊥-elim (hpk abs) -- The key was honest by hypothesis.
    ...| inj₂ sentb4
       -- the cheater replayed the message; which means the message was sent before this
       -- step; hence, call induction hypothesis.
      with msgSameSig sv
    ...| refl = hip hpk ver sentb4 ver' (MsgWithSig∈-transp sv' furtherBack') epoch≡ r≡
    PredStep-wlog-ht preach (step-honest x) hip hpk ver sv thisStep ver' sv' furtherBack' epoch≡ r≡
      with sameHonestSig⇒sameVoteData hpk ver (msgSigned sv) (sym (msgSameSig sv))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
      with sameHonestSig⇒sameVoteData hpk ver' (msgSigned sv') (sym (msgSameSig sv'))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
       = PredStep-wlog-ht' preach hip x
                 (msg⊆ sv) (Any-map (cong proj₂) (Any-map⁻ thisStep))
                 (msg⊆ sv') furtherBack'
                 (msgSigned sv) (msgSigned sv') hpk epoch≡ r≡

    -- Analogous to PredStep-wlog-ht', but here we must reason about two messages that are in the
    -- outputs of a step.
    PredStep-hh' : ∀{e pid s' outs pk}{pre : SystemState e}
            → ReachableSystemState pre → Pred pre
            → StepPeerState pid (availEpochs pre) (msgPool pre) (Map-lookup pid (peerStates pre)) s' outs
            → ∀{v m v' m'}
            → v  ⊂Msg m  → m  ∈ outs
            → v' ⊂Msg m' → m' ∈ outs
            → WithVerSig pk v → WithVerSig pk v' → Meta-Honest-PK pk
            → (v ^∙ vEpoch) ≡ (v' ^∙ vEpoch)
            → (v ^∙ vProposed ∙ biRound) ≡ (v' ^∙ vProposed ∙ biRound)
            → (v ^∙ vProposed ∙ biId) ≡ (v' ^∙ vProposed ∙ biId)
    -- Since the step is from an honest peer, we can check whether the messages are in fact
    -- new or not.
    PredStep-hh' preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈outs ver ver' hpk e≡ r≡
      with sps-corr preach hpk ps m∈outs v⊂m ver | sps-corr preach hpk ps m'∈outs v'⊂m' ver'
    -- (A) Both are old: call induction hypothesis
    ...| inj₂ vOld            | inj₂ v'Old = hip hpk ver vOld ver' v'Old e≡ r≡

    -- (B) One is new, one is old: use PredStep-wlog-ht'
    PredStep-hh' preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈outs ver ver' hpk e≡ r≡
       | inj₁ (vValid , vNew) | inj₂ v'Old
      with sameHonestSig⇒sameVoteData hpk ver' (msgSigned v'Old) (sym (msgSameSig v'Old))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
       = PredStep-wlog-ht' preach hip ps v⊂m m∈outs (msg⊆ v'Old) (msg∈pool v'Old) ver (msgSigned v'Old) hpk e≡ r≡

    -- (C) One is old, one is new: use PredStep-wlog-ht'
    PredStep-hh' preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈outs ver ver' hpk e≡ r≡
       | inj₂ vOld            | inj₁ (v'Valid , v'New)
      with sameHonestSig⇒sameVoteData hpk ver (msgSigned vOld) (sym (msgSameSig vOld))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
       = sym (PredStep-wlog-ht' preach hip ps v'⊂m' m'∈outs (msg⊆ vOld) (msg∈pool vOld) ver' (msgSigned vOld) hpk (sym e≡) (sym r≡))

    -- (D) Finally, both votes are new in this step. The proof is then trivially
    -- forwarded to the implementation obligation.
    PredStep-hh' preach hip ps {v} v⊂m m∈outs v'⊂m' m'∈outs ver ver' hpk e≡ r≡
       | inj₁ (vValid , vNew) | inj₁ (v'Valid , v'New)
       = Impl-VO2 preach ps hpk v⊂m m∈outs ver vNew vValid v'⊂m' m'∈outs ver' v'New v'Valid e≡ r≡

    PredStep-hh
      : ∀{e pid st' outs}{pre : SystemState e}
      → ReachableSystemState pre
      → (pstep : StepPeer pre pid st' outs)
      → Pred pre
      → ∀{pk v v'}
      → let pool = msgPool (StepPeer-post pstep)
         in Meta-Honest-PK pk
          → (ver  : WithVerSig pk v )(sv  : MsgWithSig∈ pk (ver-signature ver ) pool)
          → (msgSender sv  , msgWhole sv)  ∈ List-map (pid ,_) outs
          → (ver' : WithVerSig pk v')(sv' : MsgWithSig∈ pk (ver-signature ver') pool)
          → (msgSender sv' , msgWhole sv') ∈ List-map (pid ,_) outs
          → v ^∙ vEpoch ≡ v' ^∙ vEpoch
          → v ^∙ vRound ≡ v' ^∙ vRound
          → v ^∙ vProposedId ≡ v' ^∙ vProposedId
    PredStep-hh preach (step-cheat fm isCheat) hip hpk ver sv (here refl) ver' sv' (here refl) epoch≡ r≡
      with isCheat (msg⊆ sv) (msgSigned sv)
    ...| inj₁ abs    = ⊥-elim (hpk abs) -- The key was honest by hypothesis.
    ...| inj₂ sentb4
      with isCheat (msg⊆ sv') (msgSigned sv')
    ...| inj₁ abs     = ⊥-elim (hpk abs) -- The key was honest by hypothesis.
    ...| inj₂ sentb4'
      with msgSameSig sv | msgSameSig sv'
    ...| refl | refl = hip hpk ver sentb4 ver' sentb4' epoch≡ r≡
    PredStep-hh preach (step-honest x) hip hpk ver sv thisStep ver' sv' thisStep' epoch≡ r≡
      with sameHonestSig⇒sameVoteData hpk ver (msgSigned sv) (sym (msgSameSig sv))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
      with sameHonestSig⇒sameVoteData hpk ver' (msgSigned sv') (sym (msgSameSig sv'))
    ...| inj₁ abs  = ⊥-elim (meta-sha256-cr abs)
    ...| inj₂ refl
       = PredStep-hh' preach hip x
                 (msg⊆ sv ) (Any-map (cong proj₂) (Any-map⁻ thisStep))
                 (msg⊆ sv') (Any-map (cong proj₂) (Any-map⁻ thisStep'))
                 (msgSigned sv) (msgSigned sv') hpk epoch≡ r≡

    PredStep : ∀{e pid st' outs}{pre : SystemState e}
             → ReachableSystemState pre
             → (pstep : StepPeer pre pid st' outs)
             → Pred pre
             → Pred (StepPeer-post pstep)
    PredStep {e} {pid} {st'} {outs} {pre} preach pstep hip hpk ver sv ver' sv' epoch≡ r≡
    -- First we check when have the votes been sent:
      with Any-++⁻ (List-map (pid ,_) outs) {msgPool pre} (msg∈pool sv)
         | Any-++⁻ (List-map (pid ,_) outs) {msgPool pre} (msg∈pool sv')
    -- (A) Neither vote has been sent by the step under scrutiny: invoke inductive hypothesis
    ...| inj₂ furtherBack | inj₂ furtherBack'
       = hip hpk ver  (MsgWithSig∈-transp sv furtherBack)
                 ver' (MsgWithSig∈-transp sv' furtherBack') epoch≡ r≡
    -- (B) One vote was cast here; the other was cast in the past.
    PredStep {e} {pid} {st'} {outs} {pre} preach pstep hip hpk ver sv ver' sv' epoch≡ r≡
       | inj₁ thisStep    | inj₂ furtherBack'
       = PredStep-wlog-ht preach pstep hip hpk ver sv thisStep ver' sv' furtherBack' epoch≡ r≡
    -- (C) Symmetric to (B)
    PredStep {e} {pid} {st'} {outs} {pre} preach pstep hip hpk ver sv ver' sv' epoch≡ r≡
       | inj₂ furtherBack | inj₁ thisStep'
       = sym (PredStep-wlog-ht preach pstep hip hpk ver' sv' thisStep' ver sv furtherBack (sym epoch≡) (sym r≡))
    -- (D) Both votes were cast here
    PredStep {e} {pid} {st'} {outs} {pre} preach pstep hip hpk ver sv ver' sv' epoch≡ r≡
       | inj₁ thisStep    | inj₁ thisStep'
       = PredStep-hh preach pstep hip hpk ver sv thisStep ver' sv' thisStep' epoch≡ r≡

   voo : VO.Type ConcSystemState
   voo hpk refl sv refl sv' round≡
     with Step*-Step-fold Pred (λ {e} {st} _ → Pred𝓔 {e} {st}) PredStep Pred₀ r
   ...| res
     with vmsg≈v (vmFor sv) | vmsg≈v (vmFor sv')
   ...| refl | refl
      = res hpk (vmsgSigned (vmFor sv))
                (mkMsgWithSig∈ (nm (vmFor sv)) (cv (vmFor sv)) (cv∈nm (vmFor sv))
                               _ (nmSentByAuth sv) (vmsgSigned (vmFor sv)) refl)
                (vmsgSigned (vmFor sv'))
                (mkMsgWithSig∈ (nm (vmFor sv')) (cv (vmFor sv')) (cv∈nm (vmFor sv'))
                               _ (nmSentByAuth sv') (vmsgSigned (vmFor sv')) refl)
                (trans (vmsgEpoch (vmFor sv)) (sym (vmsgEpoch (vmFor sv'))))
                round≡


-- MIT License

-- Copyright (c) 2021 Luca Ciccone and Luca Padovani

-- Permission is hereby granted, free of charge, to any person
-- obtaining a copy of this software and associated documentation
-- files (the "Software"), to deal in the Software without
-- restriction, including without limitation the rights to use,
-- copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following
-- conditions:

-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.

-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-- OTHER DEALINGS IN THE SOFTWARE.

{-# OPTIONS --guardedness #-}

open import Data.Empty
open import Data.Product
open import Data.List using ([]; _∷_; _++_)

open import Relation.Nullary
open import Relation.Unary using (_⊆_)
open import Relation.Nullary.Negation using (contraposition)

open import Common

module TraceInclusion {ℙ : Set} (message : Message ℙ)
  where

open import Trace message
open import SessionType message
open import Transitions message
open import Session message
open import HasTrace message
open import TraceSet message

TraceInclusionS : SessionType -> SessionType -> Set
TraceInclusionS T S = ⟦ T ⟧ ⊆ ⟦ S ⟧

TraceExclusionS : SessionType -> SessionType -> Set
TraceExclusionS T S = ∃[ φ ] (T HasTrace φ × ¬ S HasTrace φ)

nil<=any : ∀{T} -> TraceInclusionS nil T
nil<=any (_ , () , refl)
nil<=any (_ , _ , step () _)

end<=def : ∀{T S} -> End T -> Defined S -> TraceInclusionS T S
end<=def e def (_ , tdef , refl) = _ , def , refl
end<=def (inp U) _ (_ , tdef , step inp tr) = ⊥-elim (U _ (transitions+defined->defined tr tdef))
end<=def (out U) _ (_ , tdef , step (out !x) _) = ⊥-elim (U _ !x)

inclusion-preserves-success : ∀{T S} -> TraceInclusionS T S -> ImplyS MaySucceed T S
inclusion-preserves-success spec ((R' # T') , reds , win#def w def) =
  let as , rr , tr = unzip-red* reds in
  let (S' , def' , sr) = spec (_ , def , tr) in
  _ , zip-red* rr sr , win#def w def'

input-excluded-trace :
  ∀{f : Continuation}{x as}
  (ntr : ¬ inp f HasTrace (I x ∷ as)) ->
  ¬ f x .force HasTrace as
input-excluded-trace ntr (_ , def , tr) = ntr (_ , def , step inp tr)

output-excluded-trace :
  ∀{f x as}
  (ntr : ¬ out f HasTrace (O x ∷ as)) ->
  ¬ f x .force HasTrace as
output-excluded-trace {f} {x} ntr (_ , def , tr) with x ∈? f
... | yes fx = ntr (_ , def , step (out fx) tr)
output-excluded-trace {_} {_} _ (_ , def , refl) | no nfx = ⊥-elim (nfx def)
output-excluded-trace {_} {_} _ (_ , _ , step t _) | no nfx = ⊥-elim (nfx (transition->defined t))

has-trace-input : ∀{f x as} -> inp f HasTrace (I x ∷ as) -> f x .force HasTrace as
has-trace-input (_ , def , step inp tr) = _ , def , tr

has-trace-output : ∀{f x as} -> out f HasTrace (O x ∷ as) -> f x .force HasTrace as
has-trace-output (_ , def , step (out _) tr) = _ , def , tr


open import Mockingbird.Forest using (Forest)

-- Craig’s Discovery
module Mockingbird.Problems.Chapter20 {ℓb ℓ} (forest : Forest {ℓb} {ℓ}) where

open import Data.Product using (_,_)
open import Data.Vec using ([]; _∷_)
open import Function using (_$_)
open import Relation.Unary using (Pred; _∈_; _⊆_; _⊇_)

open Forest forest
open import Mockingbird.Forest.Birds forest
open import Mockingbird.Forest.Combination.Vec forest
open import Mockingbird.Forest.Combination.Vec.Properties forest
open import Mockingbird.Forest.Extensionality forest
import Mockingbird.Problems.Chapter11 forest as Chapter₁₁
open import Mockingbird.Problems.Chapter19 forest using (_≐_)

problem₁ : ⦃ _ : HasGoldfinch ⦄ ⦃ _ : HasIdentity ⦄ → HasQuirkyBird
problem₁ = record
  { Q₃ = G ∙ I
  ; isQuirkyBird = λ x y z → begin
      G ∙ I ∙ x ∙ y ∙ z  ≈⟨ isGoldfinch I x y z ⟩
      I ∙ z ∙ (x ∙ y)    ≈⟨ congʳ $ isIdentity z ⟩
      z ∙ (x ∙ y)        ∎
  }

problem₂′ : ⦃ _ : HasQuirkyBird ⦄ ⦃ _ : HasIdentity ⦄ → HasThrush
problem₂′ = record
  { T = Q₃ ∙ I
  ; isThrush = λ x y → begin
      Q₃ ∙ I ∙ x ∙ y  ≈⟨ isQuirkyBird I x y ⟩
      y ∙ (I ∙ x)     ≈⟨ congˡ $ isIdentity x ⟩
      y ∙ x           ∎
  }

problem₂ : ⦃ _ : HasGoldfinch ⦄ ⦃ _ : HasIdentity ⦄ → HasThrush
problem₂ = record
  { T = G ∙ I ∙ I
  ; isThrush = isThrush ⦃ problem₂′ ⦄
  } where
  instance hasQuirkyBird = problem₁

problem₃ : ⦃ _ : HasGoldfinch ⦄ ⦃ _ : HasIdentity ⦄ → HasCardinal
problem₃ = record
  { C = G ∙ G ∙ I ∙ I
  ; isCardinal = λ x y z → begin
      G ∙ G ∙ I ∙ I ∙ x ∙ y ∙ z  ≈⟨ congʳ $ congʳ $ isGoldfinch G I I x ⟩
      G ∙ x ∙ (I ∙ I) ∙ y ∙ z    ≈⟨ isGoldfinch x (I ∙ I) y z ⟩
      x ∙ z ∙ (I ∙ I ∙ y)        ≈⟨ congˡ $ congʳ $ isIdentity I ⟩
      x ∙ z ∙ (I ∙ y)            ≈⟨ congˡ $ isIdentity y ⟩
      x ∙ z ∙ y                  ∎
  }

problem₄-Q : ⦃ _ : HasGoldfinch ⦄ ⦃ _ : HasIdentity ⦄ → HasQueerBird
problem₄-Q = record
  { Q = G ∙ R ∙ Q₃
  ; isQueerBird = λ x y z → begin
      G ∙ R ∙ Q₃ ∙ x ∙ y ∙ z  ≈⟨ congʳ $ isGoldfinch R Q₃ x y ⟩
      R ∙ y ∙ (Q₃ ∙ x) ∙ z    ≈⟨ isRobin y (Q₃ ∙ x) z ⟩
      Q₃ ∙ x ∙ z ∙ y          ≈⟨ isQuirkyBird x z y ⟩
      y ∙ (x ∙ z)             ∎
  } where
  instance
    hasQuirkyBird = problem₁
    hasCardinal = problem₃
    hasRobin = Chapter₁₁.problem₂₃

problem₄-B : ⦃ _ : HasGoldfinch ⦄ ⦃ _ : HasIdentity ⦄ → HasBluebird
problem₄-B = record
  { B = C ∙ Q
  ; isBluebird = λ x y z → begin
      C ∙ Q ∙ x ∙ y ∙ z  ≈⟨ congʳ $ isCardinal Q x y ⟩
      Q ∙ y ∙ x ∙ z      ≈⟨ isQueerBird y x z ⟩
      x ∙ (y ∙ z)        ∎
  } where
  instance
    hasCardinal = problem₃
    hasQueerBird = problem₄-Q

module _ ⦃ _ : Extensional ⦄
         ⦃ _ : HasBluebird ⦄ ⦃ _ : HasThrush ⦄
         ⦃ _ : HasIdentity ⦄ ⦃ _ : HasGoldfinch ⦄ where
  ⟨B,T,I⟩ = ⟨ B ∷ T ∷ I ∷ [] ⟩
  ⟨G,I⟩ = ⟨ G ∷ I ∷ [] ⟩

  module _ where
    private
      instance
        hasCardinal = Chapter₁₁.problem₂₁-bonus
        hasDove = Chapter₁₁.problem₅

      hasGoldfinch = Chapter₁₁.problem₄₇

      b : B ∈ ⟨B,T,I⟩
      b = [ here refl ]

      t : T ∈ ⟨B,T,I⟩
      t = [ there (here refl) ]

      i : I ∈ ⟨B,T,I⟩
      i = [ there (there (here refl)) ]

      c : C ∈ ⟨B,T,I⟩
      c = b ⟨∙⟩ (t ⟨∙⟩ (b ⟨∙⟩ b ⟨∙⟩ t)) ⟨∙⟩ (b ⟨∙⟩ b ⟨∙⟩ t)

      d : D ∈ ⟨B,T,I⟩
      d = b ⟨∙⟩ b

      g : G ∈ ⟨B,T,I⟩
      g = subst′
        (ext′ $ ext′ $ ext′ $ ext′ $ trans
          (isGoldfinch _ _ _ _)
          (sym $ isGoldfinch ⦃ hasGoldfinch ⦄ _ _ _ _))
        (d ⟨∙⟩ c)

    ⟨G,I⟩⊆⟨B,T,I⟩ : ⟨G,I⟩ ⊆ ⟨B,T,I⟩
    ⟨G,I⟩⊆⟨B,T,I⟩ [ here x≈G ] = subst′ x≈G g
    ⟨G,I⟩⊆⟨B,T,I⟩ [ there (here x≈I) ] = [ there (there (here x≈I)) ]
    ⟨G,I⟩⊆⟨B,T,I⟩ (x∈⟨G,I⟩ ⟨∙⟩ y∈⟨G,I⟩ ∣ xy≈z) =
      ⟨G,I⟩⊆⟨B,T,I⟩ x∈⟨G,I⟩ ⟨∙⟩ ⟨G,I⟩⊆⟨B,T,I⟩ y∈⟨G,I⟩ ∣ xy≈z

  module _ where
    private
      g : G ∈ ⟨G,I⟩
      g = [ here refl ]

      i : I ∈ ⟨G,I⟩
      i = [ there (here refl) ]

      t : T ∈ ⟨G,I⟩
      t = subst′
        (ext′ $ ext′ $ trans
          (isThrush _ _)
          (sym $ isThrush ⦃ problem₂ ⦄ _ _))
        (g ⟨∙⟩ i ⟨∙⟩ i)

      b : B ∈ ⟨G,I⟩
      b = subst′
        (ext′ $ ext′ $ ext′ $ trans
          (isBluebird _ _ _)
          (sym $ isBluebird ⦃ problem₄-B ⦄ _ _ _))
        (g ⟨∙⟩ g ⟨∙⟩ i ⟨∙⟩ i ⟨∙⟩ (g ⟨∙⟩ (g ⟨∙⟩ g ⟨∙⟩ i ⟨∙⟩ i ⟨∙⟩ (g ⟨∙⟩ g ⟨∙⟩ i ⟨∙⟩ i)) ⟨∙⟩ (g ⟨∙⟩ i)))

    ⟨G,I⟩⊇⟨B,T,I⟩ : ⟨G,I⟩ ⊇ ⟨B,T,I⟩
    ⟨G,I⟩⊇⟨B,T,I⟩ [ here x≈B ] = subst′ x≈B b
    ⟨G,I⟩⊇⟨B,T,I⟩ [ there (here x≈T) ] = subst′ x≈T t
    ⟨G,I⟩⊇⟨B,T,I⟩ [ there (there (here x≈I)) ] = [ there (here x≈I) ]
    ⟨G,I⟩⊇⟨B,T,I⟩ (x∈⟨B,T,I⟩ ⟨∙⟩ y∈⟨B,T,I⟩ ∣ xy≈z) = ⟨G,I⟩⊇⟨B,T,I⟩ x∈⟨B,T,I⟩ ⟨∙⟩ ⟨G,I⟩⊇⟨B,T,I⟩ y∈⟨B,T,I⟩ ∣ xy≈z

  ⟨G,I⟩≐⟨B,T,I⟩ : ⟨ G ∷ I ∷ [] ⟩ ≐ ⟨ B ∷ T ∷ I ∷ [] ⟩
  ⟨G,I⟩≐⟨B,T,I⟩ = (⟨G,I⟩⊆⟨B,T,I⟩ , ⟨G,I⟩⊇⟨B,T,I⟩)


------------------------------------------------------------------------
-- η-expansion of simply-kinded types in Fω with interval kinds
------------------------------------------------------------------------

{-# OPTIONS --safe --without-K #-}

module FOmegaInt.Kinding.Simple.EtaExpansion where

open import Data.Fin using (Fin; zero; suc; raise; lift)
open import Data.Fin.Substitution
open import Data.Fin.Substitution.Lemmas
open import Data.Fin.Substitution.ExtraLemmas
open import Data.List using ([]; _∷_; _∷ʳ_)
open import Data.Maybe using (Maybe)
open import Data.Nat using (zero; suc)
open import Data.Product as Prod using (_,_)
open import Data.Vec as Vec using ([])
open import Function using (_∘_)
open import Relation.Binary.PropositionalEquality

open import FOmegaInt.Syntax
open import FOmegaInt.Syntax.SingleVariableSubstitution
open import FOmegaInt.Syntax.HereditarySubstitution
open import FOmegaInt.Syntax.Normalization
open import FOmegaInt.Syntax.WeakEquality
open import FOmegaInt.Kinding.Simple

------------------------------------------------------------------------
-- η-expansion of simply kinded types.
--
-- TODO: explain the point of this and how "simple" kinding differs
-- from "canonical" kinding.

open Syntax
open SimpleCtx
open Substitution hiding (_↑; _↑⋆_; sub; subst)
open SimpHSubstLemmas
open RenamingCommutes
open WeakHereditarySubstitutionEquality
open WeakEqEtaExpansion
open KindedRenaming
open KindedHereditarySubstitution
open Kinding

module TrackSimpleKindsKindedEtaExp where
  private
    module V  = VarSubst
    module TK = TrackSimpleKindsEtaExp

  -- NOTE. The definition of the functions η-exp-Var∈ and η-exp-Ne∈
  -- below are structurally recursive in the *shape* parameter k, but
  -- *not* in the kind j because we need to weaken the domain j₁ of
  -- the dependent kind (j = Π j₁ j₂) in the arrow case.  The
  -- additional hypothesis ⌊ j ⌋≡ k ensures that k is indeed the shape
  -- of the kind j.

  mutual

    -- η-expansion preserves simple kinding of neutral types.

    η-exp-Var∈ : ∀ {n} {Γ : Ctx n} {x j k} (hyp : ⌊ j ⌋≡ k) →
                 Γ ⊢ j kds → Γ ⊢Var x ∈ k →
                 Γ ⊢Nf TK.η-exp j hyp (var∙ x) ∈ k
    η-exp-Var∈ hyp j-kd x∈k = η-exp-Ne∈ hyp j-kd (∈-∙ x∈k ∈-[])

    η-exp-Ne∈ : ∀ {n} {Γ : Ctx n} {a j k} (hyp : ⌊ j ⌋≡ k) →
                Γ ⊢ j kds → Γ ⊢Ne a ∈ k → Γ ⊢Nf TK.η-exp j hyp a ∈ k
    η-exp-Ne∈ is-★ (kds-⋯ b∈* c∈*) a∈★ = ∈-ne a∈★
    η-exp-Ne∈ (is-⇒ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) (kds-Π j₁-kds j₂-kds)
              (∈-∙ {x} {_} {_} {as} x∈l l∋as∈k₁⇒k₂) =
      subst (λ j → _ ⊢Nf η-x∙as ∈ j ⇒ _) (⌊⌋≡⇒⌊⌋-≡ ⌊j₁⌋≡k₁)
            (∈-Π-i j₁-kds (η-exp-Ne∈ ⌊j₂⌋≡k₂ j₂-kds x∙as·z∈k₂))
      where
        η-x∙as = TK.η-exp _ (is-⇒ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) (var x ∙ as)
        z∈k₁ = η-exp-Var∈ (⌊⌋≡-weaken ⌊j₁⌋≡k₁) (kds-weaken j₁-kds)
                          (∈-var zero (cong kd (⌊⌋≡⇒⌊⌋-≡ ⌊j₁⌋≡k₁)))
        x∙as·z∈k₂ = Ne∈-Π-e (Ne∈-weaken (∈-∙ x∈l l∋as∈k₁⇒k₂)) z∈k₁

  mutual

    -- η-expansion of neutrals followed by hereditary substitution
    -- vanishes if the substitution hits the head of the neutral.

    η-exp-var-Hit-/⟨⟩ : ∀ {k m n Γ Δ} {x a j} (hyp : ⌊ j ⌋≡ k) {σ : SVSub m n} →
                       Hit σ x a → Γ ⊢ j kds → Γ ⊢Var x ∈ k → Δ ⊢/⟨ k ⟩ σ ∈ Γ →
                       TK.η-exp j hyp (var∙ x) /⟨ k ⟩ σ  ≈  var∙ x /⟨ k ⟩ σ
    η-exp-var-Hit-/⟨⟩ hyp hitP j-kds x∈k σ∈Γ =
      η-exp-ne-Hit-/⟨⟩ hyp hitP j-kds (∈-∙ x∈k ∈-[]) σ∈Γ

    η-exp-ne-Hit-/⟨⟩ : ∀ {k l m n Γ Δ} {x a as j} (hyp : ⌊ j ⌋≡ k)
                       {σ : SVSub m n} → Hit σ x a → Γ ⊢ j kds →
                       Γ ⊢Ne var x ∙ as ∈ k → Δ ⊢/⟨ l ⟩ σ ∈ Γ →
                       TK.η-exp j hyp (var x ∙ as) /⟨ l ⟩ σ  ≈
                         var x ∙ as /⟨ l ⟩ σ
    η-exp-ne-Hit-/⟨⟩ is-★ hitP j-kds a∈k σ∈Γ = ≈-refl _
    η-exp-ne-Hit-/⟨⟩ (is-⇒ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) hitP
                     (kds-Π j₁-kds j₂-kds) (∈-∙ {as = as} x∈l l∋as∈k₁⇒k₂) σ∈Γ
      with lookup-/⟨⟩∈-Hit σ∈Γ x∈l hitP
    η-exp-ne-Hit-/⟨⟩ {l = l} {Γ = Γ} {a = a}
                     (is-⇒ {j₁} {j₂} {k₁} {k₂} ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) {σ} hitP
                     (kds-Π j₁-kds j₂-kds)
                     (∈-∙ {as = as} (∈-var {l} x Γ[x]≡kd-l) l∋as∈k₁⇒k₂) σ∈Γ
                   | a∈l , refl =
      let hitP-σ↑ = hitP ↑
          ηz      = TK.η-exp (weakenKind′ j₁) (⌊⌋≡-weaken ⌊j₁⌋≡k₁) (var∙ zero)
          as′     = weakenSpine as
          ηz∈k₁   = η-exp-Var∈ (⌊⌋≡-weaken ⌊j₁⌋≡k₁) (kds-weaken j₁-kds)
                               (∈-var zero refl)
          kd-l∷Γ[x+1]≡kd-l = trans (lookup-suc (kd l) Γ x) Γ[x]≡kd-l
          x+1∈l            = ∈-var (suc x) kd-l∷Γ[x+1]≡kd-l
          l∋as∈k₁⇒k₂′      = Sp∈-weaken l∋as∈k₁⇒k₂
          σ↑∈k₁∷Γ          = ∈-H↑ σ∈Γ
          l∋as//σ∈k₁⇒k₂    = Sp∈-/⟨⟩ l∋as∈k₁⇒k₂ σ∈Γ
          a∙as/σ∈k₁⇒k₂     = Nf∈-∙∙ a∈l l∋as//σ∈k₁⇒k₂
          j′ , a′ , j′-kds , a′∈k₁⇒k₂ , rest = Nf∈-⇒-inv a∙as/σ∈k₁⇒k₂
          ⌊j′⌋≡k₁ , a∙as/σ≡Λj′a′             = rest
          ⌊j₁/σ⌋≡⌊j′⌋ = let open ≡-Reasoning in begin
            ⌊ j₁ Kind/⟨ l ⟩ σ ⌋  ≡⟨ ⌊⌋-Kind/⟨⟩ j₁ ⟩
            ⌊ j₁ ⌋               ≡⟨ ⌊⌋≡⇒⌊⌋-≡ ⌊j₁⌋≡k₁ ⟩
            k₁                   ≡⟨ sym ⌊j′⌋≡k₁ ⟩
            ⌊ j′ ⌋               ∎
          open ≈-Reasoning
      in begin
        TK.η-exp (Π j₁ j₂) (is-⇒ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) (var x ∙ as) /⟨ l ⟩ σ
      ≈⟨ ≈-Λ∙ ⌊j₁/σ⌋≡⌊j′⌋ (begin
          TK.η-exp j₂ ⌊j₂⌋≡k₂
            (weakenElim (var x ∙ as) ⌜·⌝ ηz) /⟨ l ⟩ σ ↑
        ≡⟨ cong (λ y → TK.η-exp _ ⌊j₂⌋≡k₂ (var y ∙ as′ ⌜·⌝ ηz) /⟨ l ⟩ σ ↑)
                (VarLemmas.lookup-wk x) ⟩
          TK.η-exp j₂ ⌊j₂⌋≡k₂ (var (suc x) ∙ as′ ⌜·⌝ ηz) /⟨ l ⟩ σ ↑
        ≈⟨ η-exp-ne-Hit-/⟨⟩ ⌊j₂⌋≡k₂ hitP-σ↑
                            (subst (λ k → kd k ∷ _ ⊢ _ kds) (⌊⌋≡⇒⌊⌋-≡ ⌊j₁⌋≡k₁)
                                   j₂-kds)
                            (∈-∙ x+1∈l (∈-∷ʳ l∋as∈k₁⇒k₂′ ηz∈k₁)) σ↑∈k₁∷Γ ⟩
          var (suc x) ∙ as′ ⌜·⌝ ηz /⟨ l ⟩ σ ↑
        ≡⟨ cong (_?∙∙⟨ l ⟩ ((as′ ∷ʳ ηz) //⟨ l ⟩ σ ↑)) (lookup-Hit hitP-σ↑) ⟩
          weakenElim a ∙∙⟨ l ⟩ ((as′ ∷ʳ ηz) //⟨ l ⟩ σ ↑)
        ≡⟨ cong (_ ∙∙⟨ l ⟩_) (++-//⟨⟩ (as′) (_ ∷ [])) ⟩
          weakenElim a ∙∙⟨ l ⟩ (as′ //⟨ l ⟩ σ ↑ ∷ʳ ηz /⟨ l ⟩ σ ↑)
        ≡⟨ Nf∈-++-∙∙⟨⟩ (Nf∈-weaken a∈l) (Sp∈-/⟨⟩ l∋as∈k₁⇒k₂′ σ↑∈k₁∷Γ)
                       (∈-∷ (Nf∈-/⟨⟩ ηz∈k₁ σ↑∈k₁∷Γ) ∈-[]) ⟩
          weakenElim a ∙∙⟨ l ⟩ (as′ //⟨ l ⟩ σ ↑) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ (ηz /⟨ l ⟩ σ ↑)
        ≡⟨ cong (λ bs → weakenElim a ∙∙⟨ l ⟩ bs ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ (ηz /⟨ l ⟩ σ ↑))
                (wk-//⟨⟩-↑⋆ 0 as) ⟩
          weakenElim a ∙∙⟨ l ⟩ weakenSpine (as //⟨ l ⟩ σ) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩
            (ηz /⟨ l ⟩ σ ↑)
        ≡˘⟨ cong (_⌜·⌝⟨ k₁ ⇒ k₂ ⟩ (ηz /⟨ l ⟩ σ ↑))
                 (∙∙⟨⟩-/Var a l (as //⟨ l ⟩ σ)) ⟩
          weakenElim (a ∙∙⟨ l ⟩ (as //⟨ l ⟩ σ)) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ (ηz /⟨ l ⟩ σ ↑)
        ≡⟨ cong ((_⌜·⌝⟨ k₁ ⇒ k₂ ⟩ _) ∘ weakenElim) a∙as/σ≡Λj′a′ ⟩
          weakenElim (Λ∙ j′ a′) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ (ηz /⟨ l ⟩ σ ↑)
        ≡⟨ cong (weakenElim (Λ∙ j′ a′) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩_)
                (TK.η-exp-ne-Miss-/⟨⟩ zero zero (⌊⌋≡-weaken ⌊j₁⌋≡k₁) under) ⟩
          weakenElim (Λ∙ j′ a′) ⌜·⌝⟨ k₁ ⇒ k₂ ⟩
            TK.η-exp (weakenKind′ j₁ Kind/⟨ l ⟩ σ ↑) (⌊⌋≡-/⟨⟩ (⌊⌋≡-weaken ⌊j₁⌋≡k₁))
                     (var∙ zero)
        ≈⟨ Nf∈-[]-η-var [] (⌊⌋≡-/⟨⟩ (⌊⌋≡-weaken ⌊j₁⌋≡k₁))
                        (kds-/⟨⟩ (kds-weaken j₁-kds) σ↑∈k₁∷Γ) a′∈k₁⇒k₂ ⟩
          a′
        ∎) ⟩
        Λ∙ j′ a′
      ≡⟨ sym a∙as/σ≡Λj′a′ ⟩
        a ∙∙⟨ l ⟩ (as //⟨ l ⟩ σ)
      ≡˘⟨ cong (_?∙∙⟨ l ⟩ (as //⟨ l ⟩ σ)) (lookup-Hit hitP) ⟩
        var x ∙ as /⟨ l ⟩ σ
      ∎

    -- Hereditary substitutions of a variable by its η-expansion vanish.
    --
    -- NOTE.  Both the statement and the proof of this lemma are
    -- complicated by the use of de Bruijn notation in combination
    -- with single-variable (hereditary) substitution.  When
    -- formulated using the usual, human-readable variable notation,
    -- the lemma simply says that
    --
    --   e [ηⱼ(x)/ʲx] = e
    --
    -- where "e [a/ʲx]" denotes hereditary substitution of a for x in
    -- expression e at kind j, and "ηⱼ(x)" denotes the η-expansion of
    -- x at kind j.  Using de Bruijn notation, this substitution only
    -- makes sense if there are actually _two_ copies of the variable
    -- x in e on the LHS (say x and x′): one to be replaced (x) and a
    -- second one (x′) to be η-expanded and substituted for x.  To
    -- make the equation well-scoped, we therefore weaken e to
    -- introduce a new variable x′ = (suc x) just after x, which after
    -- substitution for x will correspond precisely to the de Bruijn
    -- index of x itself.  The stated form of the lemma is obtained by
    -- letting x be the n-th variable in a context with (n + m) free
    -- variables, i.e. we fix x = (raise n zero) in e.
    --
    -- Note also that the proof of this lemma is currently the only
    -- place in the development where the Eq? predicate from the
    -- FOmegaInt.Syntax.SingleVariableSubstitution module is used.
    -- It's probably possible to reformulate the proof without using
    -- this predicate, but that will likely render the proof even
    -- harder to follow than it already is.

    kds-[]-η-var : ∀ {k m n} (Γ₂ : CtxExt (suc m) n) {Γ₁ j l}
                   (hyp : ⌊ j ⌋≡ k) → kd k ∷ Γ₁ ⊢ j kds →
                   Γ₂ ++ kd k ∷ Γ₁ ⊢ l kds →
                   let l′ = l Kind′/Var (V.wk V.↑) V.↑⋆ n
                   in l′ Kind/⟨ k ⟩ sub (TK.η-exp j hyp (var∙ zero)) ↑⋆ n ≋ l
    kds-[]-η-var Γ₂ hyp j-kds (kds-⋯ a∈★ b∈★) =
      ≋-⋯ (Nf∈-[]-η-var Γ₂ hyp j-kds a∈★) (Nf∈-[]-η-var Γ₂ hyp j-kds b∈★)
    kds-[]-η-var Γ₂ hyp j-kds (kds-Π k-kds l-kds) =
      ≋-Π (kds-[]-η-var Γ₂ hyp j-kds k-kds)
          (kds-[]-η-var (_ ∷ Γ₂) hyp j-kds l-kds)

    Nf∈-[]-η-var : ∀ {k m n} (Γ₂ : CtxExt (suc m) n) {Γ₁ a j l}
                   (hyp : ⌊ j ⌋≡ k) → kd k ∷ Γ₁ ⊢ j kds →
                   Γ₂ ++ kd k ∷ Γ₁ ⊢Nf a ∈ l →
                   let a′ = a Elim/Var (V.wk V.↑) V.↑⋆ n
                   in a′ /⟨ k ⟩ sub (TK.η-exp j hyp (var∙ zero)) ↑⋆ n ≈ a
    Nf∈-[]-η-var Γ₂ hyp j-kds ∈-⊥-f = ≈-refl ⊥∙
    Nf∈-[]-η-var Γ₂ hyp j-kds ∈-⊤-f = ≈-refl ⊤∙
    Nf∈-[]-η-var Γ₂ hyp j-kds (∈-∀-f k-kds a∈★) =
      ≈-∀∙ (kds-[]-η-var Γ₂ hyp j-kds k-kds)
           (Nf∈-[]-η-var (_ ∷ Γ₂) hyp j-kds a∈★)
    Nf∈-[]-η-var Γ₂ hyp j-kds (∈-→-f a∈★ b∈★) =
      ≈-⇒∙ (Nf∈-[]-η-var Γ₂ hyp j-kds a∈★) (Nf∈-[]-η-var Γ₂ hyp j-kds b∈★)
    Nf∈-[]-η-var {k} {_} {n} Γ₂ hyp j-kds (∈-Π-i {l} l-kds a∈k) =
      let open ≡-Reasoning
          ρ = (V.wk V.↑) V.↑⋆ n
          σ = sub (TK.η-exp _ hyp (var∙ zero)) ↑⋆ n
      in ≈-Λ∙ (begin
             ⌊ l Kind′/Var ρ Kind/⟨ k ⟩ σ ⌋  ≡⟨ ⌊⌋-Kind/⟨⟩ (l Kind′/Var ρ) ⟩
             ⌊ l Kind′/Var ρ ⌋               ≡⟨ ⌊⌋-Kind′/Var l ⟩
             ⌊ l ⌋                           ∎)
           (Nf∈-[]-η-var (_ ∷ Γ₂) hyp j-kds a∈k)
    Nf∈-[]-η-var {_} {_} {n} Γ₂ hyp j-kds (∈-ne (∈-∙ (∈-var x _) k∋as∈★))
      with compare n x
    Nf∈-[]-η-var {k} {_} {n} Γ₂ {Γ₁} {_} {j} hyp j-kds
                 (∈-ne (∈-∙ {as = as} (∈-var ._ Γ[x]≡kd-k′) k′∋as∈★))
      | yes refl =
      begin
        lookupSV σ (x V./ (V.wk V.↑) V.↑⋆ n) ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≡⟨ cong (λ z → lookupSV σ z ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ))
              (lookup-raise-↑⋆ n zero refl) ⟩
        lookupSV σ (raise n zero) ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≡⟨ cong (_?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)) (lookup-Hit hitP) ⟩
        weakenElim⋆ n ηz ∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≡˘⟨ cong (_∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)) (EL./Var-wk⋆ n) ⟩
        (ηz Elim/Var V.wk⋆ n) ∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≈⟨ ≈-∙∙⟨⟩ k (≈-refl _) (Sp∈-[]-η-var Γ₂ hyp j-kds k∋as∈★) ⟩
        (ηz Elim/Var V.wk⋆ n) ∙∙⟨ k ⟩ as
      ≡⟨ cong (_∙∙⟨ k ⟩ as) (TK.η-exp-/Var hyp (var∙ zero)) ⟩
        TK.η-exp (j Kind′/Var V.wk⋆ n) (⌊⌋≡-/Var hyp)
                 (var∙ zero Elim/Var V.wk⋆ n) ∙∙⟨ k ⟩ as
      ≡⟨ cong (λ y → TK.η-exp (j Kind′/Var V.wk⋆ n) (⌊⌋≡-/Var hyp)
                              (var∙ y) ∙∙⟨ k ⟩ as)
              (lookup-wk⋆ zero n) ⟩
        TK.η-exp (j Kind′/Var V.wk⋆ n) (⌊⌋≡-/Var hyp) (var∙ x) ∙∙⟨ k ⟩ as
      ≈⟨ η-exp-ne-∙∙ (⌊⌋≡-/Var hyp)
                     (subst (_ ⊢_kds) (sym (KL./Var-wk⋆ n))
                            (kds-weaken⋆ Γ₂ j-kds))
                     (∈-∙ (∈-var x Γ[x]≡kd-k) ∈-[]) k∋as∈★ ⟩
        var x ∙ as
      ∎
      where
        x = raise n zero
        Γ = Γ₂ ++ kd k ∷ Γ₁

        Γ[x]≡kd-k = begin
          lookup Γ (raise n zero)   ≡⟨ lookup-weaken⋆ n Γ₂ _ zero ⟩
          weakenSAsc⋆ n (kd k)      ≡⟨ weakenSAsc⋆-id n ⟩
          kd k                      ∎
          where open ≡-Reasoning

        ηz     = TK.η-exp j hyp (var∙ zero)
        σ      = sub ηz ↑⋆ n
        as′    = as //Var (V.wk V.↑) V.↑⋆ n
        hitP   = Hit-sub-↑⋆ n
        k′≡k   = kd-inj′ (trans (sym Γ[x]≡kd-k′) Γ[x]≡kd-k)
        k∋as∈★ = subst (_ ⊢_∋∙ _ ∈ _) k′≡k k′∋as∈★

        open ≈-Reasoning
        open ExtLemmas₁ VarLemmas.lemmas₁ using (lookup-wk⋆; lookup-raise-↑⋆)
        module EL = TermLikeLemmas termLikeLemmasElim
        module KL = TermLikeLemmas termLikeLemmasKind′
    Nf∈-[]-η-var {k} {_} {n} Γ₂ {_} {_} {j} hyp j-kds
                 (∈-ne (∈-∙ {as = as} (∈-var ._ Γ[x]≡kd-k′) k′∋as∈★))
      | no y refl =
      begin
        lookupSV σ (x V./ (V.wk V.↑) V.↑⋆ n) ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≡⟨ cong (λ z → lookupSV σ z ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)) eq ⟩
        lookupSV σ (lift n suc x) ?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)
      ≡⟨ cong (_?∙∙⟨ k ⟩ (as′ //⟨ k ⟩ σ)) (lookup-Miss (Miss-sub-↑⋆ n x)) ⟩
        var x ∙ (as′ //⟨ k ⟩ σ)
      ≈⟨ ≈-∙ (≈-var x) (Sp∈-[]-η-var Γ₂ hyp j-kds k′∋as∈★) ⟩
        var x ∙ as
      ∎
      where
        x   = lift n suc y
        ηz  = TK.η-exp j hyp (var∙ zero)
        σ   = sub ηz ↑⋆ n
        as′ = as //Var (V.wk V.↑) V.↑⋆ n

        eq = begin
          x V./ (V.wk V.↑) V.↑⋆ n       ≡⟨ VarLemmas.lookup-wk-↑⋆-↑⋆ 1 n x ⟩
          lift n (lift 1 suc) x               ≡⟨⟩
          lift n (lift 1 suc) (lift n suc y)  ≡⟨ sym (lift-commutes 0 n y) ⟩
          lift n suc (lift n suc y)           ≡⟨⟩
          lift n suc x                        ∎
          where open ≡-Reasoning

        open ≈-Reasoning

    Sp∈-[]-η-var : ∀ {k m n} (Γ₂ : CtxExt (suc m) n) {Γ₁ as j l₁ l₂}
                   (hyp : ⌊ j ⌋≡ k) → kd k ∷ Γ₁ ⊢ j kds →
                   Γ₂ ++ kd k ∷ Γ₁ ⊢ l₁ ∋∙ as ∈ l₂ →
                   let as′ = as //Var (V.wk V.↑) V.↑⋆ n
                   in as′ //⟨ k ⟩ sub (TK.η-exp j hyp (var∙ zero)) ↑⋆ n ≈Sp as
    Sp∈-[]-η-var Γ₂ hyp j-kds ∈-[] = ≈-[]
    Sp∈-[]-η-var Γ₂ hyp j-kds (∈-∷ a∈l₁ l₂∋as∈l₃) =
      ≈-∷ (Nf∈-[]-η-var Γ₂ hyp j-kds a∈l₁) (Sp∈-[]-η-var Γ₂ hyp j-kds l₂∋as∈l₃)

    -- Reducing applications cancel out η-expansion of neutrals.

    η-exp-ne-∙∙ : ∀ {n} {Γ : Ctx n} {a bs j k} (hyp : ⌊ j ⌋≡ k) →
                  Γ ⊢ j kds → Γ ⊢Ne a ∈ k → Γ ⊢ k ∋∙ bs ∈ ★ →
                  TK.η-exp j hyp a ∙∙⟨ k ⟩ bs  ≈  a ∙∙ bs
    η-exp-ne-∙∙ is-★ j-kds a∈k ∈-[] = subst (_ ≈_) (sym (∙∙-[] _)) (≈-refl _)
    η-exp-ne-∙∙ {a = a} (is-⇒ {j₁} {j₂} {k₁} {k₂} ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂)
                (kds-Π j₁-kds j₂-kds) a∈k₁⇒k₂ (∈-∷ {b} {bs} b∈k₁ k₂∋bs∈★) =
      begin
        TK.η-exp (Π j₁ j₂) (is-⇒ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂) a ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ b ∙∙⟨ k₂ ⟩ bs
      ≈⟨ ≈-∙∙⟨⟩ k₂ (η-exp-ne-⌜·⌝ ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂ j₁-kds a∈k₁⇒k₂ b∈k₁)
                (≈Sp-refl bs) ⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (a ⌜·⌝ b) ∙∙⟨ k₂ ⟩ bs
      ≈⟨ η-exp-ne-∙∙ (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) j₂[b]-kds (Ne∈-Π-e a∈k₁⇒k₂ b∈k₁) k₂∋bs∈★ ⟩
        (a ⌜·⌝ b) ∙∙ bs
      ≡⟨ ∙∙-++ a (b ∷ []) bs ⟩
        a ∙∙ (b ∷ bs)
      ∎
      where
        open ≈-Reasoning
        j₂[b]-kds = kds-/⟨⟩ (subst (λ k → kd k ∷ _ ⊢ j₂ kds)
                                   (⌊⌋≡⇒⌊⌋-≡ ⌊j₁⌋≡k₁) j₂-kds)
                            (∈-hsub b∈k₁)

    η-exp-ne-⌜·⌝ : ∀ {n} {Γ : Ctx n} {a b j₁ j₂ k₁ k₂}
                   (hyp₁ : ⌊ j₁ ⌋≡ k₁) (hyp₂ : ⌊ j₂ ⌋≡ k₂) →
                   Γ ⊢ j₁ kds → Γ ⊢Ne a ∈ k₁ ⇒ k₂ → Γ ⊢Nf b ∈ k₁ →
                   TK.η-exp (Π j₁ j₂) (is-⇒ hyp₁ hyp₂) a ⌜·⌝⟨ k₁ ⇒ k₂ ⟩ b  ≈
                     TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ hyp₂) (a ⌜·⌝ b)
    η-exp-ne-⌜·⌝ {b = b} {j₁} {j₂} {k₁} ⌊j₁⌋≡k₁ ⌊j₂⌋≡k₂ j₁-kds
                 (∈-∙ {x} {_} {_} {as} x∈l l∋as∈k₁⇒k₂) b∈k₁ =
      let ηz = TK.η-exp (weakenKind′ j₁) (⌊⌋≡-weaken ⌊j₁⌋≡k₁) (var∙ zero)
      in begin
        TK.η-exp j₂ ⌊j₂⌋≡k₂
                 (weakenElim (var∙ x) ∙∙ weakenSpine as ⌜·⌝ ηz) /⟨ k₁ ⟩ sub b
      ≡⟨ cong (λ y → TK.η-exp _ ⌊j₂⌋≡k₂ (var y ∙ weakenSpine as ⌜·⌝ ηz) /⟨ k₁ ⟩ _)
              (VarLemmas.lookup-wk x)  ⟩
        TK.η-exp j₂ ⌊j₂⌋≡k₂ (var (suc x) ∙ weakenSpine as ⌜·⌝ ηz) /⟨ k₁ ⟩ sub b
      ≡⟨ TK.η-exp-ne-Miss-/⟨⟩ (suc x) x ⌊j₂⌋≡k₂ over ⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂)
          (var x ∙ ((weakenSpine as ∷ʳ ηz) //⟨ k₁ ⟩ sub b))
      ≡⟨ cong (λ bs → TK.η-exp _ (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (var x ∙ bs))
              (++-//⟨⟩ (weakenSpine as) _) ⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂)
          (var x ∙ (weakenSpine as //⟨ k₁ ⟩ sub b) ⌜·⌝ (ηz /⟨ k₁ ⟩ sub b))
      ≡⟨ cong (λ bs → TK.η-exp _ (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (var x ∙ bs ⌜·⌝ _))
              (//Var-wk-↑⋆-hsub-vanishes 0 as) ⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂)
                 (var x ∙ as ⌜·⌝ (ηz /⟨ k₁ ⟩ sub b))
      ≈⟨ ≈-η-exp′ (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂)
                  (≈-⌜·⌝ (≈-refl (var x ∙ as))
                         (η-exp-var-Hit-/⟨⟩ (⌊⌋≡-weaken ⌊j₁⌋≡k₁) (Hit-sub-↑⋆ 0)
                                            (kds-weaken j₁-kds)
                                            (∈-var zero refl) (∈-hsub b∈k₁))) ⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (var x ∙ as ⌜·⌝
          (var∙ zero /⟨ k₁ ⟩ sub b))
      ≡⟨⟩
        TK.η-exp (j₂ Kind[ b ∈ k₁ ]) (⌊⌋≡-/⟨⟩ ⌊j₂⌋≡k₂) (var x ∙ as ⌜·⌝ b)
      ∎
      where open ≈-Reasoning

private module TK = TrackSimpleKindsKindedEtaExp

-- η-expansion preserves simple kinding of neutral types.

η-exp-Var∈ : ∀ {n} {Γ : Ctx n} {x k} →
             Γ ⊢ k kds → Γ ⊢Var x ∈ ⌊ k ⌋ → Γ ⊢Nf η-exp k (var∙ x) ∈ ⌊ k ⌋
η-exp-Var∈ k-kd x∈⌊k⌋ = TK.η-exp-Var∈ (⌊⌋-⌊⌋≡ _) k-kd x∈⌊k⌋

η-exp-Ne∈ : ∀ {n} {Γ : Ctx n} {a k} →
            Γ ⊢ k kds → Γ ⊢Ne a ∈ ⌊ k ⌋ → Γ ⊢Nf η-exp k a ∈ ⌊ k ⌋
η-exp-Ne∈ k-kd a∈⌊k⌋ = TK.η-exp-Ne∈ (⌊⌋-⌊⌋≡ _) k-kd a∈⌊k⌋

-- η-expansion of neutrals followed by hereditary substitution
-- vanish if the substitution hits the head of the neutral.

η-exp-ne-Hit-/⟨⟩ : ∀ {k m n Γ Δ} {x a as j} {σ : SVSub m n} →
                   Hit σ x a → Γ ⊢ j kds → Γ ⊢Ne var x ∙ as ∈ ⌊ j ⌋ →
                   Δ ⊢/⟨ k ⟩ σ ∈ Γ →
                   η-exp j (var x ∙ as) /⟨ k ⟩ σ  ≈  var x ∙ as /⟨ k ⟩ σ
η-exp-ne-Hit-/⟨⟩ hitP j-kds x∙as∈k σ∈Γ =
  TK.η-exp-ne-Hit-/⟨⟩ (⌊⌋-⌊⌋≡ _) hitP j-kds x∙as∈k σ∈Γ

η-exp-var-Hit-/⟨⟩ : ∀ {k m n Γ Δ} {x a j} {σ : SVSub m n} →
                    Hit σ x a → Γ ⊢ j kds → Γ ⊢Var x ∈ ⌊ j ⌋ →
                    Δ ⊢/⟨ k ⟩ σ ∈ Γ →
                    η-exp j (var∙ x) /⟨ k ⟩ σ  ≈  var∙ x /⟨ k ⟩ σ
η-exp-var-Hit-/⟨⟩ hitP j-kds x∈k σ∈Γ =
  η-exp-ne-Hit-/⟨⟩ hitP j-kds (∈-∙ x∈k ∈-[]) σ∈Γ

-- Hereditary substitutions of a variable by its η-expansion vanish.

kds-[]-η-var : ∀ {m n} (Γ₂ : CtxExt (suc m) n) {Γ₁ j k} →
               Γ₁ ⊢ j kds → Γ₂ ++ kd ⌊ j ⌋ ∷ Γ₁ ⊢ k kds →
               let j′ = weakenKind′ j
                   k′ = k Kind′/Var (VarSubst.wk VarSubst.↑) VarSubst.↑⋆ n
               in k′ Kind/⟨ ⌊ j′ ⌋ ⟩ sub (η-exp j′ (var∙ zero)) ↑⋆ n ≋ k
kds-[]-η-var Γ₂ {Γ₁} {j} j-kds k-kds =
  TK.kds-[]-η-var Γ₂ (⌊⌋-⌊⌋≡ _) (kds-weaken j-kds)
                  (subst (λ l → Γ₂ ++ kd l ∷ Γ₁ ⊢ _ kds)
                         (sym (⌊⌋-Kind′/Var j)) k-kds)

Nf∈-[]-η-var : ∀ {m n} (Γ₂ : CtxExt (suc m) n) {Γ₁ a j k} →
               Γ₁ ⊢ j kds → Γ₂ ++ kd ⌊ j ⌋ ∷ Γ₁ ⊢Nf a ∈ k →
               let j′ = weakenKind′ j
                   a′ = a Elim/Var (VarSubst.wk VarSubst.↑) VarSubst.↑⋆ n
               in a′ /⟨ ⌊ j′ ⌋ ⟩ sub (η-exp j′ (var∙ zero)) ↑⋆ n ≈ a
Nf∈-[]-η-var Γ₂ {Γ₁} {a} {j} j-kds a∈k =
  TK.Nf∈-[]-η-var Γ₂ (⌊⌋-⌊⌋≡ _) (kds-weaken j-kds)
                  (subst (λ l → Γ₂ ++ kd l ∷ Γ₁ ⊢Nf _ ∈ _)
                         (sym (⌊⌋-Kind′/Var j)) a∈k)


module BasicIS4.Metatheory.DyadicHilbert-TarskiOvergluedDyadicImplicit where

open import BasicIS4.Syntax.DyadicHilbert public
open import BasicIS4.Semantics.TarskiOvergluedDyadicImplicit public

open ImplicitSyntax (_⊢_) (mono²⊢) public

-- Completeness with respect to a particular model.

module _ {{_ : Model}} where
  reify : ∀ {A Γ Δ} → Γ ⁏ Δ ⊩ A → Γ ⁏ Δ ⊢ A
  reify {α P}   s = syn s
  reify {A ▻ B} s = syn (s refl⊆²)
  reify {□ A}   s = syn (s refl⊆²)
  reify {A ∧ B} s = pair (reify (π₁ s)) (reify (π₂ s))
  reify {⊤}    s = unit

  reify⋆ : ∀ {Ξ Γ Δ} → Γ ⁏ Δ ⊩⋆ Ξ → Γ ⁏ Δ ⊢⋆ Ξ
  reify⋆ {∅}     ∙        = ∙
  reify⋆ {Ξ , A} (ts , t) = reify⋆ ts , reify t

-- Additional useful equipment.

module _ {{_ : Model}} where
  ⟪K⟫ : ∀ {A B Γ Δ} → Γ ⁏ Δ ⊩ A → Γ ⁏ Δ ⊩ B ▻ A
  ⟪K⟫ {A} a ψ = let a′ = mono²⊩ {A} ψ a
                in  app ck (reify a′) ⅋ K a′

  ⟪S⟫′ : ∀ {A B C Γ Δ} → Γ ⁏ Δ ⊩ A ▻ B ▻ C → Γ ⁏ Δ ⊩ (A ▻ B) ▻ A ▻ C
  ⟪S⟫′ {A} {B} {C} s₁ ψ = let s₁′ = mono²⊩ {A ▻ B ▻ C} ψ s₁
                              t   = syn (s₁′ refl⊆²)
                          in  app cs t ⅋ λ s₂ ψ′ →
                                let s₁″ = mono²⊩ {A ▻ B ▻ C} (trans⊆² ψ ψ′) s₁
                                    s₂′ = mono²⊩ {A ▻ B} ψ′ s₂
                                    t′  = syn (s₁″ refl⊆²)
                                    u   = syn (s₂′ refl⊆²)
                                in  app (app cs t′) u ⅋ ⟪S⟫ s₁″ s₂′

  _⟪D⟫_ : ∀ {A B Γ Δ} → Γ ⁏ Δ ⊩ □ (A ▻ B) → Γ ⁏ Δ ⊩ □ A → Γ ⁏ Δ ⊩ □ B
  (s₁ ⟪D⟫ s₂) ψ = let t ⅋ s₁′ = s₁ ψ
                      u ⅋ a   = s₂ ψ
                  in  app (app cdist t) u ⅋ s₁′ ⟪$⟫ a

  _⟪D⟫′_ : ∀ {A B Γ Δ} → Γ ⁏ Δ ⊩ □ (A ▻ B) → Γ ⁏ Δ ⊩ □ A ▻ □ B
  _⟪D⟫′_ {A} {B} s₁ ψ = let s₁′ = mono²⊩ {□ (A ▻ B)} ψ s₁
                        in  app cdist (reify (λ {_} ψ′ → s₁′ ψ′)) ⅋ _⟪D⟫_ s₁′

  ⟪↑⟫ : ∀ {A Γ Δ} → Γ ⁏ Δ ⊩ □ A → Γ ⁏ Δ ⊩ □ □ A
  ⟪↑⟫ {A} s ψ = app cup (syn (s ψ)) ⅋ λ ψ′ → s (trans⊆² ψ ψ′)

  _⟪,⟫′_ : ∀ {A B Γ Δ} → Γ ⁏ Δ ⊩ A → Γ ⁏ Δ ⊩ B ▻ A ∧ B
  _⟪,⟫′_ {A} a ψ = let a′ = mono²⊩ {A} ψ a
                   in  app cpair (reify a′) ⅋ _,_ a′

-- Soundness with respect to all models, or evaluation.

eval : ∀ {A Γ Δ} → Γ ⁏ Δ ⊢ A → Γ ⁏ Δ ⊨ A
eval (var i)   γ δ = lookup i γ
eval (app t u) γ δ = eval t γ δ ⟪$⟫ eval u γ δ
eval ci        γ δ = K (ci ⅋ I)
eval ck        γ δ = K (ck ⅋ ⟪K⟫)
eval cs        γ δ = K (cs ⅋ ⟪S⟫′)
eval (mvar i)  γ δ = mlookup i δ
eval (box t)   γ δ = λ ψ → let δ′ = mono²⊩⋆ ψ δ
                            in  mmulticut (reify⋆ δ′) (box t) ⅋
                                  eval t ∙ δ′
eval cdist     γ δ = K (cdist ⅋ _⟪D⟫′_)
eval cup       γ δ = K (cup ⅋ ⟪↑⟫)
eval cdown     γ δ = K (cdown ⅋ ⟪↓⟫)
eval cpair     γ δ = K (cpair ⅋ _⟪,⟫′_)
eval cfst      γ δ = K (cfst ⅋ π₁)
eval csnd      γ δ = K (csnd ⅋ π₂)
eval unit      γ δ = ∙

-- TODO: Correctness of evaluation with respect to conversion.

-- The canonical model.

private
  instance
    canon : Model
    canon = record
      { _⊩ᵅ_    = λ Π P → Π ⊢ α P
      ; mono²⊩ᵅ = mono²⊢
      }

-- Soundness with respect to the canonical model.

reflectᶜ : ∀ {A Γ Δ} → Γ ⁏ Δ ⊢ A → Γ ⁏ Δ ⊩ A
reflectᶜ {α P}   t = t ⅋ t
reflectᶜ {A ▻ B} t = λ ψ → let t′ = mono²⊢ ψ t
                            in  t′ ⅋ λ a → reflectᶜ (app t′ (reify a))
reflectᶜ {□ A}   t = λ ψ → let t′ = mono²⊢ ψ t
                            in  t′ ⅋ reflectᶜ (down t′)
reflectᶜ {A ∧ B} t = reflectᶜ (fst t) , reflectᶜ (snd t)
reflectᶜ {⊤}    t = ∙

reflectᶜ⋆ : ∀ {Ξ Γ Δ} → Γ ⁏ Δ ⊢⋆ Ξ → Γ ⁏ Δ ⊩⋆ Ξ
reflectᶜ⋆ {∅}     ∙        = ∙
reflectᶜ⋆ {Ξ , A} (ts , t) = reflectᶜ⋆ ts , reflectᶜ t

-- Reflexivity and transitivity.

refl⊩⋆ : ∀ {Γ Δ} → Γ ⁏ Δ ⊩⋆ Γ
refl⊩⋆ = reflectᶜ⋆ refl⊢⋆

mrefl⊩⋆ : ∀ {Γ Δ} → Γ ⁏ Δ ⊩⋆ □⋆ Δ
mrefl⊩⋆ = reflectᶜ⋆ mrefl⊢⋆

trans⊩⋆ : ∀ {Γ Γ′ Δ Δ′ Ξ} → Γ ⁏ Δ ⊩⋆ Γ′ ⧺ (□⋆ Δ′) → Γ′ ⁏ Δ′ ⊩⋆ Ξ → Γ ⁏ Δ ⊩⋆ Ξ
trans⊩⋆ ts us = reflectᶜ⋆ (trans⊢⋆ (reify⋆ ts) (reify⋆ us))

-- Completeness with respect to all models, or quotation.

quot : ∀ {A Γ Δ} → Γ ⁏ Δ ⊨ A → Γ ⁏ Δ ⊢ A
quot s = reify (s refl⊩⋆ mrefl⊩⋆)

-- Normalisation by evaluation.

norm : ∀ {A Γ Δ} → Γ ⁏ Δ ⊢ A → Γ ⁏ Δ ⊢ A
norm = quot ∘ eval

-- TODO: Correctness of normalisation with respect to conversion.


{-# OPTIONS --without-K --safe #-}

open import Definition.Typed.EqualityRelation

module Definition.LogicalRelation {{eqrel : EqRelSet}} where
open EqRelSet {{...}}

open import Definition.Untyped as U
open import Definition.Untyped.Properties
open import Definition.Typed.Properties
open import Definition.Typed
open import Definition.Typed.Weakening

open import Tools.Product
import Tools.PropositionalEquality as PE

-- The different cases of the logical relation are spread out through out
-- this file. This is due to them having different dependencies.

-- We will refer to expressions that satisfies the logical relation as reducible.

-- Reducibility of Neutrals:

-- Neutral type
record _⊩ne_ (Γ : Con Term) (A : Term) : Set where
  constructor ne
  field
    K   : Term
    D   : Γ ⊢ A :⇒*: K
    neK : Neutral K
    K≡K : Γ ⊢ K ~ K ∷ U

-- Neutral type equality
record _⊩ne_≡_/_ (Γ : Con Term) (A B : Term) ([A] : Γ ⊩ne A) : Set where
  constructor ne₌
  open _⊩ne_ [A]
  field
    M   : Term
    D′  : Γ ⊢ B :⇒*: M
    neM : Neutral M
    K≡M : Γ ⊢ K ~ M ∷ U

-- Neutral term in WHNF
record _⊩neNf_∷_ (Γ : Con Term) (k A : Term) : Set where
  inductive
  constructor neNfₜ
  field
    neK  : Neutral k
    ⊢k   : Γ ⊢ k ∷ A
    k≡k  : Γ ⊢ k ~ k ∷ A

-- Neutral term
record _⊩ne_∷_/_ (Γ : Con Term) (t A : Term) ([A] : Γ ⊩ne A) : Set where
  inductive
  constructor neₜ
  open _⊩ne_ [A]
  field
    k   : Term
    d   : Γ ⊢ t :⇒*: k ∷ K
    nf  : Γ ⊩neNf k ∷ K

-- Neutral term equality in WHNF
record _⊩neNf_≡_∷_ (Γ : Con Term) (k m A : Term) : Set where
  inductive
  constructor neNfₜ₌
  field
    neK  : Neutral k
    neM  : Neutral m
    k≡m  : Γ ⊢ k ~ m ∷ A

-- Neutral term equality
record _⊩ne_≡_∷_/_ (Γ : Con Term) (t u A : Term) ([A] : Γ ⊩ne A) : Set where
  constructor neₜ₌
  open _⊩ne_ [A]
  field
    k m : Term
    d   : Γ ⊢ t :⇒*: k ∷ K
    d′  : Γ ⊢ u :⇒*: m ∷ K
    nf  : Γ ⊩neNf k ≡ m ∷ K

-- Reducibility of natural numbers:

-- Natural number type
_⊩ℕ_ : (Γ : Con Term) (A : Term) → Set
Γ ⊩ℕ A = Γ ⊢ A :⇒*: ℕ

-- Natural number type equality
_⊩ℕ_≡_ : (Γ : Con Term) (A B : Term) → Set
Γ ⊩ℕ A ≡ B = Γ ⊢ B ⇒* ℕ

mutual
  -- Natural number term
  record _⊩ℕ_∷ℕ (Γ : Con Term) (t : Term) : Set where
    inductive
    constructor ℕₜ
    field
      n : Term
      d : Γ ⊢ t :⇒*: n ∷ ℕ
      n≡n : Γ ⊢ n ≅ n ∷ ℕ
      prop : Natural-prop Γ n

  -- WHNF property of natural number terms
  data Natural-prop (Γ : Con Term) : (n : Term) → Set where
    sucᵣ  : ∀ {n} → Γ ⊩ℕ n ∷ℕ → Natural-prop Γ (suc n)
    zeroᵣ : Natural-prop Γ zero
    ne    : ∀ {n} → Γ ⊩neNf n ∷ ℕ → Natural-prop Γ n

mutual
  -- Natural number term equality
  record _⊩ℕ_≡_∷ℕ (Γ : Con Term) (t u : Term) : Set where
    inductive
    constructor ℕₜ₌
    field
      k k′ : Term
      d : Γ ⊢ t :⇒*: k  ∷ ℕ
      d′ : Γ ⊢ u :⇒*: k′ ∷ ℕ
      k≡k′ : Γ ⊢ k ≅ k′ ∷ ℕ
      prop : [Natural]-prop Γ k k′

  -- WHNF property of Natural number term equality
  data [Natural]-prop (Γ : Con Term) : (n n′ : Term) → Set where
    sucᵣ  : ∀ {n n′} → Γ ⊩ℕ n ≡ n′ ∷ℕ → [Natural]-prop Γ (suc n) (suc n′)
    zeroᵣ : [Natural]-prop Γ zero zero
    ne    : ∀ {n n′} → Γ ⊩neNf n ≡ n′ ∷ ℕ → [Natural]-prop Γ n n′

-- Natural extraction from term WHNF property
natural : ∀ {Γ n} → Natural-prop Γ n → Natural n
natural (sucᵣ x) = sucₙ
natural zeroᵣ = zeroₙ
natural (ne (neNfₜ neK ⊢k k≡k)) = ne neK

-- Natural extraction from term equality WHNF property
split : ∀ {Γ a b} → [Natural]-prop Γ a b → Natural a × Natural b
split (sucᵣ x) = sucₙ , sucₙ
split zeroᵣ = zeroₙ , zeroₙ
split (ne (neNfₜ₌ neK neM k≡m)) = ne neK , ne neM

-- Reducibility of Empty

-- Empty type
_⊩Empty_ : (Γ : Con Term) (A : Term) → Set
Γ ⊩Empty A = Γ ⊢ A :⇒*: Empty

-- Empty type equality
_⊩Empty_≡_ : (Γ : Con Term) (A B : Term) → Set
Γ ⊩Empty A ≡ B = Γ ⊢ B ⇒* Empty

-- WHNF property of absurd terms
data Empty-prop (Γ : Con Term) : (n : Term) → Set where
  ne    : ∀ {n} → Γ ⊩neNf n ∷ Empty → Empty-prop Γ n

-- Empty term
record _⊩Empty_∷Empty (Γ : Con Term) (t : Term) : Set where
  inductive
  constructor Emptyₜ
  field
    n : Term
    d : Γ ⊢ t :⇒*: n ∷ Empty
    n≡n : Γ ⊢ n ≅ n ∷ Empty
    prop : Empty-prop Γ n

data [Empty]-prop (Γ : Con Term) : (n n′ : Term) → Set where
  ne    : ∀ {n n′} → Γ ⊩neNf n ≡ n′ ∷ Empty → [Empty]-prop Γ n n′

-- Empty term equality
record _⊩Empty_≡_∷Empty (Γ : Con Term) (t u : Term) : Set where
  inductive
  constructor Emptyₜ₌
  field
    k k′ : Term
    d : Γ ⊢ t :⇒*: k  ∷ Empty
    d′ : Γ ⊢ u :⇒*: k′ ∷ Empty
    k≡k′ : Γ ⊢ k ≅ k′ ∷ Empty
    prop : [Empty]-prop Γ k k′

empty : ∀ {Γ n} → Empty-prop Γ n → Neutral n
empty (ne (neNfₜ neK _ _)) = neK

esplit : ∀ {Γ a b} → [Empty]-prop Γ a b → Neutral a × Neutral b
esplit (ne (neNfₜ₌ neK neM k≡m)) = neK , neM

-- Reducibility of Unit

-- Unit type
_⊩Unit_ : (Γ : Con Term) (A : Term) → Set
Γ ⊩Unit A = Γ ⊢ A :⇒*: Unit

-- Unit type equality
_⊩Unit_≡_ : (Γ : Con Term) (A B : Term) → Set
Γ ⊩Unit A ≡ B = Γ ⊢ B ⇒* Unit

record _⊩Unit_∷Unit (Γ : Con Term) (t : Term) : Set where
  inductive
  constructor Unitₜ
  field
    n : Term
    d : Γ ⊢ t :⇒*: n ∷ Unit
    prop : Whnf n

-- Unit term equality
record _⊩Unit_≡_∷Unit (Γ : Con Term) (t u : Term) : Set where
  constructor Unitₜ₌
  field
    ⊢t : Γ ⊢ t ∷ Unit
    ⊢u : Γ ⊢ u ∷ Unit

-- Type levels

data TypeLevel : Set where
  ⁰ : TypeLevel
  ¹ : TypeLevel

data _<_ : (i j : TypeLevel) → Set where
  0<1 : ⁰ < ¹

-- Logical relation
-- Exported interface
record LogRelKit : Set₁ where
  constructor Kit
  field
    _⊩U : (Γ : Con Term) → Set
    _⊩B⟨_⟩_ : (Γ : Con Term) (W : BindingType) → Term → Set

    _⊩_ : (Γ : Con Term) → Term → Set
    _⊩_≡_/_ : (Γ : Con Term) (A B : Term) → Γ ⊩ A → Set
    _⊩_∷_/_ : (Γ : Con Term) (t A : Term) → Γ ⊩ A → Set
    _⊩_≡_∷_/_ : (Γ : Con Term) (t u A : Term) → Γ ⊩ A → Set

module LogRel (l : TypeLevel) (rec : ∀ {l′} → l′ < l → LogRelKit) where

  -- Reducibility of Universe:

  -- Universe type
  record _⊩¹U (Γ : Con Term) : Set where
    constructor Uᵣ
    field
      l′ : TypeLevel
      l< : l′ < l
      ⊢Γ : ⊢ Γ

  -- Universe type equality
  _⊩¹U≡_ : (Γ : Con Term) (B : Term) → Set
  Γ ⊩¹U≡ B = B PE.≡ U -- Note lack of reduction

  -- Universe term
  record _⊩¹U_∷U/_ {l′} (Γ : Con Term) (t : Term) (l< : l′ < l) : Set where
    constructor Uₜ
    open LogRelKit (rec l<)
    field
      A     : Term
      d     : Γ ⊢ t :⇒*: A ∷ U
      typeA : Type A
      A≡A   : Γ ⊢ A ≅ A ∷ U
      [t]   : Γ ⊩ t

  -- Universe term equality
  record _⊩¹U_≡_∷U/_ {l′} (Γ : Con Term) (t u : Term) (l< : l′ < l) : Set where
    constructor Uₜ₌
    open LogRelKit (rec l<)
    field
      A B   : Term
      d     : Γ ⊢ t :⇒*: A ∷ U
      d′    : Γ ⊢ u :⇒*: B ∷ U
      typeA : Type A
      typeB : Type B
      A≡B   : Γ ⊢ A ≅ B ∷ U
      [t]   : Γ ⊩ t
      [u]   : Γ ⊩ u
      [t≡u] : Γ ⊩ t ≡ u / [t]

  mutual

    -- Reducibility of Binding types (Π, Σ):

    -- B-type
    record _⊩¹B⟨_⟩_ (Γ : Con Term) (W : BindingType) (A : Term) : Set where
      inductive
      constructor Bᵣ
      field
        F : Term
        G : Term
        D : Γ ⊢ A :⇒*: ⟦ W ⟧ F ▹ G
        ⊢F : Γ ⊢ F
        ⊢G : Γ ∙ F ⊢ G
        A≡A : Γ ⊢ ⟦ W ⟧ F ▹ G ≅ ⟦ W ⟧ F ▹ G
        [F] : ∀ {ρ Δ} → ρ ∷ Δ ⊆ Γ → ⊢ Δ → Δ ⊩¹ U.wk ρ F
        [G] : ∀ {ρ Δ a}
            → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
            → Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ
            → Δ ⊩¹ U.wk (lift ρ) G [ a ]
        G-ext : ∀ {ρ Δ a b}
              → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
              → ([a] : Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              → ([b] : Δ ⊩¹ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              → Δ ⊩¹ a ≡ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ
              → Δ ⊩¹ U.wk (lift ρ) G [ a ] ≡ U.wk (lift ρ) G [ b ] / [G] [ρ] ⊢Δ [a]

    -- B-type equality
    record _⊩¹B⟨_⟩_≡_/_ (Γ : Con Term) (W : BindingType) (A B : Term) ([A] : Γ ⊩¹B⟨ W ⟩ A) : Set where
      inductive
      constructor B₌
      open _⊩¹B⟨_⟩_ [A]
      field
        F′     : Term
        G′     : Term
        D′     : Γ ⊢ B ⇒* ⟦ W ⟧ F′ ▹ G′
        A≡B    : Γ ⊢ ⟦ W ⟧ F ▹ G ≅ ⟦ W ⟧ F′ ▹ G′
        [F≡F′] : ∀ {ρ Δ}
               → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
               → Δ ⊩¹ U.wk ρ F ≡ U.wk ρ F′ / [F] [ρ] ⊢Δ
        [G≡G′] : ∀ {ρ Δ a}
               → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
               → ([a] : Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
               → Δ ⊩¹ U.wk (lift ρ) G [ a ] ≡ U.wk (lift ρ) G′ [ a ] / [G] [ρ] ⊢Δ [a]

    -- Term reducibility of Π-type
    _⊩¹Π_∷_/_ : (Γ : Con Term) (t A : Term) ([A] : Γ ⊩¹B⟨ BΠ ⟩ A) → Set
    Γ ⊩¹Π t ∷ A / Bᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext =
      ∃ λ f → Γ ⊢ t :⇒*: f ∷ Π F ▹ G
            × Function f
            × Γ ⊢ f ≅ f ∷ Π F ▹ G
            × (∀ {ρ Δ a b}
              ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
              ([a] : Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              ([b] : Δ ⊩¹ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              ([a≡b] : Δ ⊩¹ a ≡ b ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              → Δ ⊩¹ U.wk ρ f ∘ a ≡ U.wk ρ f ∘ b ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
            × (∀ {ρ Δ a} → ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
              → ([a] : Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
              → Δ ⊩¹ U.wk ρ f ∘ a ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
              {- NOTE(WN): Last 2 fields could be refactored to a single forall.
                           But touching this definition is painful, so only do it
                           if you have to change it anyway. -}
    -- Issue: Agda complains about record use not being strictly positive.
    --        Therefore we have to use ×

    -- Term equality of Π-type
    _⊩¹Π_≡_∷_/_ : (Γ : Con Term) (t u A : Term) ([A] : Γ ⊩¹B⟨ BΠ ⟩ A) → Set
    Γ ⊩¹Π t ≡ u ∷ A / [A]@(Bᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext) =
      ∃₂ λ f g → Γ ⊢ t :⇒*: f ∷ Π F ▹ G
               × Γ ⊢ u :⇒*: g ∷ Π F ▹ G
               × Function f
               × Function g
               × Γ ⊢ f ≅ g ∷ Π F ▹ G
               × Γ ⊩¹Π t ∷ A / [A]
               × Γ ⊩¹Π u ∷ A / [A]
               × (∀ {ρ Δ a} ([ρ] : ρ ∷ Δ ⊆ Γ) (⊢Δ : ⊢ Δ)
                 ([a] : Δ ⊩¹ a ∷ U.wk ρ F / [F] [ρ] ⊢Δ)
                 → Δ ⊩¹ U.wk ρ f ∘ a ≡ U.wk ρ g ∘ a ∷ U.wk (lift ρ) G [ a ] / [G] [ρ] ⊢Δ [a])
    -- Issue: Same as above.

    -- Term reducibility of Σ-type
    _⊩¹Σ_∷_/_ : (Γ : Con Term) (t A : Term) ([A] : Γ ⊩¹B⟨ BΣ ⟩ A) → Set
    Γ ⊩¹Σ t ∷ A / [A]@(Bᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext) =
      ∃ λ p → Γ ⊢ t :⇒*: p ∷ Σ F ▹ G
            × Product p
            × Γ ⊢ p ≅ p ∷ Σ F ▹ G
            × (Σ (Γ ⊩¹ fst p ∷ U.wk id F / [F] id (wf ⊢F)) λ [fst]
                 → Γ ⊩¹ snd p ∷ U.wk (lift id) G [ fst p ] / [G] id (wf ⊢F) [fst])

    -- Term equality of Σ-type
    _⊩¹Σ_≡_∷_/_ : (Γ : Con Term) (t u A : Term) ([A] : Γ ⊩¹B⟨ BΣ ⟩ A) → Set
    Γ ⊩¹Σ t ≡ u ∷ A / [A]@(Bᵣ F G D ⊢F ⊢G A≡A [F] [G] G-ext) =
      ∃₂ λ p r → Γ ⊢ t :⇒*: p ∷ Σ F ▹ G
               × Γ ⊢ u :⇒*: r ∷ Σ F ▹ G
               × Product p
               × Product r
               × Γ ⊢ p ≅ r ∷ Σ F ▹ G
               × Γ ⊩¹Σ t ∷ A / [A]
               × Γ ⊩¹Σ u ∷ A / [A]
               × (Σ (Γ ⊩¹ fst p ∷ U.wk id F / [F] id (wf ⊢F)) λ [fstp]
                    → Γ ⊩¹ fst r ∷ U.wk id F / [F] id (wf ⊢F)
                    × Γ ⊩¹ fst p ≡ fst r ∷ U.wk id F / [F] id (wf ⊢F)
                    × Γ ⊩¹ snd p ≡ snd r ∷ U.wk (lift id) G [ fst p ] / [G] id (wf ⊢F) [fstp])

    -- Logical relation definition
    data _⊩¹_ (Γ : Con Term) : Term → Set where
      Uᵣ  : Γ ⊩¹U → Γ ⊩¹ U
      ℕᵣ  : ∀ {A} → Γ ⊩ℕ A → Γ ⊩¹ A
      Emptyᵣ : ∀ {A} → Γ ⊩Empty A → Γ ⊩¹ A
      Unitᵣ : ∀ {A} → Γ ⊩Unit A → Γ ⊩¹ A
      ne  : ∀ {A} → Γ ⊩ne A → Γ ⊩¹ A
      Bᵣ  : ∀ {A} W → Γ ⊩¹B⟨ W ⟩ A → Γ ⊩¹ A
      emb : ∀ {A l′} (l< : l′ < l) (let open LogRelKit (rec l<))
            ([A] : Γ ⊩ A) → Γ ⊩¹ A

    _⊩¹_≡_/_ : (Γ : Con Term) (A B : Term) → Γ ⊩¹ A → Set
    Γ ⊩¹ A ≡ B / Uᵣ UA = Γ ⊩¹U≡ B
    Γ ⊩¹ A ≡ B / ℕᵣ D = Γ ⊩ℕ A ≡ B
    Γ ⊩¹ A ≡ B / Emptyᵣ D = Γ ⊩Empty A ≡ B
    Γ ⊩¹ A ≡ B / Unitᵣ D = Γ ⊩Unit A ≡ B
    Γ ⊩¹ A ≡ B / ne neA = Γ ⊩ne A ≡ B / neA
    Γ ⊩¹ A ≡ B / Bᵣ W BA = Γ ⊩¹B⟨ W ⟩ A ≡ B / BA
    Γ ⊩¹ A ≡ B / emb l< [A] = Γ ⊩ A ≡ B / [A]
      where open LogRelKit (rec l<)

    _⊩¹_∷_/_ : (Γ : Con Term) (t A : Term) → Γ ⊩¹ A → Set
    Γ ⊩¹ t ∷ .U / Uᵣ (Uᵣ l′ l< ⊢Γ) = Γ ⊩¹U t ∷U/ l<
    Γ ⊩¹ t ∷ A / ℕᵣ D = Γ ⊩ℕ t ∷ℕ
    Γ ⊩¹ t ∷ A / Emptyᵣ D = Γ ⊩Empty t ∷Empty
    Γ ⊩¹ t ∷ A / Unitᵣ D = Γ ⊩Unit t ∷Unit
    Γ ⊩¹ t ∷ A / ne neA = Γ ⊩ne t ∷ A / neA
    Γ ⊩¹ t ∷ A / Bᵣ BΠ ΠA  = Γ ⊩¹Π t ∷ A / ΠA
    Γ ⊩¹ t ∷ A / Bᵣ BΣ ΣA  = Γ ⊩¹Σ t ∷ A / ΣA
    Γ ⊩¹ t ∷ A / emb l< [A] = Γ ⊩ t ∷ A / [A]
      where open LogRelKit (rec l<)

    _⊩¹_≡_∷_/_ : (Γ : Con Term) (t u A : Term) → Γ ⊩¹ A → Set
    Γ ⊩¹ t ≡ u ∷ .U / Uᵣ (Uᵣ l′ l< ⊢Γ) = Γ ⊩¹U t ≡ u ∷U/ l<
    Γ ⊩¹ t ≡ u ∷ A / ℕᵣ D = Γ ⊩ℕ t ≡ u ∷ℕ
    Γ ⊩¹ t ≡ u ∷ A / Emptyᵣ D = Γ ⊩Empty t ≡ u ∷Empty
    Γ ⊩¹ t ≡ u ∷ A / Unitᵣ D = Γ ⊩Unit t ≡ u ∷Unit
    Γ ⊩¹ t ≡ u ∷ A / ne neA = Γ ⊩ne t ≡ u ∷ A / neA
    Γ ⊩¹ t ≡ u ∷ A / Bᵣ BΠ ΠA = Γ ⊩¹Π t ≡ u ∷ A / ΠA
    Γ ⊩¹ t ≡ u ∷ A / Bᵣ BΣ ΣA  = Γ ⊩¹Σ t ≡ u ∷ A / ΣA
    Γ ⊩¹ t ≡ u ∷ A / emb l< [A] = Γ ⊩ t ≡ u ∷ A / [A]
      where open LogRelKit (rec l<)

    kit : LogRelKit
    kit = Kit _⊩¹U _⊩¹B⟨_⟩_
              _⊩¹_ _⊩¹_≡_/_ _⊩¹_∷_/_ _⊩¹_≡_∷_/_

open LogRel public using (Uᵣ; ℕᵣ; Emptyᵣ; Unitᵣ; ne; Bᵣ; B₌; emb; Uₜ; Uₜ₌)

-- Patterns for the non-records of Π
pattern Πₜ f d funcF f≡f [f] [f]₁ = f , d , funcF , f≡f , [f] , [f]₁
pattern Πₜ₌ f g d d′ funcF funcG f≡g [f] [g] [f≡g] = f , g , d , d′ , funcF , funcG , f≡g , [f] , [g] , [f≡g]
pattern Σₜ p d pProd p≅p [fst] [snd] = p , d , pProd , p≅p , ([fst] , [snd])
pattern Σₜ₌ p r d d′ pProd rProd p≅r [t] [u] [fstp] [fstr] [fst≡] [snd≡] = p , r , d , d′ , pProd , rProd , p≅r , [t] , [u] , ([fstp] , [fstr] , [fst≡] , [snd≡])

pattern Uᵣ′ a b c = Uᵣ (Uᵣ a b c)
pattern ne′ a b c d = ne (ne a b c d)
pattern Bᵣ′ W a b c d e f g h i = Bᵣ W (Bᵣ a b c d e f g h i)
pattern Πᵣ′ a b c d e f g h i = Bᵣ′ BΠ a b c d e f g h i
pattern Σᵣ′ a b c d e f g h i = Bᵣ′ BΣ a b c d e f g h i

logRelRec : ∀ l {l′} → l′ < l → LogRelKit
logRelRec ⁰ = λ ()
logRelRec ¹ 0<1 = LogRel.kit ⁰ (λ ())

kit : ∀ (i : TypeLevel) → LogRelKit
kit l = LogRel.kit l (logRelRec l)
-- a bit of repetition in "kit ¹" definition, would work better with Fin 2 for
-- TypeLevel because you could recurse.

_⊩′⟨_⟩U : (Γ : Con Term) (l : TypeLevel) → Set
Γ ⊩′⟨ l ⟩U = Γ ⊩U where open LogRelKit (kit l)

_⊩′⟨_⟩B⟨_⟩_ : (Γ : Con Term) (l : TypeLevel) (W : BindingType) → Term → Set
Γ ⊩′⟨ l ⟩B⟨ W ⟩ A = Γ ⊩B⟨ W ⟩ A where open LogRelKit (kit l)

_⊩⟨_⟩_ : (Γ : Con Term) (l : TypeLevel) → Term → Set
Γ ⊩⟨ l ⟩ A = Γ ⊩ A where open LogRelKit (kit l)

_⊩⟨_⟩_≡_/_ : (Γ : Con Term) (l : TypeLevel) (A B : Term) → Γ ⊩⟨ l ⟩ A → Set
Γ ⊩⟨ l ⟩ A ≡ B / [A] = Γ ⊩ A ≡ B / [A] where open LogRelKit (kit l)

_⊩⟨_⟩_∷_/_ : (Γ : Con Term) (l : TypeLevel) (t A : Term) → Γ ⊩⟨ l ⟩ A → Set
Γ ⊩⟨ l ⟩ t ∷ A / [A] = Γ ⊩ t ∷ A / [A] where open LogRelKit (kit l)

_⊩⟨_⟩_≡_∷_/_ : (Γ : Con Term) (l : TypeLevel) (t u A : Term) → Γ ⊩⟨ l ⟩ A → Set
Γ ⊩⟨ l ⟩ t ≡ u ∷ A / [A] = Γ ⊩ t ≡ u ∷ A / [A] where open LogRelKit (kit l)


{-# OPTIONS --rewriting --prop --confluence-check #-}

open import Agda.Primitive
open import Agda.Builtin.Bool
open import Agda.Builtin.Nat
open import Agda.Builtin.List
open import Agda.Builtin.Equality
open import Agda.Builtin.Equality.Rewrite
open import Agda.Builtin.Sigma
open import Agda.Builtin.Unit
open import Data.Vec.Base
open import Data.Bool
open import Data.Sum

variable ℓ ℓ₁ ℓ₂ ℓ₃ ℓ₄ : Level

record Top ℓ : Set ℓ where
  constructor ⟨⟩

{-
 Axiomatisation of ExTT
-}

postulate raise : (A : Set ℓ) → A

-- we now state rewrite rules for raise

postulate raise-Pi : (A : Set ℓ) (B : A → Set ℓ₁) →
                     raise ((a : A) → B a) ≡ λ a → raise (B a)

{-# REWRITE raise-Pi #-}

postulate raise-Sigma : (A : Set ℓ) (B : A → Set ℓ₁) →
                     raise (Σ A B) ≡ (raise A , raise (B (raise A)))

{-# REWRITE raise-Sigma #-}

nat-rec : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) → (n : Nat) → P n
nat-rec P P0 PS zero = P0
nat-rec P P0 PS (suc n) = PS n (nat-rec P P0 PS n)

postulate raise-nat-rec : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                          nat-rec P P0 PS (raise Nat) ≡ raise (P (raise Nat))

{-# REWRITE raise-nat-rec #-}

postulate catch-nat : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                      (Praise : P (raise Nat)) → (n : Nat) → P n

postulate catch-nat-zero : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                           (Praise : P (raise Nat)) → catch-nat P P0 PS Praise 0 ≡ P0

postulate catch-nat-suc : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                          (Praise : P (raise Nat)) → (n : Nat) →
                          catch-nat P P0 PS Praise (suc n) ≡ PS n (catch-nat P P0 PS Praise n)

postulate catch-nat-raise : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                            (Praise : P (raise Nat)) → catch-nat P P0 PS Praise (raise Nat) ≡ Praise

{-# REWRITE catch-nat-zero #-}
{-# REWRITE catch-nat-suc #-}
{-# REWRITE catch-nat-raise #-}

postulate raise-Top : raise (Top ℓ) ≡ ⟨⟩

{-# REWRITE raise-Top #-}

postulate raise-Set : raise (Set ℓ) ≡ Top ℓ

{-# REWRITE raise-Set #-}

{-
 Axiomatisation of unk
-}

postulate unk : (A : Set ℓ) → A

-- we now state rewrite rules for unk

postulate unk-Pi : (A : Set ℓ) (B : A → Set ℓ₁) →
                     unk ((a : A) → B a) ≡ λ a → unk (B a)

{-# REWRITE unk-Pi #-}

postulate unk-Sigma : (A : Set ℓ) (B : A → Set ℓ₁) →
                     unk (Σ A B) ≡ (unk A , unk (B (unk A)))

{-# REWRITE unk-Sigma #-}

postulate unk-nat-rec : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                          nat-rec P P0 PS (unk Nat) ≡ unk (P (unk Nat))

{-# REWRITE unk-nat-rec #-}

postulate catch-unk-nat : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                      (Punk : P (unk Nat)) → (n : Nat) → P n

postulate catch-unk-nat-zero : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                           (Punk : P (unk Nat)) → catch-unk-nat P P0 PS Punk 0 ≡ P0

postulate catch-unk-nat-suc : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                          (Punk : P (unk Nat)) → (n : Nat) →
                          catch-unk-nat P P0 PS Punk (suc n) ≡ PS n (catch-unk-nat P P0 PS Punk n)

postulate catch-unk-nat-unk : (P : Nat → Set ℓ) (P0 : P 0) (PS : (n : Nat) → P n → P (suc n)) →
                            (Punk : P (unk Nat)) → catch-unk-nat P P0 PS Punk (unk Nat) ≡ Punk

{-# REWRITE catch-unk-nat-zero #-}
{-# REWRITE catch-unk-nat-suc #-}
{-# REWRITE catch-unk-nat-unk #-}

postulate unk-Top : unk (Top ℓ) ≡ ⟨⟩

{-# REWRITE unk-Top #-}

Unk : ∀ ℓ → Set ℓ
Unk ℓ = unk (Set ℓ)

-- postulate Unk : ∀ ℓ → Set (lsuc ℓ)
-- -- record Unk ℓ : Set (lsuc ℓ) where
-- --   constructor box
-- --   field
-- --     type : Set ℓ
-- --     elem : type

-- postulate raise-Unk :  ∀ ℓ → raise (Unk ℓ) ≡ box (raise (Set ℓ)) (raise _)

-- {-# REWRITE raise-Unk #-}


{- Byzantine Fault Tolerant Consensus Verification in Agda, version 0.9.

   Copyright (c) 2020, 2021, Oracle and/or its affiliates.
   Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.oracle.com/licenses/upl
-}
open import LibraBFT.Prelude
open import LibraBFT.Base.Types
import      LibraBFT.Yasm.Base as LYB

-- This module defines a model of a distributed system, parameterized by
-- SystemParameters, which establishes various application-dependent types,
-- handlers, etc.  The model supports a set of peers executing handlers in
-- response to messages received; these handlers can update the peer's
-- local state and/or send additional messages.  The model also enables
-- "cheat" steps, which can send arbitrary messages, except that they are
-- constrained to prevent a cheat step from introducing a new signature for
-- an "honest" public key.  The module also contains some structures for
-- proving properties of executions of the modeled system.

module LibraBFT.Yasm.System
   (ℓ-EC        : Level)
   (EpochConfig : Set ℓ-EC)
   (epochId     : EpochConfig → EpochId)
   (authorsN    : EpochConfig → ℕ)
   (parms : LYB.SystemParameters ℓ-EC EpochConfig epochId authorsN)
 where

 data InitStatus : Set where
   uninitd : InitStatus
   initd   : InitStatus

 open import LibraBFT.Yasm.Base            ℓ-EC EpochConfig epochId authorsN
 open SystemParameters parms
 open import LibraBFT.Yasm.AvailableEpochs PeerId ℓ-EC EpochConfig epochId authorsN
             using (AvailableEpochs) renaming (lookup'' to EC-lookup)
 import LibraBFT.Yasm.AvailableEpochs      PeerId ℓ-EC EpochConfig epochId authorsN as AE
 open import Util.FunctionOverride PeerId _≟PeerId_

 open import LibraBFT.Base.PKCS

 SenderMsgPair : Set
 SenderMsgPair = PeerId × Msg

 SentMessages : Set
 SentMessages = List SenderMsgPair

 -- The model supports sending messages that contain some fields that are
 -- /not/ covered by the message's signature.  Therefore, given a message
 -- with a verifiable signature, it is possible for a propositionally
 -- different message that verifies against the same signature to have been
 -- sent before, which is captured by the following definition.
 record MsgWithSig∈ (pk : PK)(sig : Signature)(pool : SentMessages) : Set where
   constructor mkMsgWithSig∈
   field
     msgWhole   : Msg
     msgPart    : Part
     msg⊆       : msgPart ⊂Msg msgWhole
     msgSender  : PeerId
     msg∈pool   : (msgSender , msgWhole) ∈ pool
     msgSigned  : WithVerSig pk msgPart
     msgSameSig : ver-signature msgSigned ≡ sig
 open MsgWithSig∈ public

 postulate  -- TODO-1: prove it
   MsgWithSig∈? : ∀ {pk} {sig} {pool} → Dec (MsgWithSig∈ pk sig pool)

 MsgWithSig∈-++ʳ : ∀{pk sig pool ms} → MsgWithSig∈ pk sig pool → MsgWithSig∈ pk sig (ms ++ pool)
 MsgWithSig∈-++ʳ {ms = pre} msig = record
    { msgWhole   = msgWhole msig
    ; msgPart    = msgPart  msig
    ; msg⊆       = msg⊆     msig
    ; msg∈pool   = Any-++ʳ pre (msg∈pool msig)
    ; msgSigned  = msgSigned msig
    ; msgSameSig = msgSameSig msig
    }

 MsgWithSig∈-++ˡ : ∀{pk sig pool ms} → MsgWithSig∈ pk sig ms → MsgWithSig∈ pk sig (ms ++ pool)
 MsgWithSig∈-++ˡ {ms = pre} msig = record
    { msgWhole   = msgWhole msig
    ; msgPart    = msgPart  msig
    ; msg⊆       = msg⊆     msig
    ; msg∈pool   = Any-++ˡ (msg∈pool msig)
    ; msgSigned  = msgSigned msig
    ; msgSameSig = msgSameSig msig
    }

 MsgWithSig∈-transp : ∀{pk sig pool pool'}
                    → (mws : MsgWithSig∈ pk sig pool)
                    → (msgSender mws , msgWhole mws) ∈ pool'
                    → MsgWithSig∈ pk sig pool'
 MsgWithSig∈-transp msig ∈pool' = record
    { msgWhole   = msgWhole msig
    ; msgPart    = msgPart  msig
    ; msg⊆       = msg⊆     msig
    ; msg∈pool   = ∈pool'
    ; msgSigned  = msgSigned msig
    ; msgSameSig = msgSameSig msig
    }

 -- * Forbidding the Forging of Signatures
 --
 -- Whenever our reasoning must involve digital signatures, it is standard
 -- to talk about EUF-CMA resistant signature schemes. Informally, this captures
 -- signatures schemes that cannot be compromised by certain adversaries.
 -- Formally, it means that for any probabilistic-polynomial-time adversary 𝓐,
 -- and some security parameter k:
 --
 --      Pr[ EUF-CMA(k) ] ≤ ε(k) for ε a negigible function.
 --
 -- EUF-CMA is defined as:
 --
 -- EUF-CMA(k):                           |  O(m):
 --   L        ← ∅                        |    σ ← Sign(sk , m)
 --   (pk, sk) ← Gen(k)                   |    L ← L ∪ { m }
 --   (m , σ)  ← 𝓐ᴼ(pk)                   |    return σ
 --   return (Verify(pk, m, σ) ∧ m ∉ L)   |
 --
 -- This says that 𝓐 cannot create a message that has /not yet been signed/ and
 -- forge a signature for it. The list 'L' keeps track of the messages that 𝓐
 -- asked to be signed by the oracle.
 --
 -- Because the probability of the adversary to win the EUF-CMA(k) game
 -- approaches 0 as k increases; it is reasonable to assume that winning
 -- the game is /impossible/ for realistic security parameters.
 --
 -- EUF-CMA security is incorporated into our model by constraining messages
 -- sent by a cheat step to ensure that for every verifiably signed part of
 -- such a message, if there is a signature on the part, then it is either for
 -- a dishonest public key (in which cases it's secret key may have been leaked),
 -- or a message has been sent with the same signature before (in which case the
 -- signature is simply being "replayed" from a previous message).
 --
 -- Dishonest (or "cheat") messages are those that are not the result of
 -- a /handle/ or /init/ call, but rather are the result of a /cheat/ step.
 --
 -- A part of a cheat message can contain a verifiable signature only if it
 -- is for a dishonest public key, or a message with the same signature has
 -- been sent before (a cheater can "reuse" an honest signature sent
 -- before; it just can't produce a new one).  Note that this constraint
 -- precludes a peer sending a message that contains a new verifiable
 -- signature for an honest PK, even if the PK is the peer's own PK for
 -- some epoch (implying that the peer possesses the associated secret
 -- key).  In other words, a peer that is honest for a given epoch (by
 -- virtue of being a member of that epoch and being assigned an honest PK
 -- for the epoch), cannot send a message for that epoch using a cheat
 -- step.
 CheatPartConstraint : SentMessages → Part → Set
 CheatPartConstraint pool m = ∀{pk} → (ver : WithVerSig pk m)
                                    → Meta-Dishonest-PK pk
                                    ⊎ MsgWithSig∈ pk (ver-signature ver) pool

 -- The only constraints on messages sent by cheat steps are that:
 -- * the sender is not an honest member in the epoch of any part of the message
 -- * the signature on any part of the message satisfies CheatCanSign, meaning
 --   that it is not a new signature for an honest public key
 CheatMsgConstraint : SentMessages → Msg → Set
 CheatMsgConstraint pool m = ∀{part} → part ⊂Msg m → CheatPartConstraint pool part

 -- * The System State
 --
 -- A system consists in a partial map from PeerId to PeerState, a pool
 -- of sent messages and a number of available epochs.
 record SystemState (e : ℕ) : Set ℓ-EC where
   field
     peerStates  : PeerId → PeerState
     initialised : PeerId → InitStatus
     msgPool     : SentMessages          -- All messages ever sent
     availEpochs : AvailableEpochs e
 open SystemState public

 initialState : SystemState 0
 initialState = record
   { peerStates  = const initPS
   ; initialised = const uninitd
   ; msgPool     = []
   ; availEpochs = []
   }

 -- Convenience function for appending an epoch to the system state
 pushEpoch : ∀{e} → EpochConfigFor e → SystemState e → SystemState (suc e)
 pushEpoch 𝓔 st = record
   { peerStates  = peerStates st
   ; initialised = initialised st
   ; msgPool     = msgPool st
   ; availEpochs = AE.append 𝓔 (availEpochs st)
   }

 -- * Small Step Semantics
 --
 -- The small step semantics are divided into three datatypes:
 --
 -- i)   StepPeerState executes a step through init or handle
 -- ii)  StepPeer executes a step through StepPeerState or cheat
 -- iii) Step transitions the system state by a StepPeer or by
 --      bringing a new epoch into existence

 -- The pre and post states of Honest peers are related iff
 data StepPeerState {e}(pid : PeerId)(𝓔s : AvailableEpochs e)(pool : SentMessages)
                       (peerInits : PeerId → InitStatus) (ps : PeerState) :
                       (PeerId → InitStatus) → (PeerState × List Msg) → Set where
   -- The peer receives an "initialization package"; for now, this consists
   -- of the actual EpochConfig for the epoch being initialized.  Later, we
   -- may move to a more general scheme, enabled by assuming a function
   -- 'render : InitPackage -> EpochConfig'.
   step-init : ∀ (ix : Fin e)
             → StepPeerState pid 𝓔s pool peerInits ps ⟦ peerInits , pid ← initd ⟧ (init pid (AE.lookup' 𝓔s ix) ps)

   -- The peer processes a message in the pool
   step-msg  : ∀{m}
             → m ∈ pool
             → peerInits pid ≡ initd
             → StepPeerState pid 𝓔s pool peerInits ps peerInits (handle pid (proj₂ m) ps)

 -- The pre-state of the suplied PeerId is related to the post-state and list of output messages iff:
 data StepPeer {e}(pre : SystemState e) : PeerId → PeerState → List Msg → Set where
   -- it can be obtained by a handle or init call.
   step-honest : ∀{pid st outs init'}
               → StepPeerState pid (availEpochs pre) (msgPool pre) (initialised pre) (peerStates pre pid) init' (st , outs)
               → StepPeer pre pid st outs

   -- or the peer decides to cheat.  CheatMsgConstraint ensures it cannot
   -- forge signatures by honest peers.  Cheat steps do not modify peer
   -- state: these are maintained exclusively by the implementation
   -- handlers.
   step-cheat  : ∀{pid}
               → (fm : SentMessages → PeerState → Msg)
               → let m = fm (msgPool pre) (peerStates pre pid)
                  in CheatMsgConstraint (msgPool pre) m
                   → StepPeer pre pid (peerStates pre pid) (m ∷ [])

 isCheat : ∀ {e pre pid ms outs} → StepPeer {e} pre pid ms outs → Set
 isCheat (step-honest _)  = ⊥
 isCheat (step-cheat _ _) = Unit

 -- Computes the post-sysstate for a given step-peer.
 StepPeer-post : ∀{e pid st' outs}{pre : SystemState e}
               → StepPeer pre pid st' outs → SystemState e
 StepPeer-post {e} {pid} {st'} {outs} {pre} _ = record pre
   { peerStates = ⟦ peerStates pre , pid ← st' ⟧
   ; msgPool    = List-map (pid ,_) outs ++ msgPool pre
   }

 StepPeer-post-lemma : ∀{e pid st' outs}{pre : SystemState e}
               → (pstep : StepPeer pre pid st' outs)
               → st' ≡ peerStates (StepPeer-post pstep) pid
 StepPeer-post-lemma pstep = sym override-target-≡

 cheatStepDNMPeerStates : ∀{e pid st' outs}{pre : SystemState e}
                        → (theStep : StepPeer pre pid st' outs)
                        → isCheat theStep
                        → peerStates (StepPeer-post theStep) ≡ peerStates pre
 cheatStepDNMPeerStates {pid = pid} {pre = pre} (step-cheat _ _) _
   = overrideSameVal-correct-ext {f = peerStates pre} {pid}

 cheatStepDNMPeerStates₁ : ∀{e pid pid' st' outs}{pre : SystemState e}
                        → (theStep : StepPeer pre pid st' outs)
                        → isCheat theStep
                        → peerStates (StepPeer-post theStep) pid' ≡ peerStates pre pid'
 cheatStepDNMPeerStates₁ {_} {pid} {pid'} (step-cheat fm x₁) x
   = overrideSameVal-correct pid pid'

 data Step : ∀{e e'} → SystemState e → SystemState e' → Set ℓ-EC where
   step-epoch : ∀{e}{pre : SystemState e}
              → (𝓔 : EpochConfigFor e)
              -- TODO-3: Eventually, we'll condition this step to only be
              -- valid when peers on the previous epoch have agreed that 𝓔
              -- is the new one.  → ∃EnoughValidCommitMsgsFor pre 𝓔
              → Step pre (pushEpoch 𝓔 pre)

   step-peer : ∀{e pid st' outs}{pre : SystemState e}
             → (pstep : StepPeer pre pid st' outs)
             → Step pre (StepPeer-post pstep)

 msgs-stable : ∀ {e e'} {pre : SystemState e} {post : SystemState e'} {m}
             → (theStep : Step pre post)
             → m ∈ msgPool pre
             → m ∈ msgPool post
 msgs-stable (step-epoch _) m∈ = m∈
 msgs-stable (step-peer {pid = pid} {outs = outs} _) m∈ = Any-++ʳ (List-map (pid ,_) outs) m∈

 peersRemainInitialized : ∀ {pid} {e e'} {pre : SystemState e} {post : SystemState e'}
                        → (theStep : Step pre post)
                        → initialised pre pid ≡ initd
                        → initialised post pid ≡ initd
 peersRemainInitialized (step-epoch _) isInitd = isInitd
 peersRemainInitialized {pid} (step-peer step) isInitd
   with step
 ... | step-cheat _ _ = isInitd
 ... | step-honest {pidS} {st} {outs} stp
   with pid ≟PeerId pidS
 ...| yes refl = isInitd
 ...| no imp = isInitd

 -- not used yet, but some proofs could probably be cleaned up using this,
 -- e.g., prevVoteRnd≤-pred-step in Impl.VotesOnce
 sendMessages-target : ∀ {m : SenderMsgPair} {sm : SentMessages} {ml : List SenderMsgPair}
                     → ¬ (m ∈ sm)
                     → m ∈ (ml ++ sm)
                     → m ∈ ml
 sendMessages-target {ml = ml} ¬m∈sm m∈++
   with Any-++⁻ ml m∈++
 ...| inj₁ m∈ml = m∈ml
 ...| inj₂ m∈sm = ⊥-elim (¬m∈sm m∈sm)

 step-epoch-does-not-send : ∀ {e} (pre : SystemState e) (𝓔 : EpochConfigFor e)
                            → msgPool (pushEpoch 𝓔 pre) ≡ msgPool pre
 step-epoch-does-not-send _ _ = refl

 -- * Reflexive-Transitive Closure

 data Step* : ∀{e e'} → SystemState e → SystemState e' → Set ℓ-EC where
   step-0 : ∀{e}{pre : SystemState e}
          → Step* pre pre

   step-s : ∀{e e' e''}{fst : SystemState e}{pre : SystemState e'}{post : SystemState e''}
          → Step* fst pre
          → Step pre post
          → Step* fst post

 ReachableSystemState : ∀{e} → SystemState e → Set ℓ-EC
 ReachableSystemState = Step* initialState

 eventProcessorPostSt : ∀ {pid s' s outs init'} {e} {st : SystemState e}
                      → (r : ReachableSystemState st)
                      → (stP : StepPeerState pid (availEpochs st) (msgPool st) (initialised st)
                                             (peerStates st pid) init' (s' , outs))
                      → peerStates (StepPeer-post {pre = st} (step-honest stP)) pid ≡ s
                      → s ≡ s'
 eventProcessorPostSt _ _ ps≡s = trans (sym ps≡s) override-target-≡

 Step*-mono : ∀{e e'}{st : SystemState e}{st' : SystemState e'}
            → Step* st st' → e ≤ e'
 Step*-mono step-0 = ≤-refl
 Step*-mono (step-s tr (step-peer _)) = Step*-mono tr
 Step*-mono (step-s tr (step-epoch _)) = ≤-step (Step*-mono tr)

 MsgWithSig∈-Step* : ∀{e e' sig pk}{st : SystemState e}{st' : SystemState e'}
                   → Step* st st'
                   → MsgWithSig∈ pk sig (msgPool st)
                   → MsgWithSig∈ pk sig (msgPool st')
 MsgWithSig∈-Step* step-0        msig = msig
 MsgWithSig∈-Step* (step-s tr (step-epoch _)) msig
   = MsgWithSig∈-Step* tr msig
 MsgWithSig∈-Step* (step-s tr (step-peer ps)) msig
   = MsgWithSig∈-++ʳ (MsgWithSig∈-Step* tr msig)

 MsgWithSig∈-Step*-part : ∀{e e' sig pk}{st : SystemState e}{st' : SystemState e'}
                        → (tr   : Step* st st')
                        → (msig : MsgWithSig∈ pk sig (msgPool st))
                        → msgPart msig ≡ msgPart (MsgWithSig∈-Step* tr msig)
 MsgWithSig∈-Step*-part step-0        msig = refl
 MsgWithSig∈-Step*-part (step-s tr (step-epoch _)) msig
   = MsgWithSig∈-Step*-part tr msig
 MsgWithSig∈-Step*-part (step-s tr (step-peer ps)) msig
   = MsgWithSig∈-Step*-part tr msig

 MsgWithSig∈-Step*-sender : ∀{e e' sig pk}{st : SystemState e}{st' : SystemState e'}
                          → (tr   : Step* st st')
                          → (msig : MsgWithSig∈ pk sig (msgPool st))
                          → msgSender msig ≡ msgSender (MsgWithSig∈-Step* tr msig)
 MsgWithSig∈-Step*-sender step-0        msig = refl
 MsgWithSig∈-Step*-sender (step-s tr (step-epoch _)) msig
   = MsgWithSig∈-Step*-sender tr msig
 MsgWithSig∈-Step*-sender (step-s tr (step-peer ps)) msig
   = MsgWithSig∈-Step*-sender tr msig

 ------------------------------------------

 -- Type synonym to express a relation over system states;
 SystemStateRel : (∀{e e'} → SystemState e → SystemState e' → Set ℓ-EC) → Set (ℓ+1 ℓ-EC)
 SystemStateRel P = ∀{e e'}{st : SystemState e}{st' : SystemState e'} → P st st' → Set ℓ-EC

 -- Just like Data.List.Any maps a predicate over elements to a predicate over lists,
 -- Any-step maps a relation over steps to a relation over steps in a trace.
 data Any-Step (P : SystemStateRel Step) : SystemStateRel Step* where
  step-here  : ∀{e e' e''}{fst : SystemState e}{pre : SystemState e'}{post : SystemState e''}
             → (cont : Step* fst pre)
             → {this : Step pre post}(prf  : P this)
             → Any-Step P (step-s cont this)

  step-there : ∀{e e' e''}{fst : SystemState e}{pre : SystemState e'}{post : SystemState e''}
             → {cont : Step* fst pre}
             → {this : Step pre post}
             → (prf  : Any-Step P cont)
             → Any-Step P (step-s cont this)

 Any-Step-⇒ : ∀ {P Q : SystemStateRel Step}
            → (∀ {e e'}{pre : SystemState e}{post : SystemState e'} → (x : Step pre post) → P {e} {e'} x → Q {e} {e'} x)
            → ∀ {e e' fst lst} {tr : Step* {e} {e'} fst lst}
            → Any-Step P tr
            → Any-Step Q tr
 Any-Step-⇒ p⇒q (step-here cont {this} prf) = step-here cont (p⇒q this prf)
 Any-Step-⇒ p⇒q (step-there anyStep) = step-there (Any-Step-⇒ p⇒q anyStep)

 Any-Step-elim
   : ∀{e₀ e₁}{st₀ : SystemState e₀}{st₁ : SystemState e₁}{P : SystemStateRel Step}{Q : Set ℓ-EC}
   → {r : Step* st₀ st₁}
   → (P⇒Q : ∀{d d'}{s : SystemState d}{s' : SystemState d'}{st : Step s s'}
          → P st → Step* s' st₁ → Q)
   → Any-Step P r → Q
 Any-Step-elim P⇒Q (step-here cont prf)
   = P⇒Q prf step-0
 Any-Step-elim P⇒Q (step-there {this = this} f)
   = Any-Step-elim (λ p s → P⇒Q p (step-s s this)) f

 ------------------------------------------

 module _ (P : ∀{e} → SystemState e → Set) where

   Step*-Step-fold : (∀{e}{st : SystemState e}
                        → ReachableSystemState st
                        → (𝓔 : EpochConfigFor e)
                        → P st
                        → P (pushEpoch 𝓔 st))
                   → (∀{e pid st' outs}{st : SystemState e}
                        → ReachableSystemState st
                        → (pstep : StepPeer st pid st' outs)
                        → P st
                        → P (StepPeer-post pstep))
                   → P initialState
                   → ∀{e}{st : SystemState e}
                   → (tr : ReachableSystemState st) → P st
   Step*-Step-fold fe fs p₀ step-0 = p₀
   Step*-Step-fold fe fs p₀ (step-s tr (step-epoch 𝓔)) = fe tr 𝓔 (Step*-Step-fold fe fs p₀ tr)
   Step*-Step-fold fe fs p₀ (step-s tr (step-peer p)) = fs tr p (Step*-Step-fold fe fs p₀ tr)


module STLC.Coquand.Renaming where

open import STLC.Syntax public
open import Category

--------------------------------------------------------------------------------

-- Renamings
infix 4 _∋⋆_
data _∋⋆_ : 𝒞 → 𝒞 → Set
  where
    ∅   : ∀ {Γ} → Γ ∋⋆ ∅

    _,_ : ∀ {Γ Γ′ A} → (η : Γ′ ∋⋆ Γ) (i : Γ′ ∋ A)
                     → Γ′ ∋⋆ Γ , A

putᵣ : ∀ {Γ Γ′} → (∀ {A} → Γ ∋ A → Γ′ ∋ A) → Γ′ ∋⋆ Γ
putᵣ {∅}     f = ∅
putᵣ {Γ , A} f = putᵣ (λ i → f (suc i)) , f 0

getᵣ : ∀ {Γ Γ′ A} → Γ′ ∋⋆ Γ → Γ ∋ A → Γ′ ∋ A
getᵣ (η , i) zero    = i
getᵣ (η , i) (suc j) = getᵣ η j

wkᵣ : ∀ {A Γ Γ′} → Γ′ ∋⋆ Γ → Γ′ , A ∋⋆ Γ
wkᵣ ∅       = ∅
wkᵣ (η , i) = wkᵣ η , suc i

liftᵣ : ∀ {A Γ Γ′} → Γ′ ∋⋆ Γ → Γ′ , A ∋⋆ Γ , A
liftᵣ η = wkᵣ η , 0

idᵣ : ∀ {Γ} → Γ ∋⋆ Γ
idᵣ {∅}     = ∅
idᵣ {Γ , A} = liftᵣ idᵣ

ren : ∀ {Γ Γ′ A} → Γ′ ∋⋆ Γ → Γ ⊢ A → Γ′ ⊢ A
ren η (𝓋 i)   = 𝓋 (getᵣ η i)
ren η (ƛ M)   = ƛ (ren (liftᵣ η) M)
ren η (M ∙ N) = ren η M ∙ ren η N

wk : ∀ {B Γ A} → Γ ⊢ A → Γ , B ⊢ A
wk M = ren (wkᵣ idᵣ) M

-- NOTE: _○_ = getᵣ⋆
_○_ : ∀ {Γ Γ′ Γ″} → Γ″ ∋⋆ Γ′ → Γ′ ∋⋆ Γ → Γ″ ∋⋆ Γ
η₁ ○ ∅        = ∅
η₁ ○ (η₂ , i) = η₁ ○ η₂ , getᵣ η₁ i

--------------------------------------------------------------------------------

-- (wkgetᵣ)
natgetᵣ : ∀ {Γ Γ′ A B} → (η : Γ′ ∋⋆ Γ) (i : Γ ∋ A)
                       → getᵣ (wkᵣ {B} η) i ≡ (suc ∘ getᵣ η) i
natgetᵣ (η , j) zero    = refl
natgetᵣ (η , j) (suc i) = natgetᵣ η i

idgetᵣ : ∀ {Γ A} → (i : Γ ∋ A)
                 → getᵣ idᵣ i ≡ i
idgetᵣ zero    = refl
idgetᵣ (suc i) = natgetᵣ idᵣ i
               ⦙ suc & idgetᵣ i

idren : ∀ {Γ A} → (M : Γ ⊢ A)
                → ren idᵣ M ≡ M
idren (𝓋 i)   = 𝓋 & idgetᵣ i
idren (ƛ M)   = ƛ & idren M
idren (M ∙ N) = _∙_ & idren M
                    ⊗ idren N

--------------------------------------------------------------------------------

zap○ : ∀ {Γ Γ′ Γ″ A} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (i : Γ″ ∋ A)
                     → (η₁ , i) ○ wkᵣ η₂ ≡ η₁ ○ η₂
zap○ η₁ ∅        i = refl
zap○ η₁ (η₂ , j) i = (_, getᵣ η₁ j) & zap○ η₁ η₂ i

lid○ : ∀ {Γ Γ′} → (η : Γ′ ∋⋆ Γ)
                → idᵣ ○ η ≡ η
lid○ ∅       = refl
lid○ (η , i) = _,_ & lid○ η
                   ⊗ idgetᵣ i

rid○ : ∀ {Γ Γ′} → (η : Γ′ ∋⋆ Γ)
                → η ○ idᵣ ≡ η
rid○ {∅}     ∅       = refl
rid○ {Γ , A} (η , i) = (_, i) & ( zap○ η idᵣ i
                                ⦙ rid○ η
                                )

--------------------------------------------------------------------------------

get○ : ∀ {Γ Γ′ Γ″ A} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (i : Γ ∋ A)
                     → getᵣ (η₁ ○ η₂) i ≡ (getᵣ η₁ ∘ getᵣ η₂) i
get○ η₁ (η₂ , j) zero    = refl
get○ η₁ (η₂ , j) (suc i) = get○ η₁ η₂ i

wk○ : ∀ {Γ Γ′ Γ″ A} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ)
                    → wkᵣ {A} (η₁ ○ η₂) ≡ wkᵣ η₁ ○ η₂
wk○ η₁ ∅        = refl
wk○ η₁ (η₂ , i) = _,_ & wk○ η₁ η₂
                      ⊗ (natgetᵣ η₁ i ⁻¹)

lift○ : ∀ {Γ Γ′ Γ″ A} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ)
                      → liftᵣ {A} (η₁ ○ η₂) ≡ liftᵣ η₁ ○ liftᵣ η₂
lift○ η₁ η₂ = (_, 0) & ( wk○ η₁ η₂
                          ⦙ (zap○ (wkᵣ η₁) η₂ 0 ⁻¹)
                          )

wklid○ : ∀ {Γ Γ′ A} → (η : Γ′ ∋⋆ Γ)
                    → wkᵣ {A} idᵣ ○ η ≡ wkᵣ η
wklid○ η = wk○ idᵣ η ⁻¹
         ⦙ wkᵣ & lid○ η

wkrid○ : ∀ {Γ Γ′ A} → (η : Γ′ ∋⋆ Γ)
                    → wkᵣ {A} η ○ idᵣ ≡ wkᵣ η
wkrid○ η = wk○ η idᵣ ⁻¹
         ⦙ wkᵣ & rid○ η

liftwkrid○ : ∀ {Γ Γ′ A} → (η : Γ′ ∋⋆ Γ)
                        → liftᵣ {A} η ○ wkᵣ idᵣ ≡ wkᵣ η
liftwkrid○ η = zap○ (wkᵣ η) idᵣ 0 ⦙ wkrid○ η

mutual
  ren○ : ∀ {Γ Γ′ Γ″ A} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (M : Γ ⊢ A)
                       → ren (η₁ ○ η₂) M ≡ (ren η₁ ∘ ren η₂) M
  ren○ η₁ η₂ (𝓋 i)   = 𝓋 & get○ η₁ η₂ i
  ren○ η₁ η₂ (ƛ M)   = ƛ & renlift○ η₁ η₂ M
  ren○ η₁ η₂ (M ∙ N) = _∙_ & ren○ η₁ η₂ M
                           ⊗ ren○ η₁ η₂ N

  renlift○ : ∀ {Γ Γ′ Γ″ A B} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (M : Γ , B ⊢ A)
                             → ren (liftᵣ {B} (η₁ ○ η₂)) M ≡
                                (ren (liftᵣ η₁) ∘ ren (liftᵣ η₂)) M
  renlift○ η₁ η₂ M = (λ η′ → ren η′ M) & lift○ η₁ η₂
                   ⦙ ren○ (liftᵣ η₁) (liftᵣ η₂) M

renwk○ : ∀ {Γ Γ′ Γ″ A B} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (M : Γ ⊢ A)
                         → ren (wkᵣ {B} (η₁ ○ η₂)) M ≡
                            (ren (wkᵣ η₁) ∘ ren η₂) M
renwk○ η₁ η₂ M = (λ η′ → ren η′ M) & wk○ η₁ η₂
               ⦙ ren○ (wkᵣ η₁) η₂ M

renliftwk○ : ∀ {Γ Γ′ Γ″ A B} → (η₁ : Γ″ ∋⋆ Γ′) (η₂ : Γ′ ∋⋆ Γ) (M : Γ ⊢ A)
                             → ren (wkᵣ {B} (η₁ ○ η₂)) M ≡
                                (ren (liftᵣ η₁) ∘ ren (wkᵣ η₂)) M
renliftwk○ η₁ η₂ M = (λ η′ → ren η′ M) & ( wk○ η₁ η₂
                                          ⦙ zap○ (wkᵣ η₁) η₂ 0 ⁻¹
                                          )
                   ⦙ ren○ (liftᵣ η₁) (wkᵣ η₂) M

--------------------------------------------------------------------------------

assoc○ : ∀ {Γ Γ′ Γ″ Γ‴} → (η₁ : Γ‴ ∋⋆ Γ″) (η₂ : Γ″ ∋⋆ Γ′) (η₃ : Γ′ ∋⋆ Γ)
                        → η₁ ○ (η₂ ○ η₃) ≡ (η₁ ○ η₂) ○ η₃
assoc○ η₁ η₂ ∅        = refl
assoc○ η₁ η₂ (η₃ , i) = _,_ & assoc○ η₁ η₂ η₃
                            ⊗ (get○ η₁ η₂ i ⁻¹)

--------------------------------------------------------------------------------

𝗥𝗲𝗻 : Category 𝒞 _∋⋆_
𝗥𝗲𝗻 =
  record
    { idₓ    = idᵣ
    ; _⋄_    = flip _○_
    ; lid⋄   = rid○
    ; rid⋄   = lid○
    ; assoc⋄ = assoc○
    }

getᵣPsh : 𝒯 → Presheaf₀ 𝗥𝗲𝗻
getᵣPsh A =
  record
    { Fₓ  = _∋ A
    ; F   = getᵣ
    ; idF = fext! idgetᵣ
    ; F⋄  = λ η₂ η₁ → fext! (get○ η₁ η₂)
    }

renPsh : 𝒯 → Presheaf₀ 𝗥𝗲𝗻
renPsh A =
  record
    { Fₓ  = _⊢ A
    ; F   = ren
    ; idF = fext! idren
    ; F⋄  = λ η₂ η₁ → fext! (ren○ η₁ η₂)
    }

--------------------------------------------------------------------------------