Skip to content

L11: decide graded necessity, spec QTT grade propagation — and fix Modal.idr, which had never compiled - #51

Merged
hyperpolymath merged 1 commit into
mainfrom
feat/l11-qtt-integration
Jul 16, 2026
Merged

hyperpolymath merged 1 commit into
mainfrom
feat/l11-qtt-integration

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Jul 16, 2026 •

Copy link
Copy Markdown
Owner

Closes MOTIVATION.adoc open items 1 (semantics) and 2 (QTT integration).
Then, installing Idris2 and actually running it turned up rather more than that.

TL;DR

Semantics Decided: neither S4 nor contextual — graded necessity □_r A
QTT integration Specified — split=+, comult=·, drop=0, unbox gated on 1 ⊑ r
Biggest finding Modal.idr had never compiled. The comonad laws were verified by nothing
Root cause Idris2 was never installed here, and CI doesn't gate research/
Second finding dup pins r = ω — L11 currently adds nothing over L10 (machine-checked)
Third finding TropicalKleene.idr is also broken (recorded, not fixed here)

1. Semantics — decided: neither

The question ("S4 vs contextual?") was malformed. The two refine □ along
different axes — contextual ([Ψ]A) refines which variables the box may capture;
graded (□_r A) refines how many times its content may be used. They are orthogonal
(one could have [Ψ]_r A), so the question had no answer as posed. L11 exists to
discipline usage, so it takes the quantity axis. S4 is not lost — it is the r = ω
instance. Rationale in SEMANTICS-DECISION.adoc; CMTT assessment in
reading-notes/contextual-modal-types.adoc.

Strict S4 was never really on the table anyway: openPool captures the ordinary variable
uri, so the sketch's own worked example is ill-typed under it. mkBox : a -> Box a had
no promotion rule at all — "S4" was a label on an unruled constructor.

2. QTT integration — specified

QTT-INTEGRATION.adoc: promotion scales the context (r · Γ); unbox extracts at grade
1, admissible iff 1 ⊑ r; dup → split over +; comult over ·; drop at 0.
Resolves the informal "grade of unbox should be ≤ grade of the enclosing context" into
an actual side condition.

3. The big one: Modal.idr had never compiled

Error: While processing type of comonadLaw1. Ambiguous elaboration.
    Modal.dup b
    Prelude.dup b
Error: No type declaration for Modal.comonadLaw1.

Idris2 0.7.0's Prelude exports dup : a -> (a, a). Every signature mentioning dup
failed to elaborate, so comonadLaw1/comonadLaw2 had no type declarations and were
verified by nothing
— while the header, the checklist, and STATE.a2ml all recorded them
as proved, believe_me-free, at 100%.

Pre-existing, not from this branch: origin/main's copy fails identically.

Root cause: Idris2 was never installed in this estate. IDRIS2_PREFIX and PACK_DIR
were exported and pack/bin was on PATH, but the pack tree did not exist — so
idris2 --check had never once been runnable locally. CI gates only
a-sounder-constitution/, not research/. An unrunnable proof gate produces confident
false records.

Fixed with %hide Prelude.dup. Modal.idr now checks clean, exit 0 — the laws are proved
for the first time.

4. dup pins the grade to ω — machine-checked

DupForcesOmega.idr (exit 0):

dupForcesOmega : (r : Multiplicity) -> plus r r = r -> Either (r = Zero) (r = Many)
dupForcesOmega Zero Refl = Left Refl
dupForcesOmega One  Refl impossible
dupForcesOmega Many Refl = Right Refl

The One case is impossible: plus One One reduces to Many, so the premise would
need Refl : Many = One. That one unwritable clause is the whole defect. Since r = 0 is
erased, every useful dup pins r = ω — Box a is isomorphic to an L10 value at
multiplicity ω, so L11 currently adds nothing over L10.
Exactly what MOTIVATION predicted
in prose; now a proof.

The file also supplies the plus/mult tables Multiplicity never had — it is a bare
enumeration despite its section being titled "semiring". That absence is why the defect
survived:
with no + in scope, r + r = r was never a question anyone could ask.

