Skip to content

Commit 64eb1d1

Browse files
hyperpolymathclaude
andcommitted
feat(disciplines): Axis-1-first reframe + provers_serving() cross-index
Per UX concern: a user searching for "Agda" / "Coq" / "Z3" expects to land somewhere that feels like it's about provers. Calling the 40 new TypeDiscipline variants *disciplines* is technically correct but loses that person. Fix: treat prover and discipline as **orthogonal axes**, not one replacing the other, and add the prover-first cross-index. - Rewrite the module docstring to lead with the two-axis framing (Axis 1 = `ProverKind` real binaries; Axis 2 = `TypeDiscipline` what the reasoning problem is shaped like). Both are first-class. Explicit invitation for both discovery paths: search-by-prover lands on a discipline list; search-by-discipline lands on a prover list. No more "disciplines, and by the way there are also provers". - Add `TypeDiscipline::provers_serving() -> Vec<ProverKind>`: for any discipline, returns the *classical* Axis-1 provers that natively serve it (Agda / Coq / Lean / Idris2 for Dependent; Idris2 for Linear / Affine / QTT via Idris2 QTT; Agda for Cubical; NuSMV + SPIN + TLC + UPPAAL + Prism + TLAPS for Temporal; Dafny + Viper + FramaC + KeY + Why3 for Hoare; …). Conservative by construction — only mainstream-documented support listed; speculative encodings omitted rather than misdirecting users. - Empty `Vec` means "no classical prover in echidna's lineup has mainstream coverage; the HP dispatcher is the only known route". Not a judgement on importance — Session / Choreographic / Dyadic / Echo / Tropical land there because the HP stack is *specifically* there to serve them. - Three new tests: * `provers_serving_only_lists_classical_kinds`: guarantees the cross-index never lists an HP-ecosystem `*TypeChecker` (a user who arrives via "which prover serves linear types?" must not see `LinearTypeChecker` in the answer — they're already there). * `provers_serving_is_deduplicated`: per-discipline list has no duplicates. * `provers_serving_canonical_spot_checks`: ground-truth anchors in mainstream practice (Agda does dependent + cubical; NuSMV does temporal; Dafny + Viper + KeY do Hoare; Session / Choreographic / Dyadic / Echo have no classical provers; etc). Breaking any of these should be a deliberate taxonomy decision. All 11 disciplines tests pass (8 original + 3 new). Lib test total: 582 pass / 0 fail / 2 ignored — unchanged from the TypeDiscipline transition commit (8f573f1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4d28106 commit 64eb1d1

1 file changed

Lines changed: 233 additions & 15 deletions

File tree

src/rust/disciplines/mod.rs

Lines changed: 233 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,53 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33

