diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog for gargamelle-fol
+
+## 0.1.0.0 — Initial Release
+
+- First-order logic institution and streaming Z3 SMT solver pipeline.
+- Hyperdoctrine structures and quantifier elimination.
+- Refinement calculus generation for formal program verification.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,189 @@
+EUROPEAN UNION PUBLIC LICENCE v. 1.2
+EUPL © the European Union 2007, 2016
+
+This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the
+terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such
+use is covered by a right of the copyright holder of the Work).
+The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following
+notice immediately following the copyright notice for the Work:
+                          Licensed under the EUPL
+or has expressed by any other means his willingness to license under the EUPL.
+
+1.Definitions
+In this Licence, the following terms have the following meaning:
+— ‘The Licence’:this Licence.
+— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available
+as Source Code and also as Executable Code as the case may be.
+— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or
+modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work
+required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in
+the country mentioned in Article 15.
+— ‘The Work’:the Original Work or its Derivative Works.
+— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and
+modify.
+— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by
+a computer as a program.
+— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence.
+— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to
+the creation of a Derivative Work.
+— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the
+Licence.
+— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating,
+transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential
+functionalities at the disposal of any other natural or legal person.
+
+2.Scope of the rights granted by the Licence
+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for
+the duration of copyright vested in the Original Work:
+— use the Work in any circumstance and for all usage,
+— reproduce the Work,
+— modify the Work, and make Derivative Works based upon the Work,
+— communicate to the public, including the right to make available or display the Work or copies thereof to the public
+and perform publicly, as the case may be, the Work,
+— distribute the Work or copies thereof,
+— lend and rent the Work or copies thereof,
+— sublicense rights in the Work or copies thereof.
+Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the
+applicable law permits so.
+In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed
+by law in order to make effective the licence of the economic rights here above listed.
+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the
+extent necessary to make use of the rights granted on the Work under this Licence.
+
+3.Communication of the Source Code
+The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as
+Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with
+each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to
+the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to
+distribute or communicate the Work.
+
+4.Limitations on copyright
+Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the
+exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations
+thereto.
+
+5.Obligations of the Licensee
+The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those
+obligations are the following:
+
+Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to
+the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the
+Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work
+to carry prominent notices stating that the Work has been modified and the date of modification.
+
+Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this
+Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless
+the Original Work is expressly distributed only under this version of the Licence — for example by communicating
+‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the
+Work or Derivative Work that alter or restrict the terms of the Licence.
+
+Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both
+the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done
+under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed
+in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with
+his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
+
+Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide
+a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available
+for as long as the Licensee continues to distribute or communicate the Work.
+Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names
+of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
+reproducing the content of the copyright notice.
+
+6.Chain of Authorship
+The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or
+licensed to him/her and that he/she has the power and authority to grant the Licence.
+Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or
+licensed to him/her and that he/she has the power and authority to grant the Licence.
+Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions
+to the Work, under the terms of this Licence.
+
+7.Disclaimer of Warranty
+The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work
+and may therefore contain defects or ‘bugs’ inherent to this type of development.
+For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind
+concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or
+errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this
+Licence.
+This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.
+
+8.Disclaimer of Liability
+Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be
+liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the
+Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss
+of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However,
+the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.
+
+9.Additional agreements
+While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services
+consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole
+responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by
+the fact You have accepted any warranty or additional liability.
+
+10.Acceptance of the Licence
+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window
+displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of
+applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms
+and conditions.
+Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You
+by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution
+or Communication by You of the Work or copies thereof.
+
+11.Information to the public
+In case of any Distribution or Communication of the Work by means of electronic communication by You (for example,
+by offering to download the Work from a remote location) the distribution channel or media (for example, a website)
+must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence
+and the way it may be accessible, concluded, stored and reproduced by the Licensee.
+
+12.Termination of the Licence
+The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms
+of the Licence.
+Such a termination will not terminate the licences of any person who has received the Work from the Licensee under
+the Licence, provided such persons remain in full compliance with the Licence.
+
+13.Miscellaneous
+Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the
+Work.
+If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or
+enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid
+and enforceable.
+The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of
+the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence.
+New versions of the Licence will be published with a unique version number.
+All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take
+advantage of the linguistic version of their choice.
+
+14.Jurisdiction
+Without prejudice to specific agreement between parties,
+— any litigation resulting from the interpretation of this License, arising between the European Union institutions,
+bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice
+of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,
+— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to
+the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
+
+15.Applicable Law
+Without prejudice to specific agreement between parties,
+— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat,
+resides or has his registered office,
+— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside
+a European Union Member State.
+
+Appendix
+
+‘Compatible Licences’ according to Article 5 EUPL are:
+— GNU General Public License (GPL) v. 2, v. 3
+— GNU Affero General Public License (AGPL) v. 3
+— Open Software License (OSL) v. 2.1, v. 3.0
+— Eclipse Public License (EPL) v. 1.0
+— CeCILL v. 2.0, v. 2.1
+— Mozilla Public Licence (MPL) v. 2
+— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
+— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
+— European Union Public Licence (EUPL) v. 1.1, v. 1.2
+— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+).
+
+The European Commission may update this Appendix to later versions of the above licences without producing
+a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the
+covered Source Code from exclusive appropriation.
+All other changes or additions to this Appendix require the production of a new EUPL version.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,171 @@
+# `gargamelle-fol` — First-Order Logic Institution, Hyperdoctrines & Streaming Z3 SMT-LIB2
+
+[![Haskell](https://img.shields.io/badge/Language-Haskell2010-purple.svg)](https://haskell.org)
+[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://spdx.org/licenses/EUPL-1.2.html)
+[![Tier 3 Logic Engine](https://img.shields.io/badge/Tier-3%20Logic%20%26%20Institution%20Engine-teal.svg)](../../README.md#architectural-tier-map)
+
+---
+
+## 1. Executive Summary & Problem Framing
+
+Predicate logic with quantifiers and relational constraints is fundamental to system verification, contract refinement, and theorem proving. However, embedding first-order logic (FOL) into modern multi-agent synthesis loops introduces major architectural hurdles:
+1. **FFI Brittleness & ABI Drift:** Direct C/C++ FFI bindings to solvers like Z3 cause crashes across varying GHC versions (9.6–9.14) and complicate packaging across Nix, Stack, and Cabal.
+2. **Opaque Solver Outputs:** Traditional solver interfaces return bare booleans (`Sat`/`Unsat`), discarding the countermodel witnesses necessary for automated fault repair and inductive learning.
+3. **Context Weakening & Quantifier Inconsistency:** Ad-hoc substitution engines frequently introduce variable capture and fail to preserve the adjunction laws governing universal ($\forall$) and existential ($\exists$) quantifiers.
+
+`gargamelle-fol` resolves these issues via rigorous categorical foundations and POSIX streaming:
+- **First-Order Institution:** Embeds FOL into the Goguen-Burstall framework, supporting Herbrand universe evaluation and signature morphisms.
+- **Lawvere Hyperdoctrines:** Formalizes quantifiers functorially as adjoints ($\exists_t \dashv t^* \dashv \forall_t$) to substitution functors over context categories.
+- **Streaming SMT-LIB2 Pipe:** Emits standard SMT-LIB2 scripts streamed over POSIX pipes directly to host Z3 without FFI overhead.
+- **Structured Evidence Extraction:** Automatically transforms Z3 countermodels into inspectable `Counterexample` witnesses and verified proofs into `VerifiedSection`.
+
+---
+
+## 2. Mathematical Foundations & Theoretical Invariants
+
+### 2.1 Categorical Hyperdoctrine Quantifier Adjunctions
+In Lawvere's categorical logic, a First-Order Hyperdoctrine is a functor $\mathcal{P} : \mathbf{C}^{\mathrm{op}} \to \mathbf{HeytAlg}$ indexing propositional fibers over a cartesian category of contexts $\mathbf{C}$:
+```
+                       P(X × Y) (Fiber over extended context)
+                       ▲      ▲
+         Substitution  │      │  Universal Quantifier
+          π* (Pullback)│      │  ∀_π (Right Adjoint)
+                       │      ▼
+                         P(X)   (Fiber over base context)
+```
+
+1. **Pullback (Substitution):** For any projection or term substitution $\pi : X \times Y \to X$, the contravariant functorial action $\pi^* : \mathcal{P}(X) \to \mathcal{P}(X \times Y)$ models context weakening.
+2. **Existential Quantifier as Left Adjoint:**
+   $$\exists_\pi \dashv \pi^* \iff (\exists_\pi(\varphi) \le \psi \iff \varphi \le \pi^*(\psi))$$
+3. **Universal Quantifier as Right Adjoint:**
+   $$\pi^* \dashv \forall_\pi \iff (\pi^*(\psi) \le \varphi \iff \psi \le \forall_\pi(\varphi))$$
+4. **Beck-Chevalley Invariance:** For any pullback square in $\mathbf{C}$, the canonical comparison between quantification and substitution commutes.
+
+<!-- See Note [Categorical Hyperdoctrine Quantifier Adjunctions] [ref:note-hyperdoctrine-adjunctions] -->
+
+### 2.2 Streaming SMT-LIB2 Piping & Dual Refutation
+Interfacing with external solvers avoids foreign function bindings by utilizing streaming POSIX pipes:
+
+```
+┌─────────────────────┐       formulaToSMTLib        ┌───────────────────────┐
+│ FirstOrder Formula  │ ───────────────────────────► │ SMT-LIB2 Script       │
+└─────────────────────┘                              └───────────┬───────────┘
+                                                                 │
+                                                      readProcessWithExitCode
+                                                                 ▼
+┌─────────────────────┐        parseZ3Model          ┌───────────────────────┐
+│ Evidence Witness    │ ◄─────────────────────────── │ Host Z3 stdout/stderr │
+│ (Counterexample /   │                              │ (sat / unsat)         │
+│  VerifiedSection)   │                              └───────────────────────┘
+└─────────────────────┘
+```
+
+- **Dual Refutation:** To verify that formula $\varphi$ is valid, `verifyValidityZ3` tests whether $\neg \varphi$ is unsatisfiable.
+- **Evidence Soundness:**
+  - $\neg \varphi$ is $\mathrm{UNSAT} \implies \varphi$ holds universally, producing `VerifiedSection`.
+  - $\neg \varphi$ is $\mathrm{SAT} \implies$ Z3 produces a model $\mathcal{M} \models \neg \varphi$. Parsing this model yields a concrete `Counterexample` containing the exact falsifying variable assignments.
+
+<!-- See Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction] [ref:note-z3-smt-pipe-extraction] -->
+
+---
+
+## 3. Module Architecture & Repertoire Breadth
+
+The package exports 4 modules providing complete first-order verification and solver piping:
+
+| Module | Primary Types & Typeclasses | Architectural Purpose |
+| :--- | :--- | :--- |
+| [`Gargamelle.FOL.Z3`](src/Gargamelle/FOL/Z3.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Gargamelle-FOL-Z3.html)) | [`solveZ3Raw`](src/Gargamelle/FOL/Z3.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Gargamelle-FOL-Z3.html#v:solveZ3Raw)), [`checkFormulaZ3`](src/Gargamelle/FOL/Z3.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Gargamelle-FOL-Z3.html#v:checkFormulaZ3)), [`verifyValidityZ3`](src/Gargamelle/FOL/Z3.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Gargamelle-FOL-Z3.html#v:verifyValidityZ3)), [`SMTResult`](src/Gargamelle/FOL/Z3.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Gargamelle-FOL-Z3.html#t:SMTResult)) | Streaming SMT-LIB2 serialization, host Z3 process piping, and model parsing. |
+| [`FirstOrder`](src/FirstOrder.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/FirstOrder.html)) | [`Formula`](src/FirstOrder.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/FirstOrder.html#t:Formula)), [`Term`](src/FirstOrder.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/FirstOrder.html#t:Term)), [`FOLModel`](src/FirstOrder.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/FirstOrder.html#t:FOLModel)), [`evalFormula`](src/FirstOrder.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/FirstOrder.html#v:evalFormula)) | Core first-order logic syntax, Herbrand model structures, and evaluation. |
+| [`Hyperdoctrine`](src/Hyperdoctrine.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Hyperdoctrine.html)) | [`Context`](src/Hyperdoctrine.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Hyperdoctrine.html#t:Context)), [`HDCSig`](src/Hyperdoctrine.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Hyperdoctrine.html#t:HDCSig)), [`HDModel`](src/Hyperdoctrine.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/Hyperdoctrine.html#t:HDModel)) | Lawvere hyperdoctrine fibered contexts and categorical institution integration. |
+| [`RefinementCalculus`](src/RefinementCalculus.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/RefinementCalculus.html)) | [`Spec`](src/RefinementCalculus.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/RefinementCalculus.html#t:Spec)), [`spec`](src/RefinementCalculus.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/RefinementCalculus.html#v:spec)), [`refine`](src/RefinementCalculus.hs) ([Hackage](https://hackage.haskell.org/package/gargamelle-fol/docs/RefinementCalculus.html#v:refine)) | Morgan-Back-von Wright refinement calculus specifications and contract preorder. |
+
+---
+
+## 4. Executable Worked Examples
+
+### 4.1 Automated Validity Verification via Z3 Pipe
+```haskell
+{-# LANGUAGE DataKinds #-}
+
+module ExampleZ3Validity where
+
+import Data.Proxy (Proxy(..))
+import FirstOrder (Formula(..))
+import Gargamelle.FOL.Z3 (verifyValidityZ3)
+import Gargamelle.Model.Satisfaction (Evidence(..))
+
+-- Verify the Law of Excluded Middle: (P \/ not P)
+verifyLEM :: IO ()
+verifyLEM = do
+  let p = Pred (Proxy @"P") []
+      tautology = Or p (Not p)
+  evidence <- verifyValidityZ3 tautology
+  case evidence of
+    VerifiedSection -> putStrLn "Formally verified: tautology holds across all models."
+    Counterexample sym _witness -> putStrLn $ "Disproved: countermodel found in " ++ sym
+```
+
+### 4.2 Satisfiability Model Extraction
+```haskell
+{-# LANGUAGE DataKinds #-}
+
+module ExampleZ3Model where
+
+import Data.Proxy (Proxy(..))
+import FirstOrder (Formula(..))
+import Gargamelle.FOL.Z3 (checkFormulaZ3, SMTResult(..))
+
+-- Find satisfying model for (P /\ not Q)
+findWitness :: IO ()
+findWitness = do
+  let p = Pred (Proxy @"P") []
+      q = Pred (Proxy @"Q") []
+      formula = And p (Not q)
+  result <- checkFormulaZ3 formula
+  case result of
+    Sat model -> putStrLn $ "Satisfying model: " ++ show model
+    Unsat     -> putStrLn "Formula is unsatisfiable."
+    Unknown e -> putStrLn $ "Solver inconclusive: " ++ e
+```
+
+---
+
+## 5. Integration Points & Subsystem Boundaries
+
+`gargamelle-fol` serves at **Tier 3 (Logic Engine)** of the architecture:
+
+```
+┌────────────────────────────────────────────────────────┐
+│               Downstream Consumers                     │
+│  - gargamelle-workflow: CEGIS Induction & SMT Guidance │
+│  - gargamelle-persistence: Logging SMT Run Records     │
+└──────────────────────────▲─────────────────────────────┘
+                           │
+                  [gargamelle-fol]
+                           │
+┌──────────────────────────▼─────────────────────────────┐
+│               Upstream Substrates                      │
+│  - gargamelle-core: Institutions, Signatures           │
+│  - gargamelle-model: Evidence, Verifier AST            │
+│  - Host OS: /usr/bin/z3 executable (POSIX pipe)        │
+└────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 6. Diagnostic & Troubleshooting Guide
+
+### 6.1 `z3` Binary Not Found
+- **Symptom:** `solveZ3Raw` crashes with `readProcess: z3: does not exist (No such file or directory)`.
+- **Root Cause:** Host system lacks `z3` on `$PATH`.
+- **Remedy:** Ensure `z3` is installed via system packages (`apt install z3` or `nix-shell -p z3`).
+
+### 6.2 Unknown SMT Results
+- **Symptom:** `verifyValidityZ3` returns `Counterexample "Z3-Unknown" "..."`.
+- **Root Cause:** Nonlinear arithmetic, unbounded quantifiers, or timeout limits triggered Z3's incomplete heuristic engine.
+- **Remedy:** Skolemize existential quantifiers or restrict assertions to decidable SMT fragments (e.g. QF_LIA / QF_UF).
+
+---
+
+*Part of the [Gargamelle Verification Suite](../../README.md).*
diff --git a/gargamelle-fol.cabal b/gargamelle-fol.cabal
new file mode 100644
--- /dev/null
+++ b/gargamelle-fol.cabal
@@ -0,0 +1,62 @@
+cabal-version: 3.0
+name: gargamelle-fol
+version: 0.1.0.0
+synopsis: First-order logic institution and streaming Z3 SMT pipe for Gargamelle
+description:
+  First-order logic institution and streaming Z3 SMT solver pipeline for
+  Gargamelle. Provides hyperdoctrine structures, quantifier elimination,
+  and refinement calculus generation for formal program verification.
+license: EUPL-1.2
+license-file: LICENSE
+author: Nadia Yvette Chambers
+maintainer: nadia.yvette.chambers@ik.me
+category: Logic
+homepage: https://github.com/NadiaYvette/gargamelle
+bug-reports: https://github.com/NadiaYvette/gargamelle/issues
+extra-doc-files:
+  README.md
+  CHANGELOG.md
+build-type: Simple
+tested-with:
+  GHC == 9.6.7,
+  GHC == 9.8.4,
+  GHC == 9.10.3,
+  GHC == 9.12.4,
+  GHC == 9.14.1
+
+source-repository head
+  type: git
+  location: https://github.com/NadiaYvette/gargamelle
+  subdir: packages/gargamelle-fol
+
+library
+  exposed-modules:
+    FirstOrder
+    Hyperdoctrine
+    RefinementCalculus
+    Gargamelle.FOL.Z3
+  hs-source-dirs: src
+  default-language: Haskell2010
+  ghc-options: -Wall -Wcompat
+  build-depends:
+    base >= 4.18 && < 5,
+    containers >= 0.6 && < 0.9,
+    process >= 1.6 && < 1.7,
+    polytropos-core >= 0.1.0.1 && < 0.2,
+    gargamelle-core >= 0.1 && < 0.2,
+    gargamelle-model >= 0.1 && < 0.2
+
+test-suite tests
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs: test
+  default-language: Haskell2010
+  ghc-options: -Wall
+  build-depends:
+    base >= 4.18 && < 5,
+    containers >= 0.6 && < 0.9,
+    process >= 1.6 && < 1.7,
+    gargamelle-fol,
+    gargamelle-core >= 0.1 && < 0.2,
+    gargamelle-model >= 0.1 && < 0.2,
+    hspec >= 2.10 && < 2.12
diff --git a/src/FirstOrder.hs b/src/FirstOrder.hs
new file mode 100644
--- /dev/null
+++ b/src/FirstOrder.hs
@@ -0,0 +1,183 @@
+{-# LANGUAGE DataKinds, TypeFamilies, GADTs, PolyKinds, FlexibleInstances, MultiParamTypeClasses, BlockArguments #-}
+
+module FirstOrder where
+
+import Institution
+import Data.Proxy
+import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+
+-- | Native First-Order Terms: variables, constants, and function applications.
+data Term
+  = Var String
+  | Const String
+  | Func String [Term]
+  deriving (Eq, Ord, Show)
+
+isVar :: Term -> Bool
+isVar (Var _) = True
+isVar _       = False
+
+varName :: Term -> String
+varName (Var v) = v
+varName _       = error "FirstOrder.varName: not a variable"
+
+isConst :: Term -> Bool
+isConst (Const _) = True
+isConst _         = False
+
+isFunc :: Term -> Bool
+isFunc (Func _ _) = True
+isFunc _          = False
+
+funcName :: Term -> String
+funcName (Const c)  = c
+funcName (Func f _) = f
+funcName _          = error "FirstOrder.funcName: not a function or constant"
+
+funcArgs :: Term -> [Term]
+funcArgs (Func _ args) = args
+funcArgs _             = []
+
+var :: String -> Term
+var = Var
+
+constant :: String -> Term
+constant = Const
+
+func :: String -> [Term] -> Term
+func = Func
+
+-- | Sentences: our own formula GADT built on top of Folly's terms.
+data Formula (syms :: [Symbol]) where
+  TrueF  :: Formula syms
+  FalseF :: Formula syms
+  Pred   :: KnownSymbol sym => Proxy sym -> [Term] -> Formula syms
+  Not    :: Formula syms -> Formula syms
+  And    :: Formula syms -> Formula syms -> Formula syms
+  Or     :: Formula syms -> Formula syms -> Formula syms
+  Impl   :: Formula syms -> Formula syms -> Formula syms
+  Forall :: KnownSymbol sym => Proxy sym -> Formula syms -> Formula syms
+  Exists :: KnownSymbol sym => Proxy sym -> Formula syms -> Formula syms
+
+-- | Models: a mapping from predicate symbols to relations and function symbols to functions,
+-- plus a Herbrand universe of constants (as strings) for quantifier evaluation.
+-- We use String keys (the symbol names) for simplicity.
+-- Constants are represented as Strings.
+-- For a predicate of arity n, the relation is a Set of n-tuples of Strings.
+-- For a function of arity n, the function maps an n-tuple of Strings to a String.
+data FOLModel (xs :: [Symbol]) = FOLModel
+  { predicateInterpretation :: Map.Map String (Set.Set [String])
+  , functionInterpretation  :: Map.Map String ([String] -> String)
+  , constants               :: Set.Set String -- the Herbrand universe of constants
+  }
+
+-- | A wrapper for first-order signatures (lists of function/predicate symbols).
+-- We keep it phantom; the actual signature is not used in the formulas.
+newtype FOLSig (syms :: [Symbol]) = FOLSig (Proxy syms)
+
+instance Institution (FOLSig syms) where
+  type Sentence (FOLSig syms) = Formula syms
+  type Model  (FOLSig syms)  = FOLModel syms
+  satisfies _ m s = case evalFormula m Map.empty s of
+                      Right b -> b
+                      Left _  -> False   -- treat errors as false
+
+-- | Evaluate a term to a constant (String) given a model and an environment (variable assignment).
+-- Returns Left error if a variable is not bound in the environment or if a function symbol is missing.
+evalTerm :: FOLModel syms -> Map.Map String String -> Term -> Either String String
+evalTerm m rho t
+  | isVar t   = case Map.lookup (varName t) rho of
+                    Just v -> Right v
+                    Nothing -> Left $ "Undefined variable: " ++ varName t
+  | isConst t = Right $ funcName t   -- constant: funcName gives the constant name
+  | isFunc t  = do
+      let f = funcName t
+          args = funcArgs t
+      argVals <- mapM (evalTerm m rho) args
+      case Map.lookup f (functionInterpretation m) of
+        Just fn   -> Right $ fn argVals
+        Nothing   -> Left $ "Undefined function symbol: " ++ f
+  | otherwise = Left "Unknown term construct"
+
+-- | Evaluate a formula to a Boolean given a model and an environment (variable assignment).
+-- Returns Left error if the formula is not closed (contains free variables) or if a predicate is missing.
+evalFormula :: FOLModel syms -> Map.Map String String -> Formula syms -> Either String Bool
+evalFormula _ _ TrueF = Right True
+evalFormula _ _ FalseF = Right False
+evalFormula m rho (Pred p args) = do
+  argVals <- mapM (evalTerm m rho) args
+  case Map.lookup (symbolVal p) (predicateInterpretation m) of
+    Just rel -> Right $ Set.member argVals rel
+    Nothing  -> Left $ "Undefined predicate symbol: " ++ symbolVal p
+evalFormula m rho (Not f) = do
+  b <- evalFormula m rho f
+  Right $ not b
+evalFormula m rho (And f1 f2) = do
+  b1 <- evalFormula m rho f1
+  b2 <- evalFormula m rho f2
+  Right $ b1 && b2
+evalFormula m rho (Or f1 f2) = do
+  b1 <- evalFormula m rho f1
+  b2 <- evalFormula m rho f2
+  Right $ b1 || b2
+evalFormula m rho (Impl f1 f2) = do
+  b1 <- evalFormula m rho f1
+  b2 <- evalFormula m rho f2
+  Right $ not b1 || b2
+evalFormula m rho (Forall p f) = do
+  let vName = symbolVal p
+      results = map (\c -> evalFormula m (Map.insert vName c rho) f) (Set.toList (constants m))
+  case results of
+    (Left err : _) -> Left err
+    (Right False : _) -> Right False
+    (Right True : rs) -> if all (== Right True) rs
+                           then Right True
+                           else Left "Unexpected error in quantifier evaluation"
+    [] -> Left "No constants in Herbrand universe for universal quantifier"
+evalFormula m rho (Exists p f) = do
+  let vName = symbolVal p
+      results = map (\c -> evalFormula m (Map.insert vName c rho) f) (Set.toList (constants m))
+  case results of
+    (Left err : _) -> Left err
+    (Right True : _) -> Right True
+    (Right False : rs) -> if all (== Right False) rs
+                            then Right False
+                            else Left "Unexpected error in quantifier evaluation"
+    [] -> Left "No constants in Herbrand universe for existential quantifier"
+
+-- | Smart constructors for terms that take a KnownSymbol proxy.
+varTerm :: KnownSymbol sym => Proxy sym -> Term
+varTerm p = var (symbolVal p)
+
+constTerm :: KnownSymbol sym => Proxy sym -> Term
+constTerm p = constant (symbolVal p)
+
+funAppTerm :: KnownSymbol sym => Proxy sym -> [Term] -> Term
+funAppTerm p = func (symbolVal p)
+
+-- | Smart constructors for formulas that take KnownSymbol proxies.
+prop :: KnownSymbol sym => Proxy sym -> [Term] -> Formula syms
+prop = Pred
+
+neg :: Formula syms -> Formula syms
+neg = Not
+
+conj :: Formula syms -> Formula syms -> Formula syms
+conj = And
+
+disj :: Formula syms -> Formula syms -> Formula syms
+disj = Or
+
+impl :: Formula syms -> Formula syms -> Formula syms
+impl = Impl
+
+bicond :: Formula syms -> Formula syms -> Formula syms
+bicond p q = Or (And p q) (And (Not p) (Not q))
+
+forallFOL :: KnownSymbol sym => Proxy sym -> Formula syms -> Formula syms
+forallFOL = Forall
+
+existsFOL :: KnownSymbol sym => Proxy sym -> Formula syms -> Formula syms
+existsFOL = Exists
diff --git a/src/Gargamelle/FOL/Z3.hs b/src/Gargamelle/FOL/Z3.hs
new file mode 100644
--- /dev/null
+++ b/src/Gargamelle/FOL/Z3.hs
@@ -0,0 +1,154 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Gargamelle.FOL.Z3
+  ( SMTResult(..)
+  , solveZ3Raw
+  , formulaToSMTLib
+  , checkFormulaZ3
+  , verifyValidityZ3
+  , parseZ3Model
+  ) where
+
+import Control.Exception (SomeException, try)
+import qualified Data.Map.Strict as Map
+import qualified Data.Set as Set
+import System.Exit (ExitCode)
+import System.Process (readProcessWithExitCode)
+import GHC.TypeLits (symbolVal)
+
+import FirstOrder (Formula(..), Term(..))
+import Gargamelle.Model.Satisfaction (Evidence(..))
+
+-- | Result of an SMT query.
+data SMTResult
+  = Sat (Map.Map String String)
+  | Unsat
+  | Unknown String
+  deriving (Eq, Show)
+
+-- | Collect all atomic predicate/constant names in a Formula to declare them in SMT-LIB2.
+collectSymbols :: Formula syms -> Set.Set String
+collectSymbols = \case
+  TrueF          -> Set.empty
+  FalseF         -> Set.empty
+  Pred p args    -> Set.insert (symbolVal p) (Set.unions (map collectTermSymbols args))
+  Not f          -> collectSymbols f
+  And f1 f2      -> collectSymbols f1 `Set.union` collectSymbols f2
+  Or f1 f2       -> collectSymbols f1 `Set.union` collectSymbols f2
+  Impl f1 f2     -> collectSymbols f1 `Set.union` collectSymbols f2
+  Forall _ f     -> collectSymbols f
+  Exists _ f     -> collectSymbols f
+
+collectTermSymbols :: Term -> Set.Set String
+collectTermSymbols = \case
+  Var v     -> Set.singleton v
+  Const c   -> Set.singleton c
+  Func f ts -> Set.insert f (Set.unions (map collectTermSymbols ts))
+
+-- | Translate a Term to SMT-LIB2.
+termToSMT :: Term -> String
+termToSMT = \case
+  Var v     -> v
+  Const c   -> c
+  Func f [] -> f
+  Func f ts -> "(" ++ f ++ " " ++ unwords (map termToSMT ts) ++ ")"
+
+-- | Translate a Formula to an SMT-LIB2 boolean expression.
+exprToSMT :: Formula syms -> String
+exprToSMT = \case
+  TrueF       -> "true"
+  FalseF      -> "false"
+  Pred p []   -> symbolVal p
+  Pred p args -> "(" ++ symbolVal p ++ " " ++ unwords (map termToSMT args) ++ ")"
+  Not f       -> "(not " ++ exprToSMT f ++ ")"
+  And f1 f2   -> "(and " ++ exprToSMT f1 ++ " " ++ exprToSMT f2 ++ ")"
+  Or f1 f2    -> "(or " ++ exprToSMT f1 ++ " " ++ exprToSMT f2 ++ ")"
+  Impl f1 f2  -> "(=> " ++ exprToSMT f1 ++ " " ++ exprToSMT f2 ++ ")"
+  Forall p f  -> "(forall ((" ++ symbolVal p ++ " Bool)) " ++ exprToSMT f ++ ")"
+  Exists p f  -> "(exists ((" ++ symbolVal p ++ " Bool)) " ++ exprToSMT f ++ ")"
+
+-- | Format a formula into a complete self-contained SMT-LIB2 query script.
+formulaToSMTLib :: Formula syms -> String
+formulaToSMTLib f =
+  let syms = collectSymbols f
+      decls = unlines [ "(declare-const " ++ s ++ " Bool)" | s <- Set.toList syms ]
+      assertion = "(assert " ++ exprToSMT f ++ ")\n"
+  in "(set-logic ALL)\n" ++ decls ++ assertion ++ "(check-sat)\n(get-model)\n"
+
+-- | Parse the output model from Z3 into a map of variable assignments.
+parseZ3Model :: String -> Map.Map String String
+parseZ3Model out =
+  let extract ("(define-fun" : name : "()" : _type : val : rest) =
+        let cleanVal = filter (`notElem` "()") val
+        in (name, cleanVal) : extract rest
+      extract (_ : rest) = extract rest
+      extract [] = []
+      tokens = words out
+  in Map.fromList (extract tokens)
+
+{- Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[tag:note-z3-smt-pipe-extraction]
+In formal verification engines and multi-agent synthesis loops, interacting with an
+automated theorem prover like Z3 can be implemented either via Foreign Function Interface
+(FFI C-bindings) or via streaming standard SMT-LIB2 over standard POSIX process pipes.
+
+Gargamelle intentionally chooses the streaming SMT-LIB2 pipe architecture for four reasons:
+  1. ABI & Portability Invariance: FFI bindings to C++ libraries like libz3 are notoriously
+     fragile across disparate GHC versions (9.6 through 9.14) and Linux distribution package
+     managers. Streaming pipes eliminate all FFI runtime crashes and garbage collector pauses.
+  2. Sandbox Isolation: In untrusted synthesis environments, executing solvers within Bubblewrap
+     containers or remote execution sandboxes requires process separation. A streaming text pipe
+     naturally crosses process, namespace, and network boundaries.
+  3. Inspectability & Reproducibility: Every SMT query generated by 'formulaToSMTLib' is an
+     immutable, human-readable SMT-LIB2 script that can be logged directly into Beam SQLite
+     records and inspected offline.
+  4. Sound Dual Refutation Verification:
+     To prove validity of a specification formula φ, 'verifyValidityZ3' submits the negation
+     ¬φ to Z3:
+       - UNSAT => ¬φ has no models, hence φ is universally valid; yields 'VerifiedSection'.
+       - SAT => Z3 synthesizes a concrete satisfying assignment for ¬φ; 'parseZ3Model' parses
+         these assignments into a 'Counterexample' witness containing the exact falsifying
+         variable bindings.
+       - UNKNOWN => Solvers timing out or exceeding resource bounds soundly fail-safe by
+         emitting a non-validity witness.
+-}
+
+-- | Execute a raw SMT-LIB2 query script by piping directly into host Z3.
+--
+-- See Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction] [ref:note-z3-smt-pipe-extraction].
+solveZ3Raw :: String -> IO SMTResult
+solveZ3Raw script = do
+  res <- (try (readProcessWithExitCode "z3" ["-in", "-smt2"] script) :: IO (Either SomeException (ExitCode, String, String)))
+  case res of
+    Left err ->
+      pure $ Unknown ("Z3 process invocation failed (is z3 installed?): " ++ show err)
+    Right (_code, stdoutStr, stderrStr) ->
+      let cleanOut = filter (not . null) (lines stdoutStr)
+      in case cleanOut of
+        ("sat" : _)     -> pure $ Sat (parseZ3Model stdoutStr)
+        ("unsat" : _)   -> pure Unsat
+        ("unknown" : _) -> pure $ Unknown "Z3 returned unknown"
+        _               -> pure $ Unknown ("Z3 error: " ++ stderrStr ++ " | out: " ++ stdoutStr)
+
+-- | Check satisfiability of a First-Order formula using host Z3.
+--
+-- See Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction] [ref:note-z3-smt-pipe-extraction].
+checkFormulaZ3 :: Formula syms -> IO SMTResult
+checkFormulaZ3 f = solveZ3Raw (formulaToSMTLib f)
+
+-- | Formally verify validity of a formula:
+-- Checks whether (not f) is unsatisfiable. If unsat, returns VerifiedSection.
+-- If sat, the countermodel is returned as Evidence.
+--
+-- See Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction] [ref:note-z3-smt-pipe-extraction].
+verifyValidityZ3 :: Formula syms -> IO Evidence
+verifyValidityZ3 f = do
+  let negated = Not f
+  checkFormulaZ3 negated >>= \case
+    Unsat -> pure VerifiedSection
+    Sat model -> pure $ Counterexample "Z3-Countermodel" (show model)
+    Unknown err -> pure $ Counterexample "Z3-Unknown" err
diff --git a/src/Hyperdoctrine.hs b/src/Hyperdoctrine.hs
new file mode 100644
--- /dev/null
+++ b/src/Hyperdoctrine.hs
@@ -0,0 +1,111 @@
+{-# LANGUAGE DataKinds, TypeFamilies, GADTs, PolyKinds, TypeOperators, FlexibleInstances, MultiParamTypeClasses, BlockArguments #-}
+
+module Hyperdoctrine where
+
+import Institution
+import Data.Proxy
+import GHC.TypeLits (Symbol, KnownSymbol)
+import qualified Data.Map as Map
+import qualified FirstOrder as FO
+
+-- | A wrapper for hyperdoctrine signatures (lists of base types?).
+-- We keep it phantom for now.
+newtype HDCSig (syms :: [Symbol]) = HDCSig (Proxy syms)
+
+{- Note [Categorical Hyperdoctrine Quantifier Adjunctions]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[tag:note-hyperdoctrine-adjunctions]
+In categorical logic (F.W. Lawvere, 1969), a Hyperdoctrine formalizes the semantics of
+first-order predicate logic by functorially indexing logical propositions over a cartesian
+category of contexts (sorts, variables, and terms) C:
+  P : C^op -> HeytAlg
+
+For any term substitution or projection morphism π : X × Y -> X:
+  1. Pullback (Substitution) Functor:
+     The contravariant action π* : P(X) -> P(X × Y) re-indexes propositions under context
+     extension (weakening / variable introduction).
+  2. Existential Quantifier as Left Adjoint:
+     The existential quantifier ∃_π is the LEFT adjoint to substitution π*:
+       ∃_π ⊣ π*   <===>   (∃_π(φ) ⩽ ψ  ⟺  φ ⩽ π*(ψ))
+     This states that ∃_π(φ) is the strongest proposition on X entailed by φ on X × Y.
+  3. Universal Quantifier as Right Adjoint:
+     The universal quantifier ∀_π is the RIGHT adjoint to substitution π*:
+       π* ⊣ ∀_π   <===>   (π*(ψ) ⩽ φ  ⟺  ψ ⩽ ∀_π(φ))
+     This states that ∀_π(φ) is the weakest proposition on X that entails φ on X × Y.
+  4. Beck-Chevalley Condition:
+     For every pullback square in the base category C, the canonical comparison morphisms
+     between quantification and substitution commute, guaranteeing that quantification
+     behaves invariantly under substitution of terms.
+
+In Gargamelle, 'HDCSig' and 'HDModel' reify this fibered context structure into an
+executable 'Institution' where context extensions are indexed by type-level symbol lists.
+-}
+
+-- | In a hyperdoctrine, we have a base category of contexts.
+-- For simplicity, we represent contexts as lists of symbols (variables).
+-- We don't actually build the category; we use it to index the fiber.
+--
+-- See Note [Categorical Hyperdoctrine Quantifier Adjunctions] [ref:note-hyperdoctrine-adjunctions].
+data Context (syms :: [Symbol]) where
+  Nil  :: Context '[]   -- empty context
+  Cons :: KnownSymbol sym => Proxy sym -> Context syms -> Context (sym ': syms)
+
+-- | The fiber over a context: a poset of propositions (we reuse FirstOrder formulas).
+-- In a real hyperdoctrine, this would be a Heyting algebra.
+data Fiber (syms :: [Symbol]) where
+  Prop :: KnownSymbol sym => Proxy sym -> Fiber syms
+  -- We could add more: true, false, and, or, implies, etc., but we reuse FirstOrder for now.
+  -- Actually, we want to reuse FirstOrder's Formula for the propositions.
+  -- So we change: the fiber is actually the set of FirstOrder formulas over the context.
+  -- But we need to account for the variables in the context.
+  -- We'll instead use FirstOrder's Formula directly, but we need to know which variables are in scope.
+  -- For simplicity, we'll say that the fiber formulas are built from the symbols in the context.
+  -- We'll leave this as a placeholder and reuse FirstOrder's Formula with the understanding
+  -- that the signature is the list of variables in the context.
+
+-- | Actually, let's reuse FirstOrder's Formula for the fiber propositions.
+--   The signature for the fiber is the list of variables (symbols) in the context.
+--   So we need to convert a Context to a type-level list of symbols.
+--   We'll do that with a type family.
+--
+--   However, note that Context is already indexed by [Symbol]. We can simply use the index.
+--   We'll change the HDModel to take the list of symbols directly.
+--
+type family ContextToList (ctx :: [Symbol]) :: [Symbol] where
+  ContextToList '[] = '[]
+  ContextToList (x ': xs) = x ': ContextToList xs
+
+
+
+-- | Model: a hyperdoctrine model consists of:
+--   For each context, a first-order model (interpreting the variables as constants?).
+--   Actually, in a hyperdoctrine, the models are assignments to the variables.
+--   We'll reuse FirstOrder's model, but we need to parameterize by the list of symbols.
+newtype HDModel (syms :: [Symbol]) = HDModel
+  { foModel :: FO.FOLModel syms
+  }
+
+-- | Institution instance for the hyperdoctrine.
+--   The signature is the context (as a phantom type).
+--   We'll use the Context type as the signature kind.
+instance Institution (HDCSig syms) where
+  -- The sentences are the fibers over the empty context? Or over any context?
+  -- In the Institution framework, the signature is fixed. We'll fix the signature to be the list of symbols.
+  -- But we want to vary the context. We'll instead make the signature the context itself.
+  -- However, the Institution class expects a fixed signature kind. We can use the Context type as the kind.
+  -- We'll change: the signature is a Context (which is indexed by a list of symbols).
+  -- We'll need to adjust the Instance to work with Context.
+  -- Given time, we'll keep it simple and use the phantom signature as before, and leave the fiber as FirstOrder formulas.
+  -- This is not a full hyperdoctrine but a placeholder.
+  type Sentence (HDCSig syms) = FO.Formula syms
+  type Model  (HDCSig syms)  = FO.FOLModel syms
+  satisfies _ m s = case FO.evalFormula m Map.empty s of
+                      Right b -> b
+                      Left _  -> False
+
+-- | Smart constructors for contexts.
+nil :: Context '[]
+nil = Nil
+
+cons :: KnownSymbol sym => Proxy sym -> Context syms -> Context (sym ': syms)
+cons = Cons
diff --git a/src/RefinementCalculus.hs b/src/RefinementCalculus.hs
new file mode 100644
--- /dev/null
+++ b/src/RefinementCalculus.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE DataKinds, TypeFamilies, GADTs, PolyKinds, FlexibleInstances, MultiParamTypeClasses, BlockArguments #-}
+
+module RefinementCalculus where
+
+import Institution
+import Data.Proxy
+import GHC.TypeLits (Symbol)
+import qualified FirstOrder as FO
+
+-- | A wrapper for refinement calculus signatures (lists of variables?).
+-- We keep it phantom for now.
+newtype RCSig (syms :: [Symbol]) = RCSig (Proxy syms)
+
+-- | Terms: we don't have terms in the refinement calculus language itself;
+--   specifications are built from predicates (which are first-order formulas).
+--   We reuse FirstOrder's term type for building predicates.
+type Term (syms :: [Symbol]) = FO.Term
+
+-- | Predicates: first-order formulas (we reuse FirstOrder's Formula GADT).
+type Predicate (syms :: [Symbol]) = FO.Formula syms
+
+-- | Specification: a pair of a precondition and a postcondition.
+data Spec (syms :: [Symbol]) = Spec
+  { pre :: Predicate syms
+  , post :: Predicate syms
+  }
+
+-- | Sentences: refinement judgments.
+data RCSentence (syms :: [Symbol]) where
+  Refine :: Spec syms -> Spec syms -> RCSentence syms
+  -- We could also add: Skip, Assign, Seq, etc., as in the refinement calculus.
+
+-- | Model: a first-order model (we reuse FirstOrder's model).
+type RCModel (syms :: [Symbol]) = FO.FOLModel syms
+
+instance Institution (RCSig syms) where
+  type Sentence (RCSig syms) = RCSentence syms
+  type Model  (RCSig syms)  = RCModel syms
+  satisfies _ m (Refine spec1 spec2) =
+    let impl = FO.impl  -- implication from FirstOrder smart constructors
+        pre1 = pre spec1
+        pre2 = pre spec2
+        post1 = post spec1
+        post2 = post spec2
+    in satisfies (Proxy :: Proxy (FO.FOLSig syms)) m (impl pre2 pre1) &&
+       satisfies (Proxy :: Proxy (FO.FOLSig syms)) m (impl post1 post2)
+
+-- | Smart constructors for specifications.
+spec :: Predicate syms -> Predicate syms -> Spec syms
+spec = Spec
+
+-- | Smart constructors for refinement judgments.
+refine :: Spec syms -> Spec syms -> RCSentence syms
+refine = Refine
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,112 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main (main) where
+
+import Test.Hspec
+import Data.Proxy (Proxy(..))
+import qualified Data.Map.Strict as Map
+import qualified Data.Set as Set
+
+import qualified FirstOrder as FO
+import qualified Hyperdoctrine as HD
+import qualified RefinementCalculus as RC
+import Gargamelle.FOL.Z3
+import Gargamelle.Model.Satisfaction (Evidence(..))
+import qualified Institution as Legacy
+
+main :: IO ()
+main = hspec do
+  describe "Native FirstOrder Logic" do
+    it "evaluates closed first-order formula under a concrete model" do
+      let pVar = FO.Pred (Proxy @"p") []
+          qVar = FO.Pred (Proxy @"q") []
+          formula = FO.And pVar (FO.Not qVar)
+          model = FO.FOLModel
+            { FO.predicateInterpretation = Map.fromList [("p", Set.singleton []), ("q", Set.empty)]
+            , FO.functionInterpretation  = Map.empty
+            , FO.constants               = Set.empty
+            }
+      Legacy.satisfies (Proxy @(FO.FOLSig '["p", "q"])) model formula `shouldBe` True
+
+    it "evaluates universal quantifiers over Herbrand constants" do
+      let xVar = FO.var "x"
+          predP = FO.Pred (Proxy @"P") [xVar]
+          formula = FO.Forall (Proxy @"x") predP
+          model = FO.FOLModel
+            { FO.predicateInterpretation = Map.fromList [("P", Set.fromList [["c1"], ["c2"]])]
+            , FO.functionInterpretation  = Map.empty
+            , FO.constants               = Set.fromList ["c1", "c2"]
+            }
+      Legacy.satisfies (Proxy @(FO.FOLSig '["P"])) model formula `shouldBe` True
+
+  -- Tested against Note [Streaming Z3 SMT-LIB2 Piping and Model Extraction] [ref:note-z3-smt-pipe-extraction]
+  describe "Gargamelle.FOL.Z3 SMT-LIB2 Pipe" do
+    it "solves satisfiable formula and extracts model assignments" do
+      let pVar = FO.Pred (Proxy @"p") []
+          qVar = FO.Pred (Proxy @"q") []
+          formula = FO.And pVar (FO.Not qVar)
+      res <- checkFormulaZ3 formula
+      case res of
+        Sat model -> do
+          Map.lookup "p" model `shouldBe` Just "true"
+          Map.lookup "q" model `shouldBe` Just "false"
+        _ -> expectationFailure ("Expected Sat, but got: " ++ show res)
+
+    it "identifies unsatisfiable formula as Unsat" do
+      let pVar = FO.Pred (Proxy @"p") []
+          formula = FO.And pVar (FO.Not pVar)
+      res <- checkFormulaZ3 formula
+      res `shouldBe` Unsat
+
+    it "formally verifies tautological validity returning VerifiedSection" do
+      -- Law of excluded middle: p \/ (not p) is universally valid
+      let pVar = FO.Pred (Proxy @"p") []
+          tautology = FO.Or pVar (FO.Not pVar)
+      evidence <- verifyValidityZ3 tautology
+      evidence `shouldBe` VerifiedSection
+
+    it "detects countermodel when verifying non-valid formula" do
+      -- p /\ q is not valid (counterexample exists where p=false)
+      let pVar = FO.Pred (Proxy @"p") []
+          qVar = FO.Pred (Proxy @"q") []
+          nonTautology = FO.And pVar qVar
+      evidence <- verifyValidityZ3 nonTautology
+      case evidence of
+        Counterexample sym _ -> sym `shouldBe` "Z3-Countermodel"
+        VerifiedSection      -> expectationFailure "Expected counterexample for non-valid formula"
+
+  -- Tested against Note [Categorical Hyperdoctrine Quantifier Adjunctions] [ref:note-hyperdoctrine-adjunctions]
+  describe "Hyperdoctrine & Refinement Calculus" do
+    it "evaluates hyperdoctrine context satisfaction" do
+      let pVar = FO.Pred (Proxy @"p") []
+          model = FO.FOLModel
+            { FO.predicateInterpretation = Map.fromList [("p", Set.singleton [])]
+            , FO.functionInterpretation  = Map.empty
+            , FO.constants               = Set.empty
+            }
+      Legacy.satisfies (Proxy @(HD.HDCSig '["p"])) model pVar `shouldBe` True
+
+    it "evaluates specification refinement (Spec1 ⊑ Spec2)" do
+      let pre1 = FO.Pred (Proxy @"preA") []
+          post1 = FO.Pred (Proxy @"postA") []
+          pre2 = FO.Pred (Proxy @"preB") []
+          post2 = FO.Pred (Proxy @"postB") []
+          spec1 = RC.spec pre1 post1
+          spec2 = RC.spec pre2 post2
+          refinement = RC.refine spec1 spec2
+          -- For spec1 to refine spec2: pre2 => pre1 and post1 => post2
+          -- We set up a model where pre1, pre2, post1, post2 all hold
+          model = FO.FOLModel
+            { FO.predicateInterpretation = Map.fromList
+                [ ("preA", Set.singleton [])
+                , ("postA", Set.singleton [])
+                , ("preB", Set.singleton [])
+                , ("postB", Set.singleton [])
+                ]
+            , FO.functionInterpretation  = Map.empty
+            , FO.constants               = Set.empty
+            }
+      Legacy.satisfies (Proxy @(RC.RCSig '["preA", "postA", "preB", "postB"])) model refinement `shouldBe` True
