Skip to content

Commit 90260f4

Browse files
fix(ci): actually run the verdict round-trip against the reference checker (#34)
Phase 0 of `dev-notes/trope-family-completeness-plan-2026-07-28.md`, and the highest-leverage item in it. ## The gate was green while its central assertion never ran `tests/check-examples.sh` skipped the verdict round-trip whenever the sibling `tropecheck` binary was absent — and `trope-check.yml` checked out only haec and built no checker, so that binary **could not exist in CI**. The skip was therefore unconditional. The schema drift guard was skipping for the same reason. The round-trip is *the* seam joining the three repos: Haec emits Trope IR, the checker returns a verdict. Unexercised, the repository's core claim was asserted but never measured. ## A finding I have to correct My own gap analysis reported that the Idris2 reference checker "cannot build". **That was wrong**, and the correction matters. It builds fine under the right compiler. `verification/proofs/idris2` requires Idris 2 **0.8.0** — under 0.7.0, `Trope/Fidelity.idr:72` fails to parse at the lambda-impossible clause `No (\Refl impossible)`. **Nothing in either repo declares that requirement**, so reading local state (0.7.0 installed) suggests the reference is unbuildable and invites swapping in the Rust fast-core as the new reference. That would have been the wrong trade. ADR 0002/0003 chose Idris2 precisely so that *"the thing that runs is the thing that is proved"* — the executable reuses the proved verified core. Ratifying the conformance-validated-only Rust checker would have traded away the family's central epistemic claim **to fix a toolchain pin.** ## Measured under the digest-pinned `idris2-pack` image (Idris 2 0.8.0) ``` verified core ......... typechecks 8/8 modules, exit 0 tropecheck ............ builds, exit 0 conformance suite ..... all pass (13 verdict cases + 9 schema-invalid rejections) haec round-trip ....... 3/3 correct, witness e_collapse/fate.quality exact ``` The Rust fast-core agrees with the Idris2 reference on all of the above — the cross-validation the calculus §9.3 asks for, now actually performed. ## Changes - **`trope-check.yml`** runs in the pinned 0.8.0 container; checks out `trope-checker` as a **sibling** of `haec/` so `$ROOT/../trope-checker` resolves (which also re-activates the schema drift guard); installs `jq` (the image ships none); builds the reference checker; runs the gate with `TROPECHECK_REQUIRED=1`. - **`check-examples.sh`** honours `TROPECHECK_BIN` — the same variable trope-checker's own conformance runner already uses — and treats a missing binary as **fatal** under `TROPECHECK_REQUIRED=1` rather than skipping. - **The round-trip now asserts the WITNESS.** `EXAMPLES.json` has always declared `["e_collapse","fate.quality"]`; nothing ever checked it. - The checker exits non-zero on `p-insufficient` — a *verdict*, not an error — so the loop no longer aborts on it. ## Canary-tested — a gate that cannot fail is not a gate | Falsifier | Result | |---|---| | `TROPECHECK_BIN=/nonexistent` + `REQUIRED=1` | **fails**, naming the missing path | | expected witness corrupted to `e_WRONG` | **fails**, printing got vs expected | ## Follow-ups (not in this PR) - `trope-checker` should declare Idris 2 0.8.0 in `.tool-versions`/README so the next reader does not repeat my mistake, and run its own conformance suite in CI. - `trope-checker/build/guix.scm` still reads `(name "squisher-corpus")`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e765e0f commit 90260f4

2 files changed

Lines changed: 85 additions & 10 deletions

File tree

.github/workflows/trope-check.yml

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# SPDX-License-Identifier: MPL-2.0
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
4-
# Haec: every example lowers to a well-formed Trope IR document (bash + jq, no Python).
4+
# Haec: every example lowers to a well-formed Trope IR document AND round-trips
5+
# through the reference tropechecker to its expected verdict and witness.
6+
#
7+
# The round-trip is the one seam joining the three repos — Haec emits IR, the
8+
# checker returns a verdict — so it is built and asserted here rather than
9+
# skipped. TROPECHECK_REQUIRED=1 makes a missing binary a hard failure.
510
name: "🔴 Gate: Trope conformance"
611
on:
712
push:
@@ -16,7 +21,56 @@ concurrency:
1621
jobs:
1722
examples-lower-to-valid-ir:
1823
runs-on: ubuntu-latest
24+
timeout-minutes: 20
25+
# The reference checker is an Idris2 program whose verified core requires
26+
# Idris 2 *0.8.0*: under 0.7.0, Trope/Fidelity.idr fails to parse at the
27+
# lambda-impossible clause `No (\Refl impossible)`. Pinned by image digest.
28+
container:
29+
image: ghcr.io/stefan-hoeck/idris2-pack@sha256:de9781906050dc44704ec6de0108c86f899ef17b2642932b79e00245d613b8ad
1930
steps:
20-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
21-
- name: Examples lower to valid Trope IR
31+
- name: Checkout Haec
32+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33+
with:
34+
path: haec
35+
36+
# Checked out as a SIBLING of haec/ so tests/check-examples.sh finds it at
37+
# $ROOT/../trope-checker — which also activates the schema drift guard.
38+
- name: Checkout trope-checker (reference checker + canonical IR schema)
39+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40+
with:
41+
repository: hyperpolymath/trope-checker
42+
ref: 69221ad6bec0c9286cb5c917edfad281e7e0089e # main @ 2026-07-28
43+
path: trope-checker
44+
45+
# The pinned idris2-pack image is minimal and ships no jq, which both the
46+
# test script and the conformance runner require.
47+
- name: Install container prerequisites (jq)
48+
shell: bash
49+
run: |
50+
set -euo pipefail
51+
if ! command -v jq >/dev/null 2>&1; then
52+
apt-get update -qq
53+
apt-get install -y -qq --no-install-recommends jq
54+
fi
55+
jq --version
56+
idris2 --version
57+
58+
# ADR 0002/0003: the Idris2 executable is the reference checker and reuses
59+
# the *proved* verified core, so the thing that runs is the thing that is
60+
# proved. Installing that core is therefore part of building the oracle.
61+
- name: Build the Idris2 reference checker
62+
shell: bash
63+
working-directory: trope-checker
64+
run: |
65+
set -euo pipefail
66+
idris2 --install verification/proofs/idris2/trope.ipkg
67+
( cd src/idris2 && idris2 --build tropecheck.ipkg )
68+
test -x src/idris2/build/exec/tropecheck
69+
echo "reference checker built"
70+
71+
- name: Examples lower to valid Trope IR, and round-trip to their verdicts
72+
shell: bash
73+
working-directory: haec
74+
env:
75+
TROPECHECK_REQUIRED: '1'
2276
run: bash tests/check-examples.sh

tests/check-examples.sh

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,38 @@ else
4040
echo -e "${Y}note${O} sibling trope-checker not checked out; skipping schema-drift guard"
4141
fi
4242

43-
# 4. full round-trip: verdicts via the sibling Idris2 checker, if built.
44-
BIN="$SIB/src/idris2/build/exec/tropecheck"
45-
if [ -x "$BIN" ]; then
43+
# 4. full round-trip: verdicts via the reference checker.
44+
#
45+
# TROPECHECK_BIN names the binary (same variable trope-checker's own conformance
46+
# runner honours); it defaults to the sibling Idris2 reference build. Set
47+
# TROPECHECK_REQUIRED=1 — as CI does — to make a missing binary a hard failure
48+
# rather than a skip: a round-trip that silently does not run is a fake gate.
49+
BIN="${TROPECHECK_BIN:-$SIB/src/idris2/build/exec/tropecheck}"
50+
if [ ! -x "$BIN" ] && [ "${TROPECHECK_REQUIRED:-0}" = "1" ]; then
51+
echo -e "${R}FAIL${O} TROPECHECK_REQUIRED=1 but no checker binary at $BIN"
52+
echo " build it with: idris2 --install verification/proofs/idris2/trope.ipkg"
53+
echo " idris2 --build src/idris2/tropecheck.ipkg (Idris2 0.8.0)"
54+
fail=1
55+
elif [ -x "$BIN" ]; then
4656
while read -r e; do
4757
ir=$(jq -r '.ir' <<<"$e"); exp=$(jq -r '.expect' <<<"$e")
48-
got=$("$BIN" "$EX/$ir" | awk '{print $1}')
49-
if [ "$got" = "$exp" ]; then echo -e "${G}ok${O} round-trip $ir$got"
50-
else echo -e "${R}FAIL${O} round-trip $ir: got $got, expected $exp"; fail=1; fi
58+
# the checker exits non-zero on p-insufficient — that is a verdict, not an
59+
# error, so do not let it abort the loop.
60+
out=$("$BIN" "$EX/$ir" || true)
61+
got=$(awk '{print $1}' <<<"$out")
62+
wexp=$(jq -r '.witness // [] | join("/")' <<<"$e")
63+
ok=1
64+
[ "$got" = "$exp" ] || ok=0
65+
if [ -n "$wexp" ]; then
66+
gw=$(grep -o 'witness=[^[:space:]]*' <<<"$out" | cut -d= -f2)
67+
gc=$(grep -o 'coord=[^[:space:]]*' <<<"$out" | cut -d= -f2)
68+
[ "$gw/$gc" = "$wexp" ] || ok=0
69+
fi
70+
if [ "$ok" = 1 ]; then echo -e "${G}ok${O} round-trip $ir$got${wexp:+ (witness $wexp)}"
71+
else echo -e "${R}FAIL${O} round-trip $ir: got '$out', expected $exp${wexp:+ witness $wexp}"; fail=1; fi
5172
done < <(jq -c '.examples[]' "$EX/EXAMPLES.json")
5273
else
53-
echo -e "${Y}note${O} sibling tropecheck binary not built; skipping verdict round-trip (run 'just trope-build' in ../trope-checker)"
74+
echo -e "${Y}note${O} no checker binary; skipping verdict round-trip (set TROPECHECK_BIN, or TROPECHECK_REQUIRED=1 to make this fatal)"
5475
fi
5576

5677
if [ "$fail" = 0 ]; then echo -e "\n${G}examples: all lower to valid Trope IR${O}"; else echo -e "\n${R}examples: failures${O}"; fi

0 commit comments

Comments
 (0)