Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/proofs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# Proofs Gate — type-check every Idris2 proof + enforce the trusted base.
#
# Closes the gap that let cartridge ABIs rot into non-compiling states:
# abi-drift.yml only runs iseriser's *structural* manifest check, and nothing
# ever ran `idris2 --typecheck` on cartridges/**/abi in CI (the Justfile's old
# `typecheck` recipe covered only 5 of ~50 cartridge ABIs and was never wired
# into a workflow). This gate type-checks the core ABI package AND every
# cartridge ABI under the pinned toolchain (.tool-versions → idris2 0.8.0), and
# runs the trusted-base audit so no new believe_me / axiom slips in.
name: Proofs Gate

on:
push:
branches: [main]
paths:
- 'src/abi/**'
- 'cartridges/**/abi/**'
- 'verification/**'
- 'scripts/check-trusted-base.sh'
- 'scripts/typecheck-proofs.sh'
- '.github/workflows/proofs.yml'
- '.tool-versions'
pull_request:
branches: [main]
paths:
- 'src/abi/**'
- 'cartridges/**/abi/**'
- 'verification/**'
- 'scripts/check-trusted-base.sh'
- 'scripts/typecheck-proofs.sh'
- '.github/workflows/proofs.yml'
- '.tool-versions'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: proofs-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# Cheap, dependency-free gate: scan for unsound constructs + axiom-count drift.
trusted-base:
name: Trusted-base audit (no new axioms)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Enforce trusted base
run: bash scripts/check-trusted-base.sh

# Full type-check of the core ABI + every cartridge ABI under the pinned Idris2.
typecheck:
name: Idris2 type-check (core + all cartridge ABIs)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Read pinned Idris2 version
id: ver
run: echo "idris2=$(awk '/^idris2 /{print $2}' .tool-versions)" >> "$GITHUB_OUTPUT"

- name: Install build deps (Chez Scheme + GMP)
run: sudo apt-get update && sudo apt-get install -y chezscheme libgmp-dev build-essential

- name: Cache asdf + Idris2 toolchain
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: ~/.asdf
key: idris2-asdf-${{ runner.os }}-${{ steps.ver.outputs.idris2 }}

- name: Install asdf + Idris2 ${{ steps.ver.outputs.idris2 }}
env:
# Ubuntu's chezscheme package installs the interpreter as `scheme`;
# Idris2's bootstrap Makefile reads $SCHEME (defaults to `chez`).
SCHEME: scheme
run: |
set -euo pipefail
if [ ! -f "$HOME/.asdf/asdf.sh" ]; then
git clone --depth 1 --branch v0.14.0 https://github.com/asdf-vm/asdf.git "$HOME/.asdf"
fi
. "$HOME/.asdf/asdf.sh"
asdf plugin add idris2 || true
asdf install idris2 "${{ steps.ver.outputs.idris2 }}"
asdf global idris2 "${{ steps.ver.outputs.idris2 }}"

- name: Put Idris2 on PATH
run: echo "$HOME/.asdf/shims" >> "$GITHUB_PATH"

- name: Idris2 version
run: idris2 --version

- name: Type-check all proofs (core + cartridges)
run: bash scripts/typecheck-proofs.sh
32 changes: 3 additions & 29 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -398,15 +398,8 @@ lint: verify-no-believe-me typecheck
# `--check --package boj boj.ipkg` form is invalid — `--check` is for .idr
# files and `--package` looks up an *installed* package, so it never ran).
typecheck:
@echo "Type-checking core ABI (17 modules)..."
cd src/abi && idris2 --typecheck boj.ipkg
@echo "Type-checking cartridge ABIs..."
cd cartridges/fleet-mcp/abi && idris2 --typecheck fleet-mcp.ipkg
cd cartridges/nesy-mcp/abi && idris2 --typecheck nesy-mcp.ipkg
cd cartridges/database-mcp/abi && idris2 --typecheck database-mcp.ipkg
cd cartridges/agent-mcp/abi && idris2 --typecheck agent-mcp.ipkg
cd cartridges/feedback-mcp/abi && idris2 --typecheck feedback-mcp.ipkg
@echo "All ABI files type-check!"
@echo "Type-checking core ABI + every cartridge ABI (pinned Idris2)..."
bash scripts/typecheck-proofs.sh

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

# Full verification suite: type-check + zero believe_me + build + test
verify: typecheck verify-no-believe-me build test
Expand Down
23 changes: 15 additions & 8 deletions cartridges/academic-workflow-mcp/abi/AcademicWorkflow.idr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
module AcademicWorkflow

||| Paper metadata
public record Paper where
public export
record Paper where
constructor MkPaper
title : String
authors : List String
Expand All @@ -13,35 +14,40 @@ public record Paper where
abstract : String

||| Citation format
public data CitationFormat =
| BibTeX
public export
data CitationFormat
= BibTeX
| CSL
| RIS
| EndNote

||| Proof that a citation format is supported
public data Supported : CitationFormat -> Type where
public export
data Supported : CitationFormat -> Type where
SupportedBibTeX : Supported BibTeX
SupportedCSL : Supported CSL
SupportedRIS : Supported RIS
SupportedEndNote : Supported EndNote

||| Review annotation
public record ReviewNote where
public export
record ReviewNote where
constructor MkReviewNote
page : Nat
text : String
category : String -- "typo", "unclear", "question", "suggestion"

||| Zotero collection reference
public record ZoteroCollection where
public export
record ZoteroCollection where
constructor MkZoteroCollection
id : String
name : String
itemCount : Nat