The clash was never a coincidence. Prelude.dup is unrestricted duplication, and at
r = ω the box's dup is Prelude.dup up to the MkBox wrapper. The compiler had
been pointing at the defect the entire time.

5. Second broken proof (recorded, not fixed)

research/tropical/TropicalKleene.idr also fails (exit 1):

costMatAddZeroL m1 m2 i h =
  rewrite h                    -- needs `rewrite h in ...`
  exact latAddZeroL (m2 i i)   -- `exact` is not Idris2 term syntax at all

It uses syntax that has never existed in Idris2 — it cannot ever have been run. Needs
its own PR; there may be more behind the first parse failure.

Proof sweep (idris2 0.7.0, recorded in STATE maintenance-status)

PASS  research/level-candidates/L11-modal-box/Modal.idr        (after this fix)
PASS  research/level-candidates/L11-modal-box/DupForcesOmega.idr
PASS  a-sounder-constitution/formal/Constitution.idr           (the CI-gated one)
FAIL  research/tropical/TropicalKleene.idr                     (pre-existing)

Do not conflate the two grades

Grades here are usage grades over {0,1,ω} — not the tropical disclosure grades
of epistemic-types' EchoBridge/Echo (the RMR/RMO axis). Both are graded comonads: same
shape, different semiring, different theorem. Fusing them would identify "used twice" with
"leaked twice". QTT-INTEGRATION.adoc §1.

Graduation

2 of 6 done (one only as of today), 1 spec'd, 3 not started, plus the rewrite this
surfaced.
Not close. First graduated artefact promoted to typell stays at 0%;
STATE 25% → 30%, open-failures 0 → 1.

Recommended next, in order

  1. Extend the Idris2 CI gate to research/ — both modules check in <1s now. Until it
    exists, no research milestone in STATE.a2ml is trustworthy.
  2. Audit other estate repos recording Idris2 milestones for the same unrunnable-gate
    failure mode.
  3. Fix TropicalKleene.idr (own PR).
  4. Rewrite Modal.idr to graded Box.

🤖 Generated with Claude Code

…MTT reading notes

Closes MOTIVATION.adoc open items 1 (semantics) and 2 (QTT integration), and
records a defect that closing them surfaced.

