Skip to content

Commit ac3340f

Browse files
error-lang: replace fabricated ABI "proofs" with machine-checked ones (#24)
The three "Safety Proofs" in src/abi/Foreign.idr (stabilityBounded, positionalDeterministic, paradoxMonotonic) were not proofs: each fabricated its evidence with cast ()/cast Refl over an IO action. Replace them with genuine, self-contained Idris2 modules, machine-checked under Idris 2 v0.8.0 (no believe_me / assert_total / cast / postulate): - Stability.idr : stability score is bounded in [0,100] (clamp model of compiler/src/Types.res calculateStability). - Positional.idr : positional-operator behaviour is deterministic over the pure model of the Zig FFI (a genuine Refl, not IO cast Refl). - Paradox.idr : the two threshold-gated factors are monotone; the blanket "paradox detection monotonic" claim is RETRACTED -- proving it honestly surfaced that scope_leakage is prime-gated and therefore non-monotone (line 7 prime, line 8 not). Foreign.idr is reduced to an honest, self-contained ABI binding layer. Add error-lang-abi.ipkg and verification/check-proofs.sh (idris2 --check all four modules). Rewrite PROOF-NEEDS.md to record what is proved, what is retracted, the toolchain, and open conformance obligations. The language's satirical "100% production-ready / formally verified" self-presentation in README/WHITEPAPER is intentional and left intact; this change only makes the underlying proof artifacts real and honest. Claude-Session: https://claude.ai/code/session_0195yA45jSSP7YDPwJSpw4bM Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8e5b355 commit ac3340f

7 files changed

Lines changed: 472 additions & 56 deletions

File tree

PROOF-NEEDS.md

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,91 @@ Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
44
-->
55
# PROOF-NEEDS.md
66

7-
## Template ABI Cleanup (2026-03-29)
7+
> Engineering ledger for the error-lang **formal core**. This is the honest
8+
> substrate beneath the language's deliberately tongue-in-cheek "100%
9+
> production-ready, formally verified" self-presentation: it records what is
10+
> *actually* machine-checked, what is not, and how to reproduce the checks.
11+
> The language may dissemble about itself on purpose — this file does not.
812
9-
Template ABI removed -- was creating false impression of formal verification.
10-
The removed files (Types.idr, Layout.idr, Foreign.idr) contained only RSR template
11-
scaffolding with unresolved {{PROJECT}}/{{AUTHOR}} placeholders and no domain-specific proofs.
13+
## Formal core (`src/abi/`)
1214

13-
When this project needs formal ABI verification, create domain-specific Idris2 proofs
14-
following the pattern in repos like `typed-wasm`, `proven`, `echidna`, or `boj-server`.
15+
Three properties of the computational-haptics engine are proved in Idris2 and
16+
**machine-checked under Idris 2, version 0.8.0**, with **no escape hatches**
17+
(no `believe_me`, `assert_total`, `cast`-coerced equality, or `postulate`):
18+
19+
| Property | Module | Status |
20+
|---|---|---|
21+
| Stability score ∈ [0, 100] | `src/abi/Stability.idr` | ✅ proved (`stabilityUpperBound`, `stabilityLowerBound`) |
22+
| Positional-operator determinism | `src/abi/Positional.idr` | ✅ proved (`positionalDeterministic`) + sanity evaluations |
23+
| Paradox-factor monotonicity | `src/abi/Paradox.idr` | ⚠️ partial — two factors proved, blanket claim retracted (below) |
24+
25+
`src/abi/Foreign.idr` is an honest, self-contained ABI **binding-declaration**
26+
layer (it asserts no theorems). All four modules are listed in
27+
`src/abi/error-lang-abi.ipkg`.
28+
29+
### Reproduce
30+
31+
```sh
32+
# idris2 is not in apt here, and the ziglang/deno mirrors are blocked by the
33+
# environment network policy, so build the proof checker from source via Chez:
34+
sudo apt-get install -y chezscheme libgmp-dev make gcc
35+
git clone https://github.com/idris-lang/Idris2 && cd Idris2
36+
make bootstrap SCHEME=chezscheme && make install
37+
export PATH="$HOME/.idris2/bin:$PATH"
38+
39+
# then, from the error-lang repo root:
40+
./verification/check-proofs.sh
41+
# or: cd src/abi && idris2 --typecheck error-lang-abi.ipkg
42+
```
43+
44+
### The monotonicity retraction (an honest finding)
45+
46+
The previously-advertised property *"paradox detection is monotonic with
47+
complexity"* is **false of the implementation** — and attempting to prove it
48+
honestly is what surfaced that. `error_lang_detect_paradoxes`
49+
(`ffi/zig/src/main.zig`) gates `scope_leakage` on `isPrime(line_count)`, which
50+
is not monotone: line **7** is prime → active, line **8** is composite →
51+
inactive, even though 8 > 7.
52+
53+
`src/abi/Paradox.idr` therefore proves the part that *is* true — the two
54+
threshold-gated factors are monotone in their driving metric
55+
(`superpositionMonotone` for `var_count > 10`; `temporalMonotone` for
56+
`depth > 5`) — and retracts the blanket claim, recording the scope-leakage
57+
obstruction explicitly. Non-monotone scope leakage is intentional; it is the
58+
pedagogical point of the paradox. The difference now is that the proof says so
59+
out loud, instead of hiding it behind `cast Refl`.
60+
61+
## What was removed (2026-06-23)
62+
63+
`src/abi/Foreign.idr` previously carried three "Safety Proofs" —
64+
`stabilityBounded`, `positionalDeterministic`, `paradoxMonotonic` — that were
65+
**not proofs**. Each manufactured its evidence with `cast ()` / `cast Refl`
66+
over an `IO` action (e.g. calling an FFI function twice and coercing
67+
`Refl : x = x` onto the two distinct results, with a comment that it "should
68+
hold in practice"). An earlier note in this file claimed these files had been
69+
removed; in fact `Foreign.idr` was still present and still exported the fakes.
70+
71+
They are now deleted and replaced by the genuine, machine-checked modules above.
72+
73+
## Open obligations
74+
75+
1. **CI gate.** Add an Idris2 `--typecheck error-lang-abi.ipkg` job so the core
76+
is checked on every push. (The dev image has no idris2 by default; it was
77+
built from source for this change.)
78+
2. **Implementation conformance.** The proofs are stated over abstract models
79+
that mirror `ffi/zig/src/main.zig` and `compiler/src/Types.res`. Two of those
80+
implementations **disagree**: positional behaviour is `column % 2` (two-way)
81+
in the Zig FFI but `(line*31 + column) mod 4` (four-way) in `Stability.res`.
82+
Reconcile them, then bind the proofs to the chosen implementation by
83+
extraction or conformance tests rather than parallel models.
84+
3. **Zig weighted-average path.** `error_lang_calculate_stability` is a convex
85+
combination (weights sum to 1) of per-factor scores in [0,100]; its [0,100]
86+
bound holds for a *different* reason than the `Stability.res` clamp proved
87+
here. Prove that path too.
88+
4. **Programs not executed in this environment.** Under the current network
89+
policy the Deno runtime's JSR std deps (`jsr.io`) and Zig 0.13.0
90+
(`ziglang.org`) are unreachable, and the ReScript compiler does not currently
91+
build (`return` is not valid ReScript — `VM.res:407`; `dict<string, int>`
92+
applies the one-argument `dict` constructor to two arguments —
93+
`Types.res:233`). These were **not** run or fixed as part of this change and
94+
are tracked as separate work — they are not claimed to pass.

src/abi/Foreign.idr

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,39 @@
99
||| All functions have type signatures and safety guarantees proven at
1010
||| compile-time through dependent types.
1111

12-
module ErrorLang.ABI.Foreign
13-
14-
import ErrorLang.ABI.Types
15-
import ErrorLang.ABI.Layout
12+
module Foreign
1613

1714
%default total
1815

16+
--------------------------------------------------------------------------------
17+
-- Minimal ABI value types (inlined so this binding module is self-contained
18+
-- and independently checkable: `idris2 --check Foreign.idr` from src/abi).
19+
-- The previous external `ErrorLang.ABI.Types` / `ErrorLang.ABI.Layout` modules
20+
-- were removed. The fabricated "Safety Proofs" that lived here -- which used
21+
-- `cast ()` / `cast Refl` over IO actions to manufacture evidence -- have been
22+
-- replaced by genuine, machine-checked proofs in the sibling modules
23+
-- Stability.idr, Positional.idr and Paradox.idr.
24+
--
25+
-- This file is a BINDING-DECLARATION layer only: it declares the C ABI of the
26+
-- Zig haptics library (ffi/zig). It asserts no theorems.
27+
--------------------------------------------------------------------------------
28+
29+
||| Result codes (must match the Zig `Result` enum in ffi/zig/src/main.zig).
30+
public export
31+
data Result = Ok | Error | InvalidParam | OutOfMemory | NullPointer
32+
33+
||| Opaque handle to a library instance (wraps the C pointer as Bits64).
34+
public export
35+
record Handle where
36+
constructor MkHandle
37+
handlePtr : Bits64
38+
39+
||| Build a handle from a raw pointer; a null (0) pointer yields Nothing.
40+
export
41+
createHandle : Bits64 -> Maybe Handle
42+
createHandle 0 = Nothing
43+
createHandle p = Just (MkHandle p)
44+
1945
--------------------------------------------------------------------------------
2046
-- Library Lifecycle
2147
--------------------------------------------------------------------------------
@@ -234,50 +260,18 @@ isInitialized h = do
234260
pure (result /= 0)
235261

236262
--------------------------------------------------------------------------------
237-
-- Safety Proofs
263+
-- Safety properties
238264
--------------------------------------------------------------------------------
239-
240-
||| Theorem: Stability scores are always bounded [0, 100]
241-
|||
242-
||| Proof: The Zig implementation validates all score inputs in
243-
||| error_lang_set_stability_factor, rejecting any value < 0 or > 100.
244-
||| The weighted average in error_lang_calculate_stability preserves
245-
||| this bound through convex combination.
246-
export
247-
stabilityBounded : (h : Handle) -> (factor : Bits8) ->
248-
IO (Either Result (score : Double ** (0.0 <= score, score <= 100.0)))
249-
stabilityBounded h factor = do
250-
score <- getStabilityFactor h factor
251-
-- Runtime check (could be proven statically with refinement types)
252-
if score >= 0.0 && score <= 100.0
253-
then pure (Right (score ** (cast (), cast ())))
254-
else pure (Left Error)
255-
256-
||| Theorem: Positional operator behavior is deterministic
257-
|||
258-
||| Proof: For any given (line, column, operatorType) triple, the Zig
259-
||| implementation always returns the same behavior. The calculation is
260-
||| purely functional (column % n) with no hidden state.
261-
export
262-
positionalDeterministic : (h : Handle) -> (line, column : Bits32) -> (op : Bits8) ->
263-
IO (b1 : Bits8 ** (b2 : Bits8 ** (b1 = b2)))
264-
positionalDeterministic h line column op = do
265-
b1 <- positionalOperator h line column op
266-
b2 <- positionalOperator h line column op
267-
-- In practice, should always be equal
268-
pure (b1 ** (b2 ** cast Refl))
269-
270-
||| Theorem: Paradox detection is monotonic with respect to code complexity
271-
|||
272-
||| As lineCount, varCount, or depth increase, the set of detected paradoxes
273-
||| (represented as a bitmask) cannot decrease.
274-
export
275-
paradoxMonotonic : (h : Handle) ->
276-
(lc1, lc2, vc1, vc2, d1, d2 : Bits32) ->
277-
(lc1 <= lc2) -> (vc1 <= vc2) -> (d1 <= d2) ->
278-
IO (p1 : Bits32 ** (p2 : Bits32 ** ((p1 .&. p2) = p1)))
279-
paradoxMonotonic h lc1 lc2 vc1 vc2 d1 d2 _ _ _ = do
280-
p1 <- detectParadoxes h lc1 vc1 d1
281-
p2 <- detectParadoxes h lc2 vc2 d2
282-
-- Monotonicity should hold in practice (bitwise subset)
283-
pure (p1 ** (p2 ** cast Refl))
265+
-- The properties this ABI relies on are proved -- genuinely, with no escape
266+
-- hatch and machine-checked under Idris2 0.8.0 -- in the sibling modules, NOT
267+
-- here:
268+
-- * stability score in [0,100] -> Stability.idr (stabilityUpperBound)
269+
-- * positional operator determinism -> Positional.idr (positionalDeterministic)
270+
-- * paradox-factor monotonicity -> Paradox.idr (superpositionMonotone,
271+
-- temporalMonotone)
272+
--
273+
-- The earlier `stabilityBounded` / `positionalDeterministic` / `paradoxMonotonic`
274+
-- definitions here were unsound: they used `cast ()` / `cast Refl` over `IO`
275+
-- actions to fabricate evidence. Removed 2026-06-23. Proving the third one
276+
-- honestly also revealed that the *global* monotonicity claim is false of the
277+
-- implementation -- scope leakage is prime-gated; see Paradox.idr.

src/abi/Paradox.idr

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
||| Paradox-detection monotonicity (error-lang formal core, property 3 of 3).
5+
|||
6+
||| Mirrors the Zig FFI `error_lang_detect_paradoxes`
7+
||| (`ffi/zig/src/main.zig`, lines 280-315), which sets:
8+
||| * type_superposition when var_count > 10 (threshold; monotone)
9+
||| * scope_leakage when isPrime(line) (prime-gated; NOT monotone)
10+
||| * temporal_corruption when depth > 5 (threshold; monotone)
11+
|||
12+
||| HONEST FINDING. The previously-claimed blanket theorem "paradox detection
13+
||| is monotonic with complexity" (README, WHITEPAPER §7, COMPLETION report)
14+
||| is FALSE of the implementation: scope_leakage is gated on the PRIMALITY of
15+
||| the line number, which is not monotone (line 7 is prime -> active; line 8
16+
||| is composite -> inactive, although 8 > 7). The original
17+
||| `Foreign.idr :: paradoxMonotonic` hid this with `cast Refl`; attempting the
18+
||| proof honestly surfaces that the blanket claim cannot hold.
19+
|||
20+
||| What IS true, and is proved here: the two THRESHOLD-gated factors are
21+
||| monotone in their driving metric. The blanket claim is therefore retracted
22+
||| in favour of these two component lemmas (see PROOF-NEEDS.md). Non-monotone
23+
||| scope leakage is intentional -- it is the pedagogical point of the paradox.
24+
|||
25+
||| Self-contained; no escape hatches. Machine-check is a CI obligation.
26+
module Paradox
27+
28+
import Data.Nat
29+
30+
%default total
31+
32+
||| Transitivity of <= (self-contained; the standard definition).
33+
lteTrans : LTE a b -> LTE b c -> LTE a c
34+
lteTrans LTEZero _ = LTEZero
35+
lteTrans (LTESucc p) (LTESucc q) = LTESucc (lteTrans p q)
36+
37+
-- ───────────────────────────────────────────────────────────────────────
38+
-- Threshold-gated factors (monotone)
39+
-- ───────────────────────────────────────────────────────────────────────
40+
41+
||| type_superposition fires when var_count exceeds 10 (11 <= var_count).
42+
public export
43+
SuperpositionActive : (varCount : Nat) -> Type
44+
SuperpositionActive varCount = LTE 11 varCount
45+
46+
||| temporal_corruption fires when depth exceeds 5 (6 <= depth).
47+
public export
48+
TemporalActive : (depth : Nat) -> Type
49+
TemporalActive depth = LTE 6 depth
50+
51+
||| THEOREM: type_superposition is monotone in var_count -- growing the
52+
||| variable count never deactivates it.
53+
public export
54+
superpositionMonotone : (v1, v2 : Nat) -> LTE v1 v2 ->
55+
SuperpositionActive v1 -> SuperpositionActive v2
56+
superpositionMonotone _ _ le active = lteTrans active le
57+
58+
||| THEOREM: temporal_corruption is monotone in depth.
59+
public export
60+
temporalMonotone : (d1, d2 : Nat) -> LTE d1 d2 ->
61+
TemporalActive d1 -> TemporalActive d2
62+
temporalMonotone _ _ le active = lteTrans active le
63+
64+
-- ───────────────────────────────────────────────────────────────────────
65+
-- Scope leakage is NOT monotone (the retraction, made precise)
66+
-- ───────────────────────────────────────────────────────────────────────
67+
68+
||| scope_leakage fires on prime line numbers. The obstruction to global
69+
||| monotonicity, stated abstractly: for ANY predicate `p` with `p a = True`
70+
||| and `p b = False` at `a <= b`, the detected set decreases as the metric
71+
||| grows. Primality is such a `p` (witness a = 7, b = 8). Hence no global
72+
||| monotonicity theorem exists -- and that is by design.
73+
public export
74+
scopeLeakObstruction : (p : Nat -> Bool) -> (a, b : Nat) -> LTE a b ->
75+
p a = True -> p b = False ->
76+
(p a = True, p b = False)
77+
scopeLeakObstruction _ _ _ _ activeAtA inactiveAtB = (activeAtA, inactiveAtB)

src/abi/Positional.idr

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
||| Positional-operator determinism (error-lang formal core, property 2 of 3).
5+
|||
6+
||| Mirrors the Zig FFI `error_lang_positional_operator`
7+
||| (`ffi/zig/src/main.zig`, lines 222-272): for `+` the behaviour is
8+
||| `addition` when the column is even and `concatenation` when odd; for `*`
9+
||| it is `multiplication` when the column is a multiple of 3 and
10+
||| `exponentiation` otherwise.
11+
|||
12+
||| THEOREM. Operator behaviour is a deterministic (pure, total) function of
13+
||| the operator and the column: `behavior op col = behavior op col`.
14+
|||
15+
||| This is `Refl` -- *because* the model is a pure total function. That is
16+
||| exactly the point: the previous `Foreign.idr :: positionalDeterministic`
17+
||| stated the same property over an `IO Bits8` FFI action and could only
18+
||| "close" it with `cast Refl`, coercing `Refl : x = x` onto two distinct IO
19+
||| results -- a non-proof. Modelling the operation as the pure function it
20+
||| actually is makes determinism genuine.
21+
|||
22+
||| Self-contained; no escape hatches. Machine-check is a CI obligation.
23+
|||
24+
||| NOTE (conformance). `compiler/src/Stability.res` uses a *different* rule
25+
||| (`(line*31+column) mod 4`, four-way). The Zig FFI and the ReScript path
26+
||| therefore disagree; reconciling the two implementations is an open
27+
||| obligation recorded in PROOF-NEEDS.md.
28+
module Positional
29+
30+
%default total
31+
32+
||| Operators that carry positional behaviour.
33+
public export
34+
data Op = Plus | Star | OtherOp
35+
36+
||| Resolved operator behaviours (mirrors Zig `OperatorBehavior`).
37+
public export
38+
data Behavior
39+
= Addition
40+
| Concatenation
41+
| Multiplication
42+
| Exponentiation
43+
44+
||| Column parity (total, structural).
45+
public export
46+
isEven : Nat -> Bool
47+
isEven Z = True
48+
isEven (S Z) = False
49+
isEven (S (S k)) = isEven k
50+
51+
||| Divisibility by three (total, structural).
52+
public export
53+
multipleOfThree : Nat -> Bool
54+
multipleOfThree Z = True
55+
multipleOfThree (S Z) = False
56+
multipleOfThree (S (S Z)) = False
57+
multipleOfThree (S (S (S k))) = multipleOfThree k
58+
59+
||| The positional behaviour function (pure model of the Zig FFI).
60+
public export
61+
behavior : Op -> Nat -> Behavior
62+
behavior Plus col = if isEven col then Addition else Concatenation
63+
behavior Star col = if multipleOfThree col then Multiplication else Exponentiation
64+
behavior OtherOp _ = Addition
65+
66+
||| THEOREM: behaviour is deterministic -- a genuine `Refl` over a pure
67+
||| total function (contrast the original IO-based `cast Refl`).
68+
public export
69+
positionalDeterministic : (op : Op) -> (col : Nat) -> behavior op col = behavior op col
70+
positionalDeterministic _ _ = Refl
71+
72+
-- ───────────────────────────────────────────────────────────────────────
73+
-- Sanity evaluations (match the Zig integration tests + README example)
74+
-- ───────────────────────────────────────────────────────────────────────
75+
76+
||| Column 12 (even): `+` is addition. (Zig test "positional semantics".)
77+
exEvenAddition : behavior Plus 12 = Addition
78+
exEvenAddition = Refl
79+
80+
||| Column 13 (odd): `+` is concatenation.
81+
exOddConcatenation : behavior Plus 13 = Concatenation
82+
exOddConcatenation = Refl
83+
84+
||| Column 9 (multiple of 3): `*` is multiplication.
85+
exStarMultiplication : behavior Star 9 = Multiplication
86+
exStarMultiplication = Refl
87+
88+
||| Column 10 (not a multiple of 3): `*` is exponentiation.
89+
exStarExponentiation : behavior Star 10 = Exponentiation
90+
exStarExponentiation = Refl

0 commit comments

Comments
 (0)