4-
//! Canonical type-discipline taxonomy for echidna.
4+
//! Type-discipline taxonomy — **Axis 2** of echidna's prover classification.
55
//!
6-
//! This module is the single source of truth for every type discipline that
7-
//! echidna can route, encode, validate, or reason about. It is the downstream
8-
//! target for `developer-ecosystem/katagoria/` — when katagoria graduates a
9-
//! new level in its type-theory pipeline (`katagoria → typell → typed-wasm
10-
//! → PanLL`), the corresponding variant should already exist here.
6+
//! # Two orthogonal axes
117
//!
12-
//! # Design
8+
//! Echidna classifies proving tools along two independent axes:
9+
//!
10+
//! * **Axis 1 — `ProverKind`** in `src/rust/provers/mod.rs`. The thing
11+
//! people type into a search box: *Agda*, *Coq*, *Lean 4*, *Z3*, *CVC5*,
12+
//! *Isabelle*, *HOL Light*, *Idris2*, *F\**, … Real binaries with
13+
//! websites, papers, and communities of practice.
14+
//! * **Axis 2 — `TypeDiscipline`** (this module). The thing the prover is
15+
//! *applied to*: dependent types, linear types, modal logic, session
16+
//! types, tropical resource typing, homotopy type theory, algebraic
17+
//! effects, … The semantic shape of the reasoning problem.
18+
//!
19+
//! Both axes must be first-class. A user arriving from "Agda" wants to see
20+
//! that Agda serves the dependent, indexed, higher-kinded, cubical, and
21+
//! homotopy disciplines. A user arriving from "linear types" wants to see
22+
//! that Idris2 (via QTT) and the HP-ecosystem `LinearTypeChecker` can
23+
//! serve it. Cross-links in both directions: discipline → provers via
24+
//! [`TypeDiscipline::provers_serving`]; prover → disciplines via the
25+
//! convention that `ProverKind::*TypeChecker` is the HP dispatch route and
26+
//! classical provers have their discipline coverage listed in the mapping
27+
//! below.
28+
//!
29+
//! # Relationship to katagoria
30+
//!
31+
//! This module is the downstream target for `developer-ecosystem/katagoria/`.
32+
//! When katagoria graduates a new level in its type-theory pipeline
33+
//! (`katagoria → typell → typed-wasm → PanLL`), the corresponding variant
34+
//! is expected to already exist here.
35+
//!
36+
//! # Design notes
1337
//!
1438
//! The enum is deliberately exhaustive up-front. Opening it again is
1539
//! expensive: `ProverKind::*TypeChecker` is referenced in six exhaustive
1640
//! match arms in `src/rust/provers/mod.rs` plus the HP-ecosystem dispatch
17-
//! table. Every variant added here has an immediate 1:1 mapping to a
18-
//! `ProverKind::*TypeChecker` — adding a discipline means adding both.
41+
//! table. Every variant here has an immediate 1:1 mapping to a
42+
//! `ProverKind::*TypeChecker` via [`TypeDiscipline::prover_kind`] — adding
43+
//! a discipline means adding both.
1944
//!
20-
//! Phase 1 (this transition) keeps wiring at the dispatch level only — each
21-
//! discipline routes to `typell --discipline=<tag>` (or the two named
22-
//! upstreams for `Tropical` / `Katagoria`). Phase 2 followups (see
23-
//! `AI-WORK-todo.md`) deepen the support: per-discipline proof encoding,
24-
//! Idris2 validator tagging, family-aware GNN features, discipline-tagged
25-
//! corpus fixtures.
45+
//! Phase 1 (the transition landed 2026-04-17) keeps wiring at the dispatch
46+
//! level only — each discipline routes to `typell --discipline=<tag>` (or
47+
//! the two named upstreams for `Tropical` / `Katagoria`). Phase 2 followups
48+
//! (see `AI-WORK-todo.md`) deepen the support: per-discipline proof
49+
//! encoding, Idris2 validator tagging, family-aware GNN features,
50+
//! discipline-tagged corpus fixtures.
2651
//!
2752
//! # Family grouping
2853
//!
@@ -344,6 +369,123 @@ impl TypeDiscipline {
344369
}
345370
}
346371