Semantics -- DECIDED: neither S4 nor contextual. The question was malformed.
Contextual modal types (Nanevski-Pfenning-Pientka 2008) refine the box along the
*context* axis (which variables may it depend on); graded necessity refines it
along the *quantity* axis (how many times may its content be used). They are
orthogonal, not rival, so "S4 vs contextual" had no answer as posed. L11 exists
to discipline usage, so it adopts graded necessity `Box r a`
(Orchard-Liepelt-Eades, ICFP'19). S4 is not lost: it is the r = omega instance.

QTT integration -- SPECIFIED: promotion scales the context (r . G); unbox
extracts at grade 1, admissible iff 1 <= r; dup becomes split over (+); comult
over (*); drop at 0.

Findings that invalidate the current sketch:

* `dup : Box a -> (Box a, Box a)` is admissible only where r + r = r, hence
  r in {0, omega}; and r = 0 is erased, so every useful instance pins r = omega.
  `Box a` is therefore isomorphic to an L10 value at multiplicity omega, and L11
  currently adds NOTHING over L10. This is the exact failure MOTIVATION.adoc
  predicted in prose -- now a proof rather than a worry. Blocks graduation.

* `openPool` captures the ordinary variable `uri`, so the sketch's own worked
  example is ill-typed under any honest strict-S4 reading. S4 was never really on
  the table; it was a label on an unruled constructor (`mkBox : a -> Box a` has
  no promotion rule at all).

* `Multiplicity` is a bare enumeration despite its section being titled
  "semiring": no (+), no (*), no laws, and `Box` is not indexed by it. That
  absence is why the dup defect went unnoticed -- with no (+), `r + r = r` was
  never a question anyone could ask.

* comonadLaw1/comonadLaw2 are sound but are the omega-instance laws only.

Two stale records corrected: research/level-candidates/README.adoc still claimed
"2 open believe_me proofs" (they are proved), and Modal.idr's checklist pointed
at the pre-rename `katagoria/` path.

Grades here are USAGE grades over L10's {0,1,omega} semiring. They are NOT the
tropical disclosure grades of epistemic-types' EchoBridge (the RMR/RMO axis);
conflating them would identify "used twice" with "leaked twice". See
QTT-INTEGRATION.adoc s1.

Graduation: 2 of 6 checklist items done (one qualified), 1 specified, 3 not
started, plus the rewrite this surfaced. "First graduated artefact promoted to
typell" stays at 0%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@hyperpolymath
hyperpolymath marked this pull request as ready for review July 16, 2026 21:32
@hyperpolymath
hyperpolymath merged commit 1b3bcec into main Jul 16, 2026
13 checks passed
@hyperpolymath
hyperpolymath deleted the feat/l11-qtt-integration branch July 16, 2026 21:32
@hyperpolymath hyperpolymath changed the title docs(L11): decide graded necessity, spec QTT grade propagation, add CMTT reading notes L11: decide graded necessity, spec QTT grade propagation — and fix Modal.idr, which had never compiled Jul 16, 2026
hyperpolymath added a commit that referenced this pull request Jul 17, 2026
…and #51's fix never landed) (#52)

## Why this exists

**PR #51 was merged 88 seconds after its first commit landed.** Its
second commit — the one with the actual fix — was pushed ten minutes
*later*, to a branch whose PR was already closed. It never landed.

| Event | Time (UTC) |
|---|---|
| `cf7f211` pushed (docs) | 21:30:50 |
| **#51 merged** | **21:32:18** |
| `04e37c8` committed (the fix) | 21:42:55 |

So `main` right now says *"comonad laws proved (`believe_me`-free)"*
while `main`'s `Modal.idr` **does not compile**. The docs made `main`
sound more authoritative while the fix sat on a dead branch. **Commit 1
re-ships `04e37c8` verbatim** (tree-identical; verified).

Then I asked why nothing caught it, and that turned out to be the real
bug.

## The real defect: this repo's proof checks cannot fail

| # | Hole | Effect |
|---|---|---|
| 1 | `just proof-check-idris2` did `exit 0` when idris2 was absent |
idris2 **was** absent → recipe green on every machine. It passed
**because** nothing could check the proofs. |
| 2 | Same recipe passed a **full path** to `idris2 --check` | Idris2
derives module name from path → **verdict inverted**: broken
`ABI/Compliance.idr` → *OK*; working `ABI/Foreign.idr` → *FAIL* |
| 3 | CI gate path-filtered to `a-sounder-constitution/formal/` |
Created after this same bug shipped there once (**#45**) — then it
walked into `research/` |
| 4 | **Upstream:** `idris2 --check` **exits 0 on a missing import** |
`idris2 --check X && ok` is unsound — including #45's own gate |

Hole 4, verified in a clean room against 0.7.0:

| Failure mode | Exit |
|---|---|
| **Missing import** | **0** ← |
| Type error | 1 |
| Parse error | 1 |
| Module name mismatch | 1 |

They share one shape: **a check that cannot fail.** That's not a weak
check, it's a null check that emits reassuring text — and every status
file downstream inherits the false confidence.

## What the first-ever full sweep found

**6 of 13 modules don't compile. Five are in `verification/proofs/` — a
directory named _proofs_, where only 1 of 6 modules compiles.**

One shared fingerprint: `LTE` / `NonZero` / `modNatNZ` used with **no
`import Data.Nat`**. Those were Prelude in Idris**1**, `Data.Nat` in
Idris**2**. This is never-compiled Idris1-era code, not rot.

## The fix

`scripts/check-idris2-proofs.sh` — single source of truth for CI **and**
the Justfile, so local green and CI green mean the same thing:

- **Missing toolchain is fatal**, never a skip
- Each module checked **from its own source root**
- Verdict = **exit 0 AND no `Error:`** in output (defeats hole 4)
- **Quarantine must keep failing** — if one starts passing, the script
fails and tells you to promote it, so the list can't rot
- **An `.idr` absent from the manifest is an error** — new proofs are
gated by default rather than by memory

### The gate was tested by watching it fail

A gate never observed failing is the bug under investigation, so:

| Test | Result |
|---|---|
| Unlisted `.idr` in tree | ✅ red |
| idris2 absent | ✅ red (was green) |
| Missing import (bare idris2 exits **0**) | ✅ red |
| Gated module regresses (un-`%hide` the fix) | ✅ red — **would have
caught the original** |
| Quarantined module starts passing | ✅ red, demands promotion |

Result: 8 gated PASS · 5 quarantined fail as recorded · 13/13 accounted
for.

## Correcting the record

**`PROOF-STATUS.md` is NOT stale.** It says *0/7 proven* — and 5 of the
6 modules behind those obligations don't compile. It tracks
`verification/proofs/idris2/` only, so `a-sounder-constitution` and L11
landing never made it stale. **It was the one status file in this repo
telling the truth, and `STATE.a2ml` accused it of lying.** That claim
was mine. It's withdrawn.

Also softened: *"idris2 was never installed in this estate"* is too
strong — committed `.ttc` artefacts show it ran somewhere around Aug
2025.

## Not fixed here (recorded in `STATE.a2ml`)

- **`verification/proofs/idris2/` — attempted during the CI wait and
reverted; it is NOT a typo sweep.** `import Data.Nat` is necessary but
only unmasks the next layer:

  | Module | Error after the import |
  |---|---|
  | `Types.idr` | `.inBounds` not accessible |
  | `ABI/Platform.idr` | `Undefined name lteRefl` |
| `ABI/Layout.idr` | `Can't solve constraint: S ?x vs f .fieldAlignment`
|
| `ABI/Pointers.idr` | `.nonNull` not accessible + unification failure |

`Types.idr` and `ABI/Pointers.idr` share a **design error, not a typo**:
both declare a proof field at **quantity 0** (`{auto 0 inBounds : LTE
value max}`) and then project it into a value position (`boundedLeMax b
= b.inBounds`). A 0-quantity field is erased and cannot be returned as a
value — inaccessible by construction. Needs a deliberate decision about
the records' quantities. My earlier estimate "one import line each" was
unverified and is **withdrawn** (commit `884389c`).

Near-miss worth recording: the first check of `Types.idr` *looked* green
because output was truncated at `head -8` and the exit code read was the
pipe's, not Idris2's. Re-run through this branch's own verdict logic
(exit 0 **and** no `^Error:`), it still failed — the same class of
mistake this branch exists to fix.
- `TropicalKleene.idr` (own PR)
- **`just validate-state` has never validated this project**: it greps
for TOML `[metadata]` in a *committed S-expression template leftover*
that still says `(project "rsr-template-repo")`, prints `INVALID`, and
**exits 0**

## ⚠️ Estate-wide, measured not guessed

The `exit 0` skip is **rsr-template-repo's**, in **22 repos — 20 with
real proof files** (paint-type 53, ideas-to-alphas 31, proof-burrower
18, snifs 15). **All four prover recipes share it**
(idris2/lean4/agda/coq).

**Live right now: `lean` is installed nowhere, so every `just
proof-check-lean4` in the estate is green having verified nothing.**

Fix the template first, or it re-seeds. Detection: `grep -rlE
'SKIP:.*not installed' */Justfile`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jul 17, 2026
…#53)

## What
Make main's **Idris2 Proof** gate pass. It is **red on main right now**
(run on merge commit `91b16104` = failure).

## Why main went red
PR #52 merged the sound *gate* but with a broken *toolchain step*. The
workflow's "Build & install Idris2" ran `tar xzf` **inside the
checkout**, extracting `Idris2-0.7.0/` — which ships hundreds of `.idr`
files under `libs/`, `benchmark/`, `docs/` — into the very tree the gate
sweeps. The gate's rule "any `.idr` not in the manifest is an error"
then fired on the *compiler's own sources*. Verified from the
run-29559473259 log: `FAIL: Idris2 modules present on disk but absent
from the MANIFEST → Idris2-0.7.0/libs/base/...`.

The gate did exactly what it should. The workflow was polluting the tree
it scanned.

## Fix
- Build the toolchain in a `mktemp -d` **outside** the checkout, so the
only `.idr` the gate sees are this repo's.
- Untrack `src/interface/build/ttc/2025081600/**` — committed TTC caches
(`build/` is already gitignored; these predate the rule). *Not* the CI
cause — a genuine 0.7.0 build stamps ttc version `2023090800` and
ignores a `2025081600` cache — but build output never belongs in git.

## ⚠️ Do not merge until the Idris2 Proof check on this PR is green
This is the fix for a gate that merged red once already (#52), and #51's
real fix was likewise orphaned by a fast merge. Please let the check
complete. Consider making **Idris2 Proof** a required status check so a
red gate can't ride into main again.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Aug 28, 2026
PR #51 was merged 88s after its first commit, so the fix in its second
commit (04e37c8) never landed. main therefore carries docs asserting
Modal.idr's comonad laws are proved while main's Modal.idr does not
compile. Commit 1 of this branch re-ships that orphaned fix verbatim.

Investigating why nothing caught it turned up the real defect: this
repo's proof checks cannot fail.

  1. `just proof-check-idris2` did `exit 0` when idris2 was absent
     ("SKIP: idris2 not installed"). idris2 WAS absent, so the recipe
     was green on every machine in the estate -- it reported success
     *because* nothing could check the proofs. proof-check-all depends
     on it, so the whole suite was green.
  2. The same recipe passed a full path to `idris2 --check`, but Idris2
     derives the expected module name from the path. Verified: it called
     the broken ABI/Compliance.idr OK and the working ABI/Foreign.idr
     FAIL -- inverted on both discriminating cases.
  3. The CI gate installed idris2 correctly but was path-filtered to
     a-sounder-constitution/formal/ -- created after this same bug shipped
     there once (#45). A gate scoped to where the bug last happened only
     prevents the last bug. It walked straight into research/.
  4. Upstream: `idris2 --check` EXITS 0 on a missing import while printing
     "Error: Module X not found" (verified in a clean room against 0.7.0;
     type errors, parse errors and name mismatches all exit 1). Any gate
     of the form `idris2 --check X && ok` is unsound, including #45's.

What the sweep found, running Idris2 over this repo for the first time:
6 of 13 modules do not compile, and 5 of those 6 are in verification/proofs/
-- a directory named "proofs", of which only 1 of 6 modules compiles. One
shared fingerprint: LTE/NonZero/modNatNZ used with no `import Data.Nat`
(Prelude in Idris1, Data.Nat in Idris2). Never-compiled Idris1-era code.

scripts/check-idris2-proofs.sh is now the single source of truth for CI and
the Justfile. A missing toolchain is fatal, never a skip. Each module is
checked from its own source root. A verdict requires exit 0 AND no "Error:"
in the output. Known-broken modules are quarantined and must KEEP failing --
if one starts passing, the script fails and tells you to promote it, so the
list cannot rot. And an .idr absent from the manifest is an error, so new
proofs are gated by default rather than by memory.

The gate was adversarially tested against all five of its failure modes
(unlisted module, absent toolchain, missing-import-exits-0, gated
regression, quarantined module passing) -- each correctly turns it red.
Commenting out the %hide fix turns it red, so it would have caught the
original bug.

Corrects the record: PROOF-STATUS.md is NOT stale. It says 0/7 proven, and
5 of the 6 modules behind those obligations do not compile. It tracks
verification/proofs/idris2 only, so a-sounder-constitution and L11 landing
never made it stale. It was the one status file in this repo telling the
truth, and STATE.a2ml accused it of lying. That claim was mine; it is
withdrawn.

Not fixed here, recorded in STATE.a2ml: verification/proofs/idris2 (own PR);
TropicalKleene.idr (own PR); `just validate-state`, which greps for TOML
[metadata] in a committed S-expression template leftover that still says
(project "rsr-template-repo"), prints INVALID, and exits 0. And the estate:
the exit-0 skip is in 22 repos, 20 with real proof files (paint-type 53,
proof-burrower 18). All four prover recipes share it, so it is an
rsr-template-repo bug. `lean` is installed nowhere, so every
`just proof-check-lean4` in the estate is green right now having verified
nothing. Fix the template first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant