Skip to content

Commit cf56397

Browse files
feat(tg-11): epistemic types — Epi[k, ρ, τ], non-factive by construction (#98)
Wires `hyperpolymath/epistemic-types` into the mechanised core, following the route TG-10 took for echo. **Lean 0 errors, sorry/axiom gate clean.** ## The commitment: a warrant is not knowledge `Epi[κ, ρ, τ]` — at standpoint κ, evidence of type ρ purporting to support a claim of type τ. The claim type appears in the type and **no elimination rule delivers it**. `evidence` is the sole projection, and it yields ρ. That mirrors upstream precisely: - `Warrant.agda` gives the record only an `Evidence` field — deliberately *not* `Evidence → A`, because that "would make every warrant factive" — and puts extraction in a separate `SoundWarrant`. - `Base.agda` splits `FactiveModality` (with `reflect : E κ A → A`) from `BeliefModality` (without), rather than treating belief as knowledge with a missing proof. The absence here is that same design choice, mechanised. ## Six capstones | Theorem | What it establishes | |---|---| | `epi_evidence_recovers` | `evidence ∘ warrant` returns the token | | **`epi_claim_is_opaque`** | two warrants, same evidence, **different claims**, observationally identical | | `epi_only_yields_evidence` | from `Epi[κ,ρ,τ]` the only elimination is at ρ | | `epi_distinguishes_standpoints` | κ₁ ≠ κ₂ gives genuinely different types | | `epi_roundtrip_typed` | type safety of intro + projection | | `epi_over_echo_typed` | `Epi[κ, Echo[ρ,τ], σ]` — the modalities **nest** | `epi_claim_is_opaque` is the exact dual of `echo_distinguishes_collapsed`: **echo *reveals* what `close` forgot; a warrant *withholds* what it purports.** Holding evidence is not holding the fact. `epi_over_echo_typed` mirrors `EpistemicEcho` in `EchoBridge.agda`, which is explicit that these are different modalities — echo grades irrecoverability, epi indexes standpoints — and that they compose without collapsing. ## Metatheory extended, not weakened Progress, Preservation, Determinism, TypeSafety, `infer_sound`, `infer_complete` all cover the new fragment, plus a new `canonical_epi` lemma. Worth noting where non-factivity actually lands: in preservation, the `evidenceVal` case inverts `tEvidence` then `tEpiVal` and returns the **token's** typing (`he`), never the claim's (`hc`). Non-factivity is discharged by the type system rather than asserted in a comment. ## Both engines OCaml mirrors it — `TEpi` former, `Warrant`/`EpiVal`/`Evidence`, `VEpi` runtime value, typing and evaluation. TG-3 extended with 4 pins (including that `evidence` yields the *evidence* type, never the claim); differential regenerated and **kernel-checked: 496 obligations, 0 errors**. TG-3 self-check 1008 → 1012. ## ⚠ Registered honestly — A-TG-11.1 What the simply-typed shadow does **not** model: - standpoints are `Nat`, not an arbitrary index set `K`; - upstream's `LawfulModality` functor laws, `FactiveModality.reflect` and `ReturnModality.return` are omitted (all opt-in there); - **the real gap** — the claim is *carried* in the value rather than *erased*, because erasing it would break uniqueness of typing in a system without quantities. A **QTT** treatment (quantity `0` on the claim) would be the faithful version — which is the same conclusion the linear/affine discussion reached independently. ## Tests 6 typecheck cases, including two negatives: that `evidence` never returns the claim type when the two differ, and that `evidence` of a non-warrant is rejected. Typecheck suite 129 → 135; all suites green; corpus and RSR gates pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 1403f2a commit cf56397

9 files changed

Lines changed: 387 additions & 1 deletion

File tree

ASSUMPTIONS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Cross-references use `[[A-TG-N.M]]` syntax, resolved here.
3434
| A-TG-7.1 | MATH | Word problem in the braid group `B_n` is solvable in polynomial time (Birman–Ko–Lee / Garside normal form) | TG-7 | Birman–Ko–Lee 1998; _A New Approach to the Word and Conjugacy Problems in the Braid Groups_ |
3535
| A-TG-7.2 | IMPL | `braidEquiv` (`proofs/Tangle.lean`) and `braid_equiv.ml` implement Dehornoy handle reduction **correctly**, and agree with each other. Since the 2026-07-29 ruling (#50) routed `==` through them, this is **load-bearing for the semantics of `==`**: the Step relation's metatheory is proven only *relative to* `braidEquiv`, never that it decides braid-group equality. Evidenced by testing (2220 + 8 assertions), not proof. Retired by the mechanised Garside/Dehornoy proof (#51, research-grade). The Lean port is additionally **fuel-bounded**, so termination is assumed rather than proven. | TG-7 | `compiler/lib/braid_equiv.ml`; `proofs/Tangle.lean` §BRAID-GROUP EQUIVALENCE; `compiler/test/tg7` |
3636
| A-TG-92.1 | MATH | Comparing braid words of different widths is decided in B_max(n,m) via the standard embedding Bn -> Bn+1 (adjoin a strand no generator touches). Used to justify widening `T-Eq-Word` (#92) and the match-arm width join. The embedding is standard mathematics but is **asserted in prose, not mechanised** — no Lean lemma states it. What IS machine-checked is that the metatheory (Progress/Preservation/Determinism/TypeSafety, infer_sound/complete) holds under the widened rule, and that OCaml `infer_expr` still agrees with Lean `infer` on the corpus (TG-3, 496 obligations). | TG-7 / #92 | `proofs/Tangle.lean` (`tEqWord`, `infer`); `compiler/lib/typecheck.ml` |
37+
| A-TG-11.1 | DESIGN | The simply-typed shadow is FAITHFUL to `epistemic-types`: `Epi[k,rho,tau]` models `Epi K k A` (Warrant.agda) with standpoints as Nat indices rather than an arbitrary index set K, and it omits the upstream `LawfulModality` functor laws, `FactiveModality.reflect` and `ReturnModality.return` (all deliberately opt-in upstream). What IS mechanised here is non-factivity: no elimination yields the claim. Erasure and quantity are NOT modelled - the claim is carried in the value rather than erased, because erasing it would break uniqueness of typing in a system without quantities. A QTT treatment (quantity 0 for the claim) would be the faithful version. | TG-11 | `proofs/Tangle.lean` section EPISTEMIC; `epistemic-types/src/EpistemicTypes/{Base,Warrant,EchoBridge}.agda` |
3738
| A-TG-8.1 | DESIGN | Each dialect's grammar is a strict superset of core's EBNF (`tangle.ebnf`) | TG-8 | `dialects/*/grammar.ebnf` |
3839
| A-TG-8.2 | DESIGN | Each dialect's typing rules are additive (new constructors + their typing rules only; no modification of existing rules) | TG-8 | Per-dialect spec |
3940
| A-TG-9.1 | DESIGN | `tangle-lsp` emits diagnostics in four documented categories (`PARSE_ERROR`, `MISSPELLING_HINT`, `STRUCTURAL_HINT`, `NAME_HINT`); only `PARSE_ERROR` corresponds to a grammar-level rejection. The other three are LSP-only by design (Option B from TG-9 audit; Option A — full refinement via FFI to `typecheck.ml` — remains queued at #28). Each emission site is tagged in the `Diagnostic.source` field as `tangle-lsp[CATEGORY]`. | TG-9 | `compiler/tangle-lsp/src/backend.rs`; `compiler/tangle-lsp/docs/lsp-diagnostic-categories.md` |

PROOF-NEEDS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Cross-referenced to [PROOF-NARRATIVE.md §3](PROOF-NARRATIVE.md#3-remaining-obli
6060
| TG-5 | `compositional.ml` (418 LoC) rewriter preserves types | TP | OCaml property test | P2 || **LANDED** (`compiler/test/tg5/tg5_invariants.ml`, 189 assertions in `dune runtest`). compositional is below the Ty layer, so "preserves types" = preserves the PD-lowering structural invariants + echo residue-recovery: `OpenWord`/`ClosedDiagram`/`EchoClosed` each pinned (closedness, `\|crossings\|`=unit-length, source unit-expanded, **verbatim residue** for `EchoClose` with `expand(residue)=diagram word` and echo-diagram pdv1-identical to plain `close`), error paths, count pins. Lean IR model = optional later rung |
6161
| TG-6 | WASM compilation preserves semantics (source eval ≡ wasm exec) | TP / ALG | differential + Lean bisimulation | P1 || **RUNG LANDED (differential)**: `compiler/tangle-wasm/tests/differential.rs` EXECUTES the generated wasm with the `wasmi` interpreter (dev-dep) and checks the braid strand-permutation equals an independent reference model (trefoil, non-commuting pairs, braid-relation pairs, 5-strand weave). Validates codegen vs the permutation semantics; not a cross-binary diff against `eval.ml`, and Markov helpers untested. Full source↔wasm bisimulation (WasmCert) remains research-grade |
6262
| TG-7 | `Step.eqBraids` decides braid-group equivalence (not list equality) | ALG / DOM | OCaml + Lean 4 | P2 || **SEMANTICS LANDED 2026-07-29** (owner ruling #50 → (a) true braid-group equivalence). `==` on braids now decides braid-group equality in BOTH engines: OCaml `eval.ml` `Eq`/`Isotopy` route through `compiler/lib/braid_equiv.ml`; Lean `Step.eqBraids`/`eqIdBraid`/`eqBraidId` (and the three `echoEq` counterparts) use `braidEquiv`, a faithful in-Lean port of the same Dehornoy procedure. Progress/Preservation/Determinism re-verified unchanged (they need only a total function into `Bool`). Tested: `compiler/test/tg7` 2220 assertions + 8 new semantics-distinguishing cases in `test_eval.ml`. **Remaining (research-grade): the mechanised Garside/Dehornoy correctness proof — `braidEquiv` is TRUSTED, NOT PROVEN; the Step relation is proven only RELATIVE to it** |
63+
| TG-11 | Epistemic types: `Epi[k, rho, tau]` former + `warrant`/`epiVal`/`evidence`, with Progress/Preservation/Determinism/TypeSafety extended to cover them, and NON-FACTIVITY established (no elimination delivers the claim) | TP / DOM | Lean 4 | P1 | - | **LANDED**: `proofs/Tangle.lean` §EPISTEMIC. Six capstones: `epi_evidence_recovers`, `epi_claim_is_opaque`, `epi_only_yields_evidence`, `epi_distinguishes_standpoints`, `epi_roundtrip_typed`, `epi_over_echo_typed`. Mirrors `hyperpolymath/epistemic-types` (Warrant.agda / Base.agda / EchoBridge.agda). TG-3 extended: 4 new pins, differential regenerated and kernel-checked (496 obligations, 0 errors) |
6364
| TG-8 | Each dialect (braid-calculus, quantum-circuit, skein-algebra, string-diagram, virtual-knot) is a conservative extension of core | TP | OCaml model + Lean per-dialect | P3 || **TEMPLATE LANDED (virtual-knot)**: `compiler/lib/dialect_vk.ml` models VBₙ ⊃ Bₙ as core + a virtual layer that DELEGATES to `Braid_equiv` on the real fragment, so conservativity holds by construction; `compiler/test/tg8` (2311 assertions) verifies faithful embedding, core-delegation, invariant agreement, proper extension, virtual involution, honest undecided-frontier. Remaining: surface-syntax parser integration, the other 4 dialects (replicate the template), and a Lean conservativity proof |
6465
| TG-9 | LSP diagnostics are a subset of `HasType` failures (no LSP-only diagnostics) | INV | Audit + refactor | P2 || **LANDED** (`tangle-lsp` delegates all diagnostics to `tanglec --check``compiler/lib/check.ml`; hand-rolled LSP-only false positives removed. Subset holds by construction. Tests: `test_check.ml` + tangle-lsp unit/delegation tests) |
6566
| TG-10 | Echo-types integrated into the type system: `Echo[ρ,τ]` former + `echoClose`/`lower`/`residue`/`echoAdd`/`echoEq` + product type (`pair`/`fst`/`snd`), with Progress/Preservation/Determinism/TypeSafety extended to cover them and the non-injectivity / residue-recovery capstones proven | TP / DOM | Lean 4 | P1 || **LANDED** (`proofs/Tangle.lean` §ECHO-TYPES) |

compiler/lib/ast.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ and expr =
9393
| EchoAdd of expr * expr (* echo-preserving addition (residue = summand pair) *)
9494
| EchoEq of expr * expr (* echo-preserving equality (residue = operand pair) *)
9595

96+
(* ---- Epistemic (warranted claim) — mirrors §EPISTEMIC in
97+
* proofs/Tangle.lean and epistemic-types' Warrant.agda.
98+
* NON-FACTIVE: `Evidence` is the only elimination. There is no
99+
* operation taking a warrant to the thing warranted. ---- *)
100+
| Warrant of int * expr * expr (* warrant κ claim evidence (redex) *)
101+
| EpiVal of int * expr * expr (* formed warrant: standpoint, claim, token *)
102+
| Evidence of expr (* project the evidence token (ONLY elimination) *)
103+
96104
(* ---- Literals ---- *)
97105
| BraidLit of generator list
98106
| Identity

compiler/lib/eval.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ type value =
4242
| VFun of string list * expr * env (** Closure: params, body, captured env *)
4343
| VUnit (** Unit / void result *)
4444
| VInvariant of string * string (** Invariant name, result string *)
45+
| VEpi of int * value * value (** Formed warrant: standpoint, claim, token.
46+
The claim is CARRIED but has no
47+
projection — `Evidence` is the only
48+
elimination. Mirrors epiVal in
49+
proofs/Tangle.lean. *)
4550
| VEcho of value * value (** Formed echo: residue, result —
4651
mirrors [echoVal] in proofs/Tangle.lean *)
4752
| VPair of value * value (** Product value *)
@@ -92,6 +97,8 @@ let rec pp_value (v : value) : string =
9297
| VFun _ -> "<function>"
9398
| VUnit -> "()"
9499
| VInvariant (name, result) -> Printf.sprintf "%s = %s" name result
100+
| VEpi (k, c, ev) ->
101+
Printf.sprintf "warrant[%d](%s, %s)" k (pp_value c) (pp_value ev)
95102
| VEcho (res, result) ->
96103
"echo(" ^ pp_value res ^ ", " ^ pp_value result ^ ")"
97104
| VPair (a, b) ->
@@ -396,6 +403,21 @@ let rec eval_expr (env : env) (e : expr) : value =
396403
(pp_value v)
397404
end
398405

406+
(* Epistemic. `warrant` forms the value; `evidence` is the sole projection
407+
and yields the TOKEN. There is deliberately no operation returning the
408+
claim — holding a warrant is not holding the fact. *)
409+
| Warrant (k, claim, ev) ->
410+
VEpi (k, eval_expr env claim, eval_expr env ev)
411+
412+
| EpiVal (k, claim, ev) ->
413+
VEpi (k, eval_expr env claim, eval_expr env ev)
414+
415+
| Evidence e ->
416+
begin match eval_expr env e with
417+
| VEpi (_, _, ev) -> ev
418+
| v -> eval_error "evidence requires a warrant, got %s" (pp_value v)
419+
end
420+
399421
| Cap (_e1, _e2) ->
400422
(* Cap creates a tangle that absorbs two strands — a single-crossing
401423
cup/cap pair. Represented as an empty closed tangle. *)

compiler/lib/pretty.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,17 @@ let rec pp_expr ctx = function
276276
emit ctx " yield strands ";
277277
pp_strand_list ctx w.weave_outputs
278278

279+
| Warrant (k, c, ev) ->
280+
emit ctx "warrant["; emit ctx (string_of_int k); emit ctx "](";
281+
pp_expr ctx c; emit ctx ", "; pp_expr ctx ev; emit ctx ")"
282+
283+
| EpiVal (k, c, ev) ->
284+
emit ctx "epiVal["; emit ctx (string_of_int k); emit ctx "](";
285+
pp_expr ctx c; emit ctx ", "; pp_expr ctx ev; emit ctx ")"
286+
287+
| Evidence e ->
288+
emit ctx "evidence("; pp_expr ctx e; emit ctx ")"
289+
279290
| Crossing (a, op, b) ->
280291
emit ctx "(";
281292
emit ctx a;

compiler/lib/typecheck.ml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type ty =
4444
| TProd of ty * ty (** ρ × σ — product / residue carrier for lossy ops *)
4545
| TEcho of ty * ty (** Echo[ρ, τ] — structured loss: residue ρ, result τ.
4646
Mirrors Ty.echo in proofs/Tangle.lean. *)
47+
| TEpi of int * ty * ty (** Epi[κ, ρ, τ] — at standpoint κ, evidence ρ
48+
purporting to support claim τ. Mirrors
49+
Ty.epi in proofs/Tangle.lean. τ appears
50+
in the type but NO rule eliminates to it:
51+
a warrant is not knowledge. *)
4752

4853
(** Function signature: (param_types) -> return_type. *)
4954
type fun_sig = {
@@ -100,6 +105,7 @@ let rec pp_ty = function
100105
| TStr -> "Str"
101106
| TProd (a, b) -> Printf.sprintf "(%s * %s)" (pp_ty a) (pp_ty b)
102107
| TEcho (r, t) -> Printf.sprintf "Echo[%s, %s]" (pp_ty r) (pp_ty t)
108+
| TEpi (k, r, t) -> Printf.sprintf "Epi[%d, %s, %s]" k (pp_ty r) (pp_ty t)
103109

104110
(* ================================================================== *)
105111
(* Environment operations *)
@@ -218,6 +224,26 @@ let rec infer_expr (gamma : env) (sigma : strand_ctx) (e : expr) : ty =
218224
) wb.weave_outputs in
219225
TTangle (input_boundary, output_boundary)
220226

227+
(* [T-Warrant] / [T-Epi-Val] / [T-Evidence] — epistemic.
228+
Note the absence: no case here produces τ from an Epi. `Evidence` yields
229+
the token type ρ, and that is the only elimination. Contrast `Lower`,
230+
which DOES deliver an echo's result. *)
231+
| Warrant (k, claim, ev) ->
232+
let t_claim = infer_expr gamma sigma claim in
233+
let t_ev = infer_expr gamma sigma ev in
234+
TEpi (k, t_ev, t_claim)
235+
236+
| EpiVal (k, claim, ev) ->
237+
let t_claim = infer_expr gamma sigma claim in
238+
let t_ev = infer_expr gamma sigma ev in
239+
TEpi (k, t_ev, t_claim)
240+
241+
| Evidence e ->
242+
begin match infer_expr gamma sigma e with
243+
| TEpi (_, rho, _) -> rho
244+
| t -> type_error "evidence requires an Epi[k, rho, tau], got %s" (pp_ty t)
245+
end
246+
221247
(* ---- Variables [T-Var] ---- *)
222248

223249
| Var name ->
@@ -652,6 +678,7 @@ and strand_type_of_ty (t : ty) : strand_type =
652678
| TTangle _ -> StrandDefault
653679
| TProd _ -> StrandDefault
654680
| TEcho _ -> StrandDefault
681+
| TEpi _ -> StrandDefault
655682

656683
(** Convert a strand_type to a boundary element for self-crossing. *)
657684
and strand_to_type (st : strand_type) : strand_type = st
@@ -725,7 +752,9 @@ let rec expr_calls (f : string) (e : expr) : bool =
725752
| Cap (e1, e2) | Cup (e1, e2) | Pair (e1, e2)
726753
| EchoAdd (e1, e2) | EchoEq (e1, e2) -> go e1 || go e2
727754
| UnaryOp (_, e1) | Close e1 | Mirror e1 | Reverse e1 | Simplify e1
728-
| Twist e1 | EchoClose e1 | Lower e1 | Residue e1 | Fst e1 | Snd e1 -> go e1
755+
| Twist e1 | EchoClose e1 | Lower e1 | Residue e1 | Fst e1 | Snd e1
756+
| Evidence e1 -> go e1
757+
| Warrant (_, c, ev) | EpiVal (_, c, ev) -> go c || go ev
729758
| Weave wb -> go wb.weave_body
730759
| BraidLit _ | Identity | BoolLit _ | IntLit _ | FloatLit _
731760
| StringLit _ | Var _ | Crossing _ -> false

compiler/test/test_typecheck.ml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,40 @@ let test_echo_types () =
10891089
] in
10901090
infer [] (Match (BraidLit [sigma 1], arms)) = TWord 2);
10911091

1092+
(* ---------------------------------------------------------------- *)
1093+
(* EPISTEMIC TYPES. The commitment is NON-FACTIVITY: holding a warrant *)
1094+
(* is not holding the fact. `Evidence` is the ONLY elimination, and it *)
1095+
(* yields the token type, never the claim type. *)
1096+
(* ---------------------------------------------------------------- *)
1097+
1098+
test "epi: warrant types as Epi[k, rho, tau]" (fun () ->
1099+
infer [] (Warrant (0, IntLit 1, BraidLit [sigma 1]))
1100+
= TEpi (0, TWord 2, TNum));
1101+
1102+
test "epi: evidence yields the EVIDENCE type, not the claim" (fun () ->
1103+
(* The heart of it. Claim is Num, evidence is Word[2]; the projection
1104+
must give Word[2]. If it gave Num, the warrant would be factive. *)
1105+
infer [] (Evidence (Warrant (0, IntLit 1, BraidLit [sigma 1])))
1106+
= TWord 2);
1107+
1108+
test "epi: NO elimination delivers the claim type" (fun () ->
1109+
(* Guard: evidence must never return the claim type when the two differ. *)
1110+
infer [] (Evidence (Warrant (0, StringLit "claimed", BraidLit [sigma 1])))
1111+
<> TStr);
1112+
1113+
test "epi: standpoints are distinguished" (fun () ->
1114+
infer [] (Warrant (1, IntLit 1, IntLit 2))
1115+
<> infer [] (Warrant (2, IntLit 1, IntLit 2)));
1116+
1117+
test "epi: composes over echo without collapsing into it" (fun () ->
1118+
(* EpistemicEcho from EchoBridge.agda: standpoint k with access to an
1119+
echo. The two modalities nest; neither absorbs the other. *)
1120+
infer [] (Warrant (1, IntLit 0, EchoClose (BraidLit [sigma 1])))
1121+
= TEpi (1, TEcho (TWord 2, TWord 0), TNum));
1122+
1123+
test "epi: evidence of a non-warrant is rejected" (fun () ->
1124+
raises (fun () -> infer [] (Evidence (IntLit 1))));
1125+
10921126
test "#92 match arms of genuinely different KINDS still fail" (fun () ->
10931127
let arms = [
10941128
{ arm_pattern = PatIdentity; arm_body = IntLit 1 };

compiler/test/tg3/tg3_emit.ml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ let rec lean_ty (t : ty) : string =
6161
| TWord n -> Printf.sprintf "(.word %d)" n
6262
| TEcho (r, v) -> Printf.sprintf "(.echo %s %s)" (lean_ty r) (lean_ty v)
6363
| TProd (a, b) -> Printf.sprintf "(.prod %s %s)" (lean_ty a) (lean_ty b)
64+
| TEpi (k, r, t) -> Printf.sprintf "(.epi %d %s %s)" k (lean_ty r) (lean_ty t)
6465
| TTangle _ ->
6566
failwith "TG-3: TTangle has no Lean image — a non-core term leaked into the corpus"
6667

@@ -69,6 +70,7 @@ let rec lean_ty (t : ty) : string =
6970
let rec ty_tangle_free = function
7071
| TTangle _ -> false
7172
| TEcho (a, b) | TProd (a, b) -> ty_tangle_free a && ty_tangle_free b
73+
| TEpi (_, r, t) -> ty_tangle_free r && ty_tangle_free t
7274
| TWord _ | TNum | TStr | TBool -> true
7375

7476
(* ================================================================== *)
@@ -130,6 +132,13 @@ let rec lean_expr (scope : string list) (e : expr) : string =
130132
| Snd e1 -> Printf.sprintf "(.snd %s)" (lean_expr scope e1)
131133
| EchoAdd (a, b) -> Printf.sprintf "(.echoAdd %s %s)" (lean_expr scope a) (lean_expr scope b)
132134
| EchoEq (a, b) -> Printf.sprintf "(.echoEq %s %s)" (lean_expr scope a) (lean_expr scope b)
135+
(* Epistemic is IN the mechanised core (proofs/Tangle.lean §EPISTEMIC), so
136+
these are translated, not rejected. *)
137+
| Warrant (k, c, ev) ->
138+
Printf.sprintf "(.warrant %d %s %s)" k (lean_expr scope c) (lean_expr scope ev)
139+
| EpiVal (k, c, ev) ->
140+
Printf.sprintf "(.epiVal %d %s %s)" k (lean_expr scope c) (lean_expr scope ev)
141+
| Evidence e1 -> Printf.sprintf "(.evidence %s)" (lean_expr scope e1)
133142
(* Non-core: must never appear in the corpus (close is the boundary gateway). *)
134143
| FloatLit _ | BinOp ((Sub | Mul | Div | Isotopy), _, _) | UnaryOp _
135144
| Close _ | Mirror _ | Reverse _ | Simplify _ | Cap _ | Cup _ | Twist _
@@ -360,6 +369,17 @@ let check () =
360369
(* #92: widths need not agree, so this is now WELL-TYPED rather than
361370
rejected. It is the `identity == braid` shape the Lean step relation has
362371
always had rules for (eqIdBraid / eqBraidId). *)
372+
(* Epistemic pins: OCaml infer_expr must agree with Lean infer on the new
373+
constructors. The `evidence` pin is the important one: it must yield the
374+
EVIDENCE type, never the claim type. *)
375+
pin "warrant num/word" (Warrant (0, IntLit 1, BraidLit [s 1]))
376+
(TEpi (0, TWord 2, TNum));
377+
pin "warrant word/str" (Warrant (3, BraidLit [s 1], StringLit "ev"))
378+
(TEpi (3, TStr, TWord 2));
379+
pin "evidence yields rho" (Evidence (Warrant (0, IntLit 1, BraidLit [s 1])))
380+
(TWord 2);
381+
pin "epi over echo" (Warrant (1, IntLit 0, EchoClose (BraidLit [s 1])))
382+
(TEpi (1, TEcho (TWord 2, TWord 0), TNum));
363383
pin "eq diff-width word" (BinOp (Eq, BraidLit [s 1], Identity)) TBool;
364384

365385
(* 3. Reject pins — these must raise Type_error. *)

0 commit comments

Comments
 (0)