372+
/// Which **classical** (Axis-1) provers can natively serve this
373+
/// discipline, beyond the HP-ecosystem dispatch route.
374+
///
375+
/// Returned kinds are the real binaries a user would reach for if they
376+
/// searched for this discipline by name — Agda for dependent types, NuSMV
377+
/// for temporal logic, Viper for separation, and so on. The HP-ecosystem
378+
/// dispatcher (available through [`prover_kind`]) is always a valid route
379+
/// and is intentionally *not* listed here — this helper exists to power
380+
/// Axis-1-first discovery.
381+
///
382+
/// An empty `Vec` means no classical prover in echidna's current lineup
383+
/// has mainstream coverage of the discipline; the HP dispatcher is the
384+
/// only known route. That is not a judgement on the discipline's
385+
/// importance — several communication-style disciplines (Session,
386+
/// Choreographic, Echo, Dyadic) live entirely in the research-prototype
387+
/// world that the HP stack is specifically here to serve.
388+
///
389+
/// Conservative by construction: a prover is only listed when its
390+
/// mainstream documentation explicitly advertises the discipline, or
391+
/// when a widely-used published encoding exists. Extend carefully;
392+
/// spurious entries mislead users worse than empty lists.
393+
pub fn provers_serving(self) -> Vec<ProverKind> {
394+
use ProverKind as P;
395+
use TypeDiscipline as D;
396+
match self {
397+
// Foundation — simply-typed lambda calculus. Everyone who
398+
// supports anything supports this; listing the proof assistants
399+
// that have explicit STLC tutorials.
400+
D::Ordinary => {
401+
vec![P::Agda, P::Coq, P::Lean, P::Isabelle, P::Idris2, P::FStar]
402+
}
403+
404+
// Polymorphism family.
405+
D::Polymorphic => vec![
406+
P::Agda,
407+
P::Coq,
408+
P::Lean,
409+
P::Isabelle,
410+
P::Idris2,
411+
P::FStar,
412+
P::HOL4,
413+
P::HOLLight,
414+
P::PVS,
415+
],
416+
D::Existential => vec![P::Agda, P::Coq, P::Lean, P::Idris2, P::FStar],
417+
D::HigherKinded => vec![P::Agda, P::Coq, P::Lean, P::Idris2, P::FStar],
418+
D::Row => vec![], // Koka-native; none in echidna's classical lineup.
419+
D::Phantom => vec![
420+
P::Agda, P::Coq, P::Lean, P::Idris2, P::FStar, P::Dafny,
421+
],
422+
423+
// Subtyping family.
424+
D::Subtyping => vec![P::FStar, P::Dafny],
425+
D::Intersection => vec![], // Flow / Typed Racket territory; none classical.
426+
D::Union => vec![P::FStar, P::Dafny],
427+
D::Gradual => vec![], // Grift / Reticulated Python; none in echidna.
428+
429+
// Dependent / refinement family.
430+
D::Dependent => vec![P::Agda, P::Coq, P::Lean, P::Idris2, P::FStar, P::PVS],
431+
// LiquidHaskell is the canonical refinement-type home but is
432+
// not in echidna's classical lineup, so it's intentionally
433+
// absent here.
434+
D::Refinement => vec![P::FStar, P::Dafny, P::Why3],
435+
D::Hoare => vec![P::Dafny, P::Viper, P::FramaC, P::KeY, P::Why3],
436+
D::Indexed => vec![P::Agda, P::Coq, P::Idris2],
437+
438+
// Substructural family. Mainstream support is thin; Idris2 via
439+
// QTT is the canonical route for Linear/Affine, the rest are
440+
// research-prototype (routed through the HP dispatcher).
441+
D::Qtt => vec![P::Idris2],
442+
D::Linear => vec![P::Idris2],
443+
D::Affine => vec![P::Idris2],
444+
D::Relevant => vec![],
445+
D::Ordered => vec![],
446+
D::Uniqueness => vec![], // Clean is canonical but not in echidna.
447+
448+
// Mutability / capability.
449+
D::Immutable => vec![P::Dafny, P::Viper, P::FramaC, P::KeY],
450+
D::Capability => vec![P::Viper],
451+
D::Bunched => vec![P::Viper], // Separation-logic flavoured.
452+
453+
// Modal family.
454+
D::Modal => vec![P::Isabelle], // Isabelle/ModalHOL + other instances.
455+
D::Epistemic => vec![], // DEL / S5 tooling lives outside echidna's current lineup.
456+
D::Temporal => vec![
457+
P::NuSMV, P::TLC, P::SPIN, P::UPPAAL, P::Prism, P::TLAPS,
458+
],
459+
D::Provability => vec![], // GL logic, mostly research.
460+
461+
// Effects / coeffects.
462+
D::EffectRow => vec![P::FStar], // F* effect algebras.
463+
D::Impure => vec![P::FStar, P::Dafny], // Stateful-program verifiers.
464+
D::Coeffect => vec![], // Granule; not in echidna.
465+
D::Probabilistic => vec![P::Prism, P::DReal],
466+
467+
// Process / communication — the HP stack is specifically here
468+
// to serve these. No classical prover in echidna advertises
469+
// first-class session / choreographic / echo / dyadic typing.
470+
D::Session => vec![],
471+
D::Choreographic => vec![],
472+
D::Dyadic => vec![],
473+
D::Echo => vec![],
474+
475+
// Resource semirings — Tropical is an HP-stack specialty.
476+
D::Tropical => vec![],
477+
478+
// Homotopy foundations.
479+
D::Homotopy => vec![P::Agda, P::Lean], // HoTT libraries on both.
480+
D::Cubical => vec![P::Agda], // Cubical Agda is the canonical home.
481+
482+
// Entry-point kernels: these are the HP gateways themselves;
483+
// they don't correspond to a classical prover in the Axis-1
484+
// sense.
485+
D::TypeLl | D::Katagoria => vec![],
486+
}
487+
}
488+
347489
/// Inverse of [`prover_kind`]. Returns `None` for non-HP-ecosystem kinds.
348490
pub fn from_prover_kind(kind: ProverKind) -> Option<TypeDiscipline> {
349491
use ProverKind as P;
@@ -511,6 +653,82 @@ mod tests {
511653
}
512654
}
513655

