Skip to content

Commit 79c8903

Browse files
fix(cartridges): type-check all cartridge ABIs under Idris2 0.8.0 + gate them in CI (#191)
## What Makes **all 104 cartridge ABI modules** type-check under the pinned toolchain (Idris2 0.8.0, Chez) — previously 21 cartridges failed — and adds the CI gate that was missing so they can't drift again. Local verification: `scripts/typecheck-proofs.sh` → **PASS=105 FAIL=0** (core ABI + 104 cartridge ABIs); `scripts/check-trusted-base.sh` → **5 sanctioned class-(J) axioms, 0 undocumented escapes**. ## Why this happened Nothing in CI ever ran `idris2 --typecheck` on `cartridges/**/abi`: `abi-drift.yml` only runs `iseriser`'s *structural* manifest check, and the Justfile's `typecheck` recipe covered just **5 of ~50** cartridge ABIs and was never wired into a workflow. So 99 ABIs rotted undetected against 0.8.0. ## Fixes (by root cause — theorem statements & axiom budget unchanged) - **Reserved-keyword record fields** (0.8.0 reserves these): `total`→`totalCount` (conflow, panic-attack, reposystem), `data`→`payload` (ephapax), `namespace`→`namespaceName` (k8s, ml), `prefix`→`txnPrefix` (matrix). - **Missing imports**: `Data.List` for `NonEmpty` (panic-attack, stapeln, civic-connect, hypatia, local-coord/Protocol); `Data.Nat` for `LTE` (pmpl). - **`lteTransitive`→`transitive`** + explicit `{s,n}` binders (hypatia). - **Visibility/layout**: bare `public`→`public export`; modifier on its own line so the `where`-block isn't mis-indented vs `record`/`data` (academic-workflow, hesiod); unqualified `interface` names. - **Named fields are GADT-only**: `data X = MkC (n : T)` → `MkC T` (model-router, burble-admin). - **`octadLength`** via `Data.Vect.lengthCorrect` — `Refl` can't reduce `length` on an abstract `Vect` (verisimdb). - **Constructor-name collision**: `ValidTransition`'s proof-only `Connect`/`Disconnect` → `TConnect`/`TDisconnect`, so the FFI-bearing `PostgresqlAction` enum keeps its names (postgresql). - **Large type-level `Nat` blowup**: `ValidPort` takes explicit `LTE` proofs via `Data.Nat.lteReflectsLTE` (builtin-Integer path), mirroring `SafeWebSocket`'s `FrameSizeSafeUpTo` — typecheck **>90s (OOM) → ~2s** (local-coord). - **`IsLoopback` giant-Nat-literal function** (`IsLoopback 5178 = ()`, ~150s) → top-level GADT (~1s), matching the other adapters (bofig, ephapax, fireflag, sanctify). - **Pre-existing proof bug** surfaced once the blowup cleared: `identityDoesNotEnableFederation` relied on a nullary CAF reducing in the unifier; restated over `LocalOnly` directly (local-coord/Identity). ## Durable gate - `scripts/typecheck-proofs.sh` — type-checks core ABI + **every** cartridge ABI. - `scripts/check-trusted-base.sh` — the audit `PROOF-NEEDS.md`/`docs/proof-debt.md` already reference but which never existed. - `.github/workflows/proofs.yml` — runs both, installing the pinned Idris2 via asdf (`.tool-versions`). - `Justfile` `typecheck`/`verify-no-believe-me` now delegate to the scripts (one source of truth). ## Note `e2e.yml` triggers on `cartridges/**` and is currently red on `main` for an unrelated reason (the `/menu` + feedback-o-tron contract drift between `openapi.yaml`, the Elixir router, and `tests/e2e_full.sh`). That's tracked/fixed separately as deliverable-2; it is not introduced by this PR. 🤖 Draft via Claude Code. --- _Generated by [Claude Code](https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7b02920 commit 79c8903

27 files changed

Lines changed: 307 additions & 90 deletions

File tree

.github/workflows/proofs.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
# Proofs Gate — type-check every Idris2 proof + enforce the trusted base.
5+
#
6+
# Closes the gap that let cartridge ABIs rot into non-compiling states:
7+
# abi-drift.yml only runs iseriser's *structural* manifest check, and nothing
8+
# ever ran `idris2 --typecheck` on cartridges/**/abi in CI (the Justfile's old
9+
# `typecheck` recipe covered only 5 of ~50 cartridge ABIs and was never wired
10+
# into a workflow). This gate type-checks the core ABI package AND every
11+
# cartridge ABI under the pinned toolchain (.tool-versions → idris2 0.8.0), and
12+
# runs the trusted-base audit so no new believe_me / axiom slips in.
13+
name: Proofs Gate
14+
15+
on:
16+
push:
17+
branches: [main]
18+
paths:
19+
- 'src/abi/**'
20+
- 'cartridges/**/abi/**'
21+
- 'verification/**'
22+
- 'scripts/check-trusted-base.sh'
23+
- 'scripts/typecheck-proofs.sh'
24+
- '.github/workflows/proofs.yml'
25+
- '.tool-versions'
26+
pull_request:
27+
branches: [main]
28+
paths:
29+
- 'src/abi/**'
30+
- 'cartridges/**/abi/**'
31+
- 'verification/**'
32+
- 'scripts/check-trusted-base.sh'
33+
- 'scripts/typecheck-proofs.sh'
34+
- '.github/workflows/proofs.yml'
35+
- '.tool-versions'
36+
workflow_dispatch:
37+
38+
permissions:
39+
contents: read
40+
41+
concurrency:
42+
group: proofs-${{ github.ref }}
43+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
44+
45+
jobs:
46+
# Cheap, dependency-free gate: scan for unsound constructs + axiom-count drift.
47+
trusted-base:
48+
name: Trusted-base audit (no new axioms)
49+
runs-on: ubuntu-latest
50+
timeout-minutes: 10
51+
steps:
52+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
53+
- name: Enforce trusted base
54+
run: bash scripts/check-trusted-base.sh
55+
56+
# Full type-check of the core ABI + every cartridge ABI under the pinned Idris2.
57+
typecheck:
58+
name: Idris2 type-check (core + all cartridge ABIs)
59+
runs-on: ubuntu-latest
60+
timeout-minutes: 45
61+
steps:
62+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
63+
64+
- name: Read pinned Idris2 version
65+
id: ver
66+
run: echo "idris2=$(awk '/^idris2 /{print $2}' .tool-versions)" >> "$GITHUB_OUTPUT"
67+
68+
- name: Install build deps (Chez Scheme + GMP)
69+
run: sudo apt-get update && sudo apt-get install -y chezscheme libgmp-dev build-essential
70+
71+
- name: Cache asdf + Idris2 toolchain
72+
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
73+
with:
74+
path: ~/.asdf
75+
key: idris2-asdf-${{ runner.os }}-${{ steps.ver.outputs.idris2 }}
76+
77+
- name: Install asdf + Idris2 ${{ steps.ver.outputs.idris2 }}
78+
env:
79+
# Ubuntu's chezscheme package installs the interpreter as `scheme`;
80+
# Idris2's bootstrap Makefile reads $SCHEME (defaults to `chez`).
81+
SCHEME: scheme
82+
run: |
83+
set -euo pipefail
84+
if [ ! -f "$HOME/.asdf/asdf.sh" ]; then
85+
git clone --depth 1 --branch v0.14.0 https://github.com/asdf-vm/asdf.git "$HOME/.asdf"
86+
fi
87+
. "$HOME/.asdf/asdf.sh"
88+
asdf plugin add idris2 || true
89+
asdf install idris2 "${{ steps.ver.outputs.idris2 }}"
90+
asdf global idris2 "${{ steps.ver.outputs.idris2 }}"
91+
92+
- name: Put Idris2 on PATH
93+
run: echo "$HOME/.asdf/shims" >> "$GITHUB_PATH"
94+
95+
- name: Idris2 version
96+
run: idris2 --version
97+
98+
- name: Type-check all proofs (core + cartridges)
99+
run: bash scripts/typecheck-proofs.sh

Justfile

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -398,15 +398,8 @@ lint: verify-no-believe-me typecheck
398398
# `--check --package boj boj.ipkg` form is invalid — `--check` is for .idr
399399
# files and `--package` looks up an *installed* package, so it never ran).
400400
typecheck:
401-
@echo "Type-checking core ABI (17 modules)..."
402-
cd src/abi && idris2 --typecheck boj.ipkg
403-
@echo "Type-checking cartridge ABIs..."
404-
cd cartridges/fleet-mcp/abi && idris2 --typecheck fleet-mcp.ipkg
405-
cd cartridges/nesy-mcp/abi && idris2 --typecheck nesy-mcp.ipkg
406-
cd cartridges/database-mcp/abi && idris2 --typecheck database-mcp.ipkg
407-
cd cartridges/agent-mcp/abi && idris2 --typecheck agent-mcp.ipkg
408-
cd cartridges/feedback-mcp/abi && idris2 --typecheck feedback-mcp.ipkg
409-
@echo "All ABI files type-check!"
401+
@echo "Type-checking core ABI + every cartridge ABI (pinned Idris2)..."
402+
bash scripts/typecheck-proofs.sh
410403

411404
# Verify the trusted base: no unsound constructs beyond the sanctioned axioms.
412405
#
@@ -417,26 +410,7 @@ typecheck:
417410
# This recipe fails on any believe_me/assert_* outside that module, and also
418411
# fails if the audited axiom count drifts from 5 (keep the docs in sync).
419412
verify-no-believe-me:
420-
#!/usr/bin/env bash
421-
set -euo pipefail
422-
AXIOM_FILE="src/abi/Boj/SafetyLemmas.idr"
423-
echo "Scanning for unsound constructs outside the sanctioned axiom module..."
424-
FOUND=$(grep -rn 'believe_me\|assert_total\|assert_smaller' --include='*.idr' src/ cartridges/ 2>/dev/null \
425-
| grep -v -- '--.*believe_me' | grep -v '|||.*believe_me' \
426-
| grep -v -- '--.*Admitted' | grep -v '|||.*Admitted' \
427-
| grep -v "^${AXIOM_FILE}:" || true)
428-
if [ -n "$FOUND" ]; then
429-
echo "CRITICAL: Found undocumented unsound constructs:"
430-
echo "$FOUND"
431-
exit 1
432-
fi
433-
AXIOM_COUNT=$(grep -c 'believe_me ()' "$AXIOM_FILE" 2>/dev/null || true)
434-
if [ "$AXIOM_COUNT" != "5" ]; then
435-
echo "CRITICAL: expected exactly 5 sanctioned class-(J) axioms in $AXIOM_FILE, found $AXIOM_COUNT."
436-
echo "If this change is intentional, update PROOF-NEEDS.md and docs/proof-debt.md to match."
437-
exit 1
438-
fi
439-
echo "OK: no undocumented unsound constructs; $AXIOM_COUNT sanctioned class-(J) axioms (all in SafetyLemmas.idr)."
413+
bash scripts/check-trusted-base.sh
440414

441415
# Full verification suite: type-check + zero believe_me + build + test
442416
verify: typecheck verify-no-believe-me build test

cartridges/academic-workflow-mcp/abi/AcademicWorkflow.idr

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
module AcademicWorkflow
55

66
||| Paper metadata
7-
public record Paper where
7+
public export
8+
record Paper where
89
constructor MkPaper
910
title : String
1011
authors : List String
@@ -13,35 +14,40 @@ public record Paper where
1314
abstract : String
1415

1516
||| Citation format
16-
public data CitationFormat =
17-
| BibTeX
17+
public export
18+
data CitationFormat
19+
= BibTeX
1820
| CSL
1921
| RIS
2022
| EndNote
2123

2224
||| Proof that a citation format is supported
23-
public data Supported : CitationFormat -> Type where
25+
public export
26+
data Supported : CitationFormat -> Type where
2427
SupportedBibTeX : Supported BibTeX
2528
SupportedCSL : Supported CSL
2629
SupportedRIS : Supported RIS
2730
SupportedEndNote : Supported EndNote
2831

2932
||| Review annotation
30-
public record ReviewNote where
33+
public export
34+
record ReviewNote where
3135
constructor MkReviewNote
3236
page : Nat
3337
text : String
3438
category : String -- "typo", "unclear", "question", "suggestion"
3539

3640
||| Zotero collection reference
37-
public record ZoteroCollection where
41+
public export
42+
record ZoteroCollection where
3843
constructor MkZoteroCollection
3944
id : String
4045
name : String
4146
itemCount : Nat
4247

4348
||| Academic workflow operations
44-
public interface AcademicWorkflow.Workflow (m : Type -> Type) where
49+
public export
50+
interface Workflow (m : Type -> Type) where
4551
||| Search Zotero collections
4652
searchZotero : (query : String) -> m (List ZoteroCollection)
4753

@@ -63,7 +69,8 @@ public interface AcademicWorkflow.Workflow (m : Type -> Type) where
6369
exportCollection : (collectionId : String) -> m String
6470

6571
||| Loopback proof: academic-mcp runs on 127.0.0.1:5174
66-
public data IsLoopback : (port : Nat) -> Type where
72+
public export
73+
data IsLoopback : (port : Nat) -> Type where
6774
LoopbackProof : IsLoopback 5174
6875

6976
export

cartridges/bofig-mcp/abi/Bofig.idr

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- SPDX-License-Identifier: MPL-2.0
22
-- Bofig Cartridge ABI — Evidence graph query interface
33

4-
module ABI.Bofig
4+
module Bofig
55

66
%language ElabReflection
77

@@ -75,10 +75,12 @@ interface Bofig.Graph where
7575
-- Get graph statistics
7676
getGraphStats : IO (Nat, Nat) -- (nodes, edges)
7777

78-
-- Loopback proof: cartridge runs on localhost only
79-
IsLoopback : (port : Nat) -> Type
80-
IsLoopback 5178 = ()
8178

79+
||| Loopback proof: cartridge binds on localhost only.
8280
public export
83-
Loopback.proof : IsLoopback 5178
84-
Loopback.proof = ()
81+
data IsLoopback : (port : Nat) -> Type where
82+
LoopbackProof : IsLoopback 5178
83+
84+
export
85+
loopbackInvariant : IsLoopback 5178
86+
loopbackInvariant = LoopbackProof

cartridges/burble-admin-mcp/abi/BurbleAdmin/Protocol.idr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ data RequiresPermission : Operation -> PermLevel -> Type where
4848

4949
||| Room capacity is bounded (1-500 participants)
5050
public export
51-
data RoomCapacity = MkCapacity (n : Fin 500)
51+
data RoomCapacity = MkCapacity (Fin 500)
5252

5353
-- ═══════════════════════════════════════════════════════════════════════
5454
-- C ABI Exports

cartridges/civic-connect-mcp/abi/CivicConnect/Protocol.idr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
module CivicConnect.Protocol
77

88
import Data.Nat
9+
import Data.List
910

1011
||| CivicConnect operation codes.
1112
public export

cartridges/conflow-mcp/abi/Conflow/Protocol.idr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ record ApplyResult where
4242
constructor MkApplyResult
4343
applied : Nat
4444
skipped : Nat
45-
total : Nat
46-
sumPrf : applied + skipped = total
45+
totalCount : Nat
46+
sumPrf : applied + skipped = totalCount
4747

4848
||| Proof: a valid config with no errors has empty error list.
4949
export

cartridges/ephapax-mcp/abi/Ephapax.idr

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- SPDX-License-Identifier: MPL-2.0
22
-- Ephapax Cartridge ABI — Proof-compiler query interface
33

4-
module ABI.Ephapax
4+
module Ephapax
55

66
%language ElabReflection
77

@@ -31,7 +31,7 @@ record QueryResult where
3131
constructor MkQueryResult
3232
success : Bool
3333
message : String
34-
data : String
34+
payload : String
3535

3636
-- Type-checking result
3737
public export
@@ -56,10 +56,12 @@ interface Ephapax.Compiler where
5656
-- Analyze proof complexity and dependencies
5757
analyzeProof : String -> IO QueryResult
5858

59-
-- Check if cartridge port is loopback-only (compile-time proof)
60-
IsLoopback : (port : Nat) -> Type
61-
IsLoopback 5175 = ()
6259

60+
||| Loopback proof: cartridge binds on localhost only.
6361
public export
64-
Loopback.proof : IsLoopback 5175
65-
Loopback.proof = ()
62+
data IsLoopback : (port : Nat) -> Type where
63+
LoopbackProof : IsLoopback 5175
64+
65+
export
66+
loopbackInvariant : IsLoopback 5175
67+
loopbackInvariant = LoopbackProof

cartridges/fireflag-mcp/abi/Fireflag.idr

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- SPDX-License-Identifier: MPL-2.0
22
-- Fireflag Cartridge ABI — Extension-to-MCP mapping interface
33

4-
module ABI.Fireflag
4+
module Fireflag
55

66
%language ElabReflection
77

@@ -63,10 +63,12 @@ interface Fireflag.Mapper where
6363
-- Discover extensions in directory
6464
discoverExtensions : String -> IO (List ExtensionMetadata)
6565

66-
-- Loopback proof: cartridge runs on localhost only
67-
IsLoopback : (port : Nat) -> Type
68-
IsLoopback 5177 = ()
6966

67+
||| Loopback proof: cartridge binds on localhost only.
7068
public export
71-
Loopback.proof : IsLoopback 5177
72-
Loopback.proof = ()
69+
data IsLoopback : (port : Nat) -> Type where
70+
LoopbackProof : IsLoopback 5177
71+
72+
export
73+
loopbackInvariant : IsLoopback 5177
74+
loopbackInvariant = LoopbackProof

cartridges/hesiod-mcp/abi/Hesiod.idr

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
module Hesiod
55

66
||| DNS record type enumeration (proof-indexed)
7-
public data DNSRecordType =
8-
| A -- IPv4 address
7+
public export
8+
data DNSRecordType
9+
= A -- IPv4 address
910
| AAAA -- IPv6 address
1011
| CNAME -- Canonical name
1112
| MX -- Mail exchange
@@ -15,7 +16,8 @@ public data DNSRecordType =
1516
| SRV -- Service record
1617

1718
||| Proof that a record type is queryable
18-
public data Queryable : DNSRecordType -> Type where
19+
public export
20+
data Queryable : DNSRecordType -> Type where
1921
QueryableA : Queryable A
2022
QueryableAAAA : Queryable AAAA
2123
QueryableCNAME : Queryable CNAME
@@ -26,23 +28,26 @@ public data Queryable : DNSRecordType -> Type where
2628
QueryableSRV : Queryable SRV
2729

2830
||| DNS response record
29-
public record DNSRecord where
31+
public export
32+
record DNSRecord where
3033
constructor MkDNSRecord
3134
name : String
3235
type : DNSRecordType
3336
ttl : Nat
3437
value : String
3538

3639
||| Lookup result type (success or failure)
37-
public data LookupResult : Type where
40+
public export
41+
data LookupResult : Type where
3842
Success : (records : List DNSRecord) -> LookupResult
3943
NotFound : (hostname : String) -> LookupResult
4044
NetworkError : (message : String) -> LookupResult
4145
Timeout : (hostname : String) -> (seconds : Nat) -> LookupResult
4246

4347
||| Type-safe DNS lookup interface
4448
||| Proof ensures only valid record types are queried
45-
public interface Hesiod.Lookup (m : Type -> Type) where
49+
public export
50+
interface Lookup (m : Type -> Type) where
4651
||| Query DNS records for a hostname
4752
||| @hostname The domain to query
4853
||| @rectype The record type to look up
@@ -59,7 +64,8 @@ public interface Hesiod.Lookup (m : Type -> Type) where
5964
Queryable rectype -> m (List LookupResult)
6065

6166
||| Loopback proof: hesiod-mcp only runs on localhost:5173
62-
public data IsLoopback : (port : Nat) -> Type where
67+
public export
68+
data IsLoopback : (port : Nat) -> Type where
6369
LoopbackProof : IsLoopback 5173
6470

6571
export

0 commit comments

Comments
 (0)