||| Academic workflow operations
public interface AcademicWorkflow.Workflow (m : Type -> Type) where
public export
interface Workflow (m : Type -> Type) where
||| Search Zotero collections
searchZotero : (query : String) -> m (List ZoteroCollection)

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

||| Loopback proof: academic-mcp runs on 127.0.0.1:5174
public data IsLoopback : (port : Nat) -> Type where
public export
data IsLoopback : (port : Nat) -> Type where
LoopbackProof : IsLoopback 5174

export
Expand Down
14 changes: 8 additions & 6 deletions cartridges/bofig-mcp/abi/Bofig.idr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- SPDX-License-Identifier: MPL-2.0
-- Bofig Cartridge ABI — Evidence graph query interface

module ABI.Bofig
module Bofig

%language ElabReflection

Expand Down Expand Up @@ -75,10 +75,12 @@ interface Bofig.Graph where
-- Get graph statistics
getGraphStats : IO (Nat, Nat) -- (nodes, edges)

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

||| Loopback proof: cartridge binds on localhost only.
public export
Loopback.proof : IsLoopback 5178
Loopback.proof = ()
data IsLoopback : (port : Nat) -> Type where
LoopbackProof : IsLoopback 5178

export
loopbackInvariant : IsLoopback 5178
loopbackInvariant = LoopbackProof
2 changes: 1 addition & 1 deletion cartridges/burble-admin-mcp/abi/BurbleAdmin/Protocol.idr
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data RequiresPermission : Operation -> PermLevel -> Type where

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

-- ═══════════════════════════════════════════════════════════════════════
-- C ABI Exports
Expand Down
1 change: 1 addition & 0 deletions cartridges/civic-connect-mcp/abi/CivicConnect/Protocol.idr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
module CivicConnect.Protocol

import Data.Nat
import Data.List

||| CivicConnect operation codes.
public export
Expand Down
4 changes: 2 additions & 2 deletions cartridges/conflow-mcp/abi/Conflow/Protocol.idr
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ record ApplyResult where
constructor MkApplyResult
applied : Nat
skipped : Nat
total : Nat
sumPrf : applied + skipped = total
totalCount : Nat
sumPrf : applied + skipped = totalCount

||| Proof: a valid config with no errors has empty error list.
export
Expand Down
16 changes: 9 additions & 7 deletions cartridges/ephapax-mcp/abi/Ephapax.idr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- SPDX-License-Identifier: MPL-2.0
-- Ephapax Cartridge ABI — Proof-compiler query interface

module ABI.Ephapax
module Ephapax

%language ElabReflection

Expand Down Expand Up @@ -31,7 +31,7 @@ record QueryResult where
constructor MkQueryResult
success : Bool
message : String
data : String
payload : String

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

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

||| Loopback proof: cartridge binds on localhost only.
public export
Loopback.proof : IsLoopback 5175
Loopback.proof = ()
data IsLoopback : (port : Nat) -> Type where
LoopbackProof : IsLoopback 5175

export
loopbackInvariant : IsLoopback 5175
loopbackInvariant = LoopbackProof
14 changes: 8 additions & 6 deletions cartridges/fireflag-mcp/abi/Fireflag.idr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- SPDX-License-Identifier: MPL-2.0
-- Fireflag Cartridge ABI — Extension-to-MCP mapping interface

module ABI.Fireflag
module Fireflag

%language ElabReflection

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

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

||| Loopback proof: cartridge binds on localhost only.
public export
Loopback.proof : IsLoopback 5177
Loopback.proof = ()
data IsLoopback : (port : Nat) -> Type where
LoopbackProof : IsLoopback 5177

export
loopbackInvariant : IsLoopback 5177
loopbackInvariant = LoopbackProof
20 changes: 13 additions & 7 deletions cartridges/hesiod-mcp/abi/Hesiod.idr
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
module Hesiod

||| DNS record type enumeration (proof-indexed)
public data DNSRecordType =
| A -- IPv4 address
public export
data DNSRecordType
= A -- IPv4 address
| AAAA -- IPv6 address
| CNAME -- Canonical name
| MX -- Mail exchange
Expand All @@ -15,7 +16,8 @@ public data DNSRecordType =
| SRV -- Service record

||| Proof that a record type is queryable
public data Queryable : DNSRecordType -> Type where
public export
data Queryable : DNSRecordType -> Type where
QueryableA : Queryable A
QueryableAAAA : Queryable AAAA
QueryableCNAME : Queryable CNAME
Expand All @@ -26,23 +28,26 @@ public data Queryable : DNSRecordType -> Type where
QueryableSRV : Queryable SRV

||| DNS response record
public record DNSRecord where
public export
record DNSRecord where
constructor MkDNSRecord
name : String
type : DNSRecordType
ttl : Nat
value : String

||| Lookup result type (success or failure)
public data LookupResult : Type where
public export
data LookupResult : Type where
Success : (records : List DNSRecord) -> LookupResult
NotFound : (hostname : String) -> LookupResult
NetworkError : (message : String) -> LookupResult
Timeout : (hostname : String) -> (seconds : Nat) -> LookupResult

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

||| Loopback proof: hesiod-mcp only runs on localhost:5173
public data IsLoopback : (port : Nat) -> Type where
public export
data IsLoopback : (port : Nat) -> Type where
LoopbackProof : IsLoopback 5173

export
Expand Down
Loading
Loading