656+
#[test]
657+
fn provers_serving_only_lists_classical_kinds() {
658+
// Axis-1-first discovery: `provers_serving` must never list
659+
// the HP-ecosystem dispatcher (which is already reachable via
660+
// `prover_kind`). Otherwise a user arriving via "which prover
661+
// serves linear types?" sees `LinearTypeChecker` in the list
662+
// and is no better off than when they started.
663+
for &d in TypeDiscipline::ALL.iter() {
664+
for kind in d.provers_serving() {
665+
assert!(
666+
!kind.is_hp_ecosystem(),
667+
"{:?} lists HP-ecosystem kind {:?} in provers_serving; should be classical only",
668+
d,
669+
kind
670+
);
671+
}
672+
}
673+
}
674+
675+
#[test]
676+
fn provers_serving_is_deduplicated() {
677+
for &d in TypeDiscipline::ALL.iter() {
678+
let list = d.provers_serving();
679+
let mut sorted: Vec<ProverKind> = list.clone();
680+
sorted.sort_by_key(|k| format!("{:?}", k));
681+
let before = sorted.len();
682+
sorted.dedup();
683+
assert_eq!(
684+
before,
685+
sorted.len(),
686+
"{:?} has duplicates in provers_serving: {:?}",
687+
d,
688+
list
689+
);
690+
}
691+
}
692+
693+
#[test]
694+
fn provers_serving_canonical_spot_checks() {
695+
// Ground-truth spot checks anchored in mainstream usage.
696+
// Breaking these should be a deliberate taxonomy decision.
697+
use TypeDiscipline as D;
698+
699+
let dep = D::Dependent.provers_serving();
700+
assert!(dep.contains(&ProverKind::Agda));
701+
assert!(dep.contains(&ProverKind::Coq));
702+
assert!(dep.contains(&ProverKind::Lean));
703+
assert!(dep.contains(&ProverKind::Idris2));
704+
705+
let linear = D::Linear.provers_serving();
706+
assert!(linear.contains(&ProverKind::Idris2)); // QTT.
707+
708+
let cubical = D::Cubical.provers_serving();
709+
assert!(cubical.contains(&ProverKind::Agda)); // Cubical Agda.
710+
711+
let temporal = D::Temporal.provers_serving();
712+
assert!(temporal.contains(&ProverKind::NuSMV));
713+
assert!(temporal.contains(&ProverKind::SPIN));
714+
assert!(temporal.contains(&ProverKind::TLC));
715+
716+
let hoare = D::Hoare.provers_serving();
717+
assert!(hoare.contains(&ProverKind::Dafny));
718+
assert!(hoare.contains(&ProverKind::Viper));
719+
assert!(hoare.contains(&ProverKind::KeY));
720+
721+
// Communication-style disciplines: HP stack is their home.
722+
assert!(D::Session.provers_serving().is_empty());
723+
assert!(D::Choreographic.provers_serving().is_empty());
724+
assert!(D::Dyadic.provers_serving().is_empty());
725+
assert!(D::Echo.provers_serving().is_empty());
726+
727+
// Entry-point kernels don't have a classical counterpart.
728+
assert!(D::TypeLl.provers_serving().is_empty());
729+
assert!(D::Katagoria.provers_serving().is_empty());
730+
}
731+
514732
#[test]
515733
fn tag_matches_hp_ecosystem_upstream() {
516734
// Sanity: TypeDiscipline::tag() must agree with the string passed

0 commit comments

Comments
 (0)