Repository navigation
docs(decisions): lexicon v2 + hub-and-probe identity arbitration ADR, with the false-friend census script #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+398
−0
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
eb5d581
docs(decisions): lexicon v2 and hub-and-probe identity arbitration AD…
hyperpolymath 0f6d48f
Merge branch 'main' into claude/zen-brahmagupta-opqqle
hyperpolymath 38c986f
fix(scripts): false_friend_scan exits 0 after its sample; ADR states …
hyperpolymath 5be5583
docs(scripts): document extract and census contracts in false_friend_…
coderabbitai[bot] 4486d2d
fix(scripts): drop empty names and statements from the census; ADR ca…
hyperpolymath 64af302
Merge remote-tracking branch 'origin/claude/zen-brahmagupta-opqqle' i…
hyperpolymath File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
313 changes: 313 additions & 0 deletions
313
docs/decisions/2026-10-19-lexicon-v2-and-identity-arbitration.adoc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| // SPDX-License-Identifier: CC-BY-SA-4.0 | ||
| // SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> | ||
| = ADR: lexicon v2 (false friends, phrases, equivalences) and hub-and-probe identity arbitration (stage 6c) | ||
| :revdate: 2026-10-19 | ||
| :status: proposed — owner decisions D1–D5 in §7 are open; nothing below is ruled | ||
|
|
||
| == Context (measured on `main` at `ed6ef60`, 2026-10-19) | ||
|
|
||
| The owner asked for two things at once: push the saturation levers again | ||
| (provers, corpora, vocabulary, synonyms) and go past lexical synonymy toward | ||
| *phrases*, *equivalent expressions*, *false friends*, and ultimately a way to | ||
| arbitrate proofs across prover families by establishing what mathematical | ||
| object each system is talking about — "feeling our way around mathematical | ||
| objects" rather than only checking the same text in many kernels. | ||
|
|
||
| What the tree already has: | ||
|
|
||
| * *Exchange bridges*: six (`src/rust/exchange/`: OpenTheory, Dedukti, | ||
| Lambdapi, TPTP, SMT-LIB, SMTCoq). All work on rendered text. None produces a | ||
| kernel-checked translation; `term_hash` is taken over the rendering, with no | ||
| α-normalisation (roadmap 6c), so α-equivalent terms hash apart. Worse, the | ||
| renderings are lossy: `term_to_opentheory` collapses every term outside | ||
| Var / Const / App / Lambda to `const "echidna_opaque"` and drops binder | ||
| types, so two different source terms can render, and hash, alike. Nothing | ||
| fuses on these hashes today because dispatch attaches no evidence; once it | ||
| does, a `KernelTranslation` may only come from an injective rendering of a | ||
| supported fragment, with unsupported terms rejected, never collapsed (§5). | ||
| * *Arbitration*: four fusion mechanisms behind `ResultArbiter`, and since | ||
| 2026-10-14 (ECH-05, #430) a fidelity level `belief` / `epi` / `sound` set by | ||
| per-attempt `FidelityEvidence` (`KernelTranslation` / | ||
| `DeclaredCorrespondence` / `None`), with the Idris2 statement in | ||
| `src/abi/EchidnaABI/Arbitration.idr`. Dispatch attaches no evidence yet, so | ||
| every dispatched verdict is `belief`. The owner's first kernel-translation | ||
| target is HOL4 ↔ HOL Light through OpenTheory (6a), then Lean 4 ↔ Rocq | ||
| through Dedukti (6b). Issue #429 asks to *consume* fidelity evidence (fibre | ||
| coverage, round trips, cross-prover agreement from one kernel plus | ||
| independent standpoints) rather than collect N re-proofs. | ||
| * *Lexicon*: `data/synonyms/` holds 14 per-prover tables (343 `[[synonym]]` | ||
| rows) and 4 cross-prover dictionaries (`_msc2020`, `_wordnet_math`, | ||
| `_conceptnet_seed`, `_disciplines`; 262 rows). `semantic_class`, the only | ||
| cross-prover join key, is present on 61 of the 343 per-prover rows, 40 of | ||
| them in `mizar.toml`; nine tables have none. The schema can say "these names | ||
| are interchangeable" and nothing else: no way to record a *false friend* | ||
| (same name, different object), a *phrase* (a multi-token idiom that is one | ||
| tactic or one concept), a *specialisation* (same theorem at a narrower | ||
| type), or an *equivalence with a witness* (and which kind of witness). | ||
| * *Corpus*: `training_data/` is uneven. Over thirty provers have empty | ||
| `premises_*` / `tactics_*` files; HOL Light alone contributes 328,825 | ||
| premise rows. `models/premise_vocab.txt` has 1,311,903 lines, which already | ||
| exceeds the roadmap's "~1 M canonical tokens" target as a raw count; the | ||
| metric that matters is `metrics/oov_rate.jl` (≤ 3 %), unmeasured since the | ||
| saturation campaign. | ||
| * *Toolchain in the session container*: Rust only. No prover, no Julia, no | ||
| Idris2. Everything measured below is lexical; no kernel was consulted. | ||
|
|
||
| === Experiment 1 — the owner's first pair, HOL4 ↔ HOL Light | ||
|
|
||
| `scripts/false_friend_scan.sh training_data hol4 hol_light` (this PR; | ||
| deterministic, jq + coreutils, under a minute; rows with an empty name or | ||
| statement are dropped, which removes three HOL4 rows): | ||
|
|
||
| [cols="3,1",options="header"] | ||
| |=== | ||
| | Quantity | Value | ||
| | HOL4 distinct (name, statement) rows / distinct names | 95,738 / 90,168 | ||
| | HOL Light distinct (name, statement) rows / distinct names | 43,937 / 42,456 | ||
| | Theorem names shared by both corpora | 5,060 | ||
| | Shared-name statement pairs | 11,490 | ||
| | … with equal statement text after whitespace normalisation (cognates) | 1,030 | ||
| | … with differing statement text | 10,460 | ||
| |=== | ||
|
|
||
| Reading the differing pairs in order shows the dominant pattern is *not* | ||
| contradiction and *not* synonymy. It is type generalisation: HOL4's | ||
| `!f:real->real …` against HOL Light's `!f:real^M->real^N …`, `abs` against | ||
| `norm`, `0` against `vec 0` (HOL Light's multivariate library is the | ||
| n-dimensional generalisation of the shared ancestor). Those are | ||
| *specialisation* pairs: the HOL4 statement is an instance of the HOL Light | ||
| one. A lexicon with only `synonym` cannot express that, and an arbiter that | ||
| accepted them as "the same theorem" would be wrong in one direction and | ||
| right in the other. Genuine false friends also exist in the same list: the | ||
| name `lemma` carries unrelated statements in each corpus. | ||
|
hyperpolymath marked this conversation as resolved.
|
||
|
|
||
| === Experiment 2 — within-prover homonymy | ||
|
|
||
| Same script, census rows (one name, more than one distinct statement; rows | ||
| with an empty name or statement dropped): | ||
|
|
||
| [cols="2,1,1,1,1",options="header"] | ||
| |=== | ||
| | Corpus | Rows | Names | Homonymous names | Rate | ||
| | hol4 | 95,738 | 90,168 | 3,755 | 4.2 % | ||
| | hol_light | 43,937 | 42,456 | 1,218 | 2.9 % | ||
| | lean3 | 92,680 | 68,823 | 13,213 | 19.2 % | ||
| | mizar | 76,522 | 18,795 | 340 | 1.8 % | ||
| | agda | 46,028 | 36,936 | 3,178 | 8.6 % | ||
| | afp | 102,683 | 100,116 | 2,075 | 2.1 % | ||
| | acl2 | 135,963 | 118,117 | 9,437 | 8.0 % | ||
| | why3 | 25,180 | 15,395 | 3,368 | 21.9 % | ||
| | dafny | 10,487 | 7,551 | 920 | 12.2 % | ||
| | fstar | 9,386 | 6,367 | 1,216 | 19.1 % | ||
| | lambda_prolog | 24,897 | 15,232 | 7,615 | 50.0 % | ||
| |=== | ||
|
|
||
| The Mizar figure is an artefact of the extractor keying on article-local | ||
| labels (`Th1`, `Th2`, …): 76,522 rows collapse onto 18,795 names, so the | ||
| corpus is nearly all homonyms *by construction*, and the low rate means the | ||
| extractor dropped most of them. Lean 3 and Why3 are genuinely namespace-heavy | ||
| (`add_comm` in dozens of structures). Both facts matter for anything that | ||
| joins corpora by name: a name is not an identity, even inside one prover. | ||
|
|
||
| === What the numbers establish | ||
|
|
||
| . Lexical alignment is unsafe as *evidence*, which ECH-05 already assumes; the | ||
| experiment quantifies how unsafe (nine in ten shared-name pairs differ). | ||
| . Lexical alignment is a high-yield *candidate generator*: 1,030 | ||
| whitespace-normalised cognates and roughly ten thousand specialisation | ||
| candidates fall out of the first pair for free, before any kernel runs. | ||
| . The relation the lexicon most needs next is not more synonyms but | ||
| `specialisation` / `generalisation`, then `false_friend`, then `phrase`. | ||
|
|
||
| == Options | ||
|
|
||
| Rated first for elegance and correctness, then for dependability and | ||
| security. Aspects noted where relevant. | ||
|
|
||
| === A. Logical-framework hub (λΠ-modulo as the lingua franca) | ||
|
|
||
| Dedukti / Lambdapi as the single exchange target; prover-specific encoders do | ||
| the work (the Dedukti ecosystem ships them: OpenTheory → Dedukti, Coq → | ||
| Dedukti, Agda → Dedukti, Isabelle → Dedukti, Lean partial). Identity of two | ||
| statements is identity of their λΠ terms after normalisation, which the | ||
| arbiter already names as `KernelTranslation { term_hash }` and already proves | ||
| cannot fuse a collision to `sound`. | ||
|
|
||
| * Elegance / correctness: highest. One hub, kernel-checked, matches roadmap | ||
| 6b and #191 exactly, and the arbiter's `sound` level is already defined | ||
| over it. | ||
| * Dependability / security: high once an encoder exists; each encoder is a | ||
| research-grade artefact and the set of provers it covers is bounded. | ||
| * Aspect: this is the *only* route to `sound`. Everything else tops out at | ||
| `epi`. | ||
|
|
||
| === B. Behavioural identity by consequence partitions ("feeling our way around") | ||
|
|
||
| Identify an object by what it does, not by its term: fix a shared probe set | ||
| (consequences, instances, counter-instances), have each prover decide the | ||
| probes for its candidate statement, and call two candidates the same object | ||
| when they induce the same partition of the probe set. This is the standpoint | ||
| in `hyperpolymath/autoformal-fidelity` (two target standpoints; agreement is | ||
| identical partitions; convergence is evidence only under independence) and it | ||
| is what #429 asks ECHIDNA to consume. It is neurosymbolic in the intended | ||
| sense: the neural layer proposes alignments from corpus embeddings and | ||
| lexical candidates (Experiment 1); the symbolic layer tests them; the result | ||
| lands in the existing `DeclaredCorrespondence { consequence_tests, failures }` | ||
| constructor and yields `epi`, never `sound`. | ||
|
|
||
| * Elegance / correctness: high; reuses the ECH-05 vocabulary unchanged. | ||
| Correctness is bounded by the probe set and by independence (same kernel, | ||
| same library, same author all reduce it). The current | ||
| `DeclaredCorrespondence { consequence_tests, failures }` has no place to | ||
| carry independence, and `fidelity_level` lifts any tested, failure-free | ||
| correspondence to `Epi`, so independence must travel inside the evidence: | ||
| `DeclaredCorrespondence` gains a standpoint record (kernel, library and | ||
| authorship of each side), `fidelity_level` lifts to `Epi` only when the | ||
| standpoints differ, and the `Arbitration.idr` constructor takes the same | ||
| argument. That is an additive arbiter and ABI change, and the one this | ||
| proposal needs. | ||
| * Dependability / security: high. Probes are ordinary prover invocations and | ||
| run inside the existing sandbox and integrity checks. | ||
| * Aspect: this is the route that handles *false friends* natively (different | ||
| partitions) and *specialisation* (one partition refines the other), neither | ||
| of which a term hash can express. | ||
|
|
||
| === C. An in-house super-language or universal IR | ||
|
|
||
| A new estate language in which every prover's statements are written. | ||
| Rated lowest: it reinvents MMT / Dedukti, needs the same per-prover encoders | ||
| as A, and adds a kernel nobody has checked. The useful kernel of the idea is | ||
| already in the tree: `core::Term` in `crates/echidna-core/src/core.rs` is a | ||
| small dependent-type IR (Var, Const, App, Lambda, Pi, Sigma, universes, Let). | ||
| The elegant move is to make `core::Term` the α-normalised *projection target* | ||
| that renders to λΠ (6c), not to invent a language around it. KYAML, deed and | ||
| System-ET are serialisation surfaces, not logics: they can carry the lexicon | ||
| and the certificates, and they cannot carry identity. | ||
|
|
||
| === D. Pairwise translators (Mathport-style) | ||
|
|
||
| Lean 3 → Lean 4, HOL Light → HOL4 via OpenTheory, and so on. Pragmatic where | ||
| a mature tool exists; quadratic in the number of provers; keeps every pair as | ||
| its own special case. Kept as *feeders* into A, never as the architecture. | ||
|
|
||
| === E. Identity as a VeriSimDB consonance subject (alternative framing, cross-repo) | ||
|
|
||
| `hyperpolymath/verisimdb` already defines exactly this problem for data: | ||
| one identity maintained across modal witnesses, with drift detection and the | ||
| verbs `MERGE` / `SPLIT` / `RETRACT`. Treat each prover as a modal witness of | ||
| one mathematical object: a cognate is consonance, a false friend is drift, | ||
| curation is `MERGE` or `SPLIT`, retraction of a backend | ||
| (`ResultArbiter::retract`) is `RETRACT`. This is not a competitor to A + B; | ||
| it is where the identity lives between runs, and it is the estate trope the | ||
| owner asked to look for. It belongs in verisimdb, not here. | ||
|
|
||
| == Recommendation | ||
|
|
||
| *Hub and probe*: A for `sound` where an encoder exists (6a OpenTheory on the | ||
| owner's HOL4 ↔ HOL Light pair first, 6b Dedukti next), B for `epi` everywhere | ||
| else, D as feeders, C rejected, E as the persistence layer, filed against | ||
| verisimdb. The hybrid is the elegant option because it maps onto the two | ||
| evidence constructors ECH-05 already proves properties about: the fusion | ||
| mechanisms are untouched, and the arbiter and ABI changes it needs are | ||
| additive (the standpoint record on `DeclaredCorrespondence`, option B, and | ||
| the injectivity requirement on `KernelTranslation` renderings, option A). | ||
| It departs from nothing the owner has ruled. | ||
|
|
||
| == Decision (proposed; lands only after D1–D5) | ||
|
|
||
| . *Lexicon v2 schema*, additive and loader-compatible (serde defaults), on | ||
| every `[[synonym]]` row: | ||
| * `relation`: one of `synonym` (default), `specialisation`, | ||
| `generalisation`, `false_friend`, `phrase`. | ||
| * `phrase`: ordered token list for a multi-token idiom (for example | ||
| Mizar's `then thus`, Coq's `apply … ; auto`, Isabelle's `by (simp add:`). | ||
| * `false_friends`: names that *look* like this entry in other tables and | ||
| are not it, with the other table named. | ||
| * `evidence`: `none` (default), `curated`, `probe { tests, failures }`, | ||
| `kernel { format, checked_by, term_hash }`. `curated` records provenance | ||
| only and lifts to `FidelityEvidence::None` until probe or kernel evidence | ||
| is attached; `probe` lifts to `DeclaredCorrespondence`, and `kernel` to | ||
| `KernelTranslation` only when every field, `checked_by` included, is | ||
| present. A lexicon row therefore never raises an attempt's fidelity | ||
| above what its own evidence carries. | ||
| * `fidelity`: `exact` or `lossy` with a `residue` note, after echo-types' | ||
| loss-with-residue formalism, so a lossy correspondence says what is lost. | ||
| * `scope`: library and version the relation holds in (the existing `since` | ||
| / `until` cover the prover version only). | ||
| . *Candidate generation from corpora*: `scripts/false_friend_scan.sh` now; a | ||
| Rust `echidna lexicon scan` later that emits v2 rows with | ||
| `evidence = none`. Candidates never set `semantic_class` on their own. | ||
| . *Consequence probes*: a `verification/consequence_probe.rs` that takes a | ||
| probe set per pair, runs it through the existing dispatcher and sandbox, | ||
| and produces `DeclaredCorrespondence` with its standpoint record; | ||
| `fidelity_level` and `Arbitration.idr` are extended in the same PR so that | ||
| correlated probes cannot reach `Epi`. First probe | ||
| pair: the SMT-LIB pair Z3 ↔ CVC5, because both binaries already install in | ||
| `live-provers.yml` Tier 1, so the probe loop is testable in CI today. | ||
| . *Hub*: 6a real OpenTheory (article reader and writer against the real | ||
| stack-machine format, compatible with the OpenTheory → Dedukti encoder), | ||
| with the CR-8 round-trip test from `standards` | ||
| `3-practice/testing-and-benchmarking/TESTING-TAXONOMY.adoc`. | ||
| . *6c*: α-normalised projection of `core::Term` to λΠ; `term_hash` over the | ||
| canonical form; the renderer rejects terms outside the supported fragment | ||
| instead of collapsing them to an opaque constant; property tests that | ||
| α-equivalent terms hash equal and that distinct terms in the fragment never | ||
| share a hash. | ||
| . *Dispatch attaches evidence* (closes "every verdict is `belief`"), then | ||
| #429's ingestion. | ||
|
|
||
| == Work breakdown (stacked PRs, each independently reviewable) | ||
|
|
||
| [cols="1,4,2",options="header"] | ||
| |=== | ||
| | PR | Content | Depends on | ||
| | 0 (this) | This ADR + `scripts/false_friend_scan.sh` | — | ||
| | 1 | Lexicon v2 schema in `src/rust/suggest/synonyms.rs` + `data/synonyms/README.adoc` + tests; existing tables load unchanged | D2 | ||
| | 2 | Fill `semantic_class` on the 282 per-prover rows that lack it, curated, with `evidence = curated`; relations added from the scan where the owner's reading confirms them | 1, D4 | ||
| | 3 | `echidna lexicon scan`: Rust port of the census, emits v2 candidate rows across all corpus pairs; cognate and specialisation candidates for HOL4 ↔ HOL Light as fixtures | 1 | ||
| | 4 | `consequence_probe.rs` + `DeclaredCorrespondence` producer with the standpoint record + `fidelity_level` and `Arbitration.idr` extension + CI job on Z3 ↔ CVC5 | D3 | ||
| | 5 | 6a OpenTheory real + CR-8 round-trip test (#190) | — | ||
| | 6 | 6c α-normalised λΠ projection; unsupported terms rejected; canonical `term_hash`; injectivity property test | 5 | ||
| | 7 | Dispatch attaches fidelity; #429 ingestion | 4, 6 | ||
| |=== | ||
|
|
||
| Saturation levers that need no decision and can run alongside: synonym | ||
| tables for the Tier-1 provers that still have none (Z3, CVC5, PVS), filling | ||
| the empty `premises_*` files for Tier-2 provers whose extractor exists, and | ||
| re-running `metrics/run_all.jl` to re-baseline `oov_rate` and | ||
| `alignment_rate` (needs Julia; not possible in the session container). | ||
|
|
||
| == Beyond this repository | ||
|
|
||
| * `hyperpolymath/verisimdb`: map cross-prover object identity onto a | ||
| consonance subject (option E); one issue to open. | ||
| * `hyperpolymath/standards`: CR-8 names the round-trip pairs; the FRG grades' | ||
| "cross-prover replication ≥ 2 / ≥ 3" (V2 / V3) should require `epi` or | ||
| better, not lexical agreement, once this lands. | ||
| * `hyperpolymath/autoformal-fidelity` and `hyperpolymath/nextgen-typing` | ||
| (`docs/TYPE-CONNECTIONS.adoc`): the partition-agreement statement B | ||
| implements; cite, do not re-derive. | ||
| * `hyperpolymath/echo-types`: the residue of a lossy correspondence | ||
| (`fidelity = lossy`) is an echo-types L3 object; the lexicon should cite the | ||
| loss taxonomy rather than invent names for kinds of loss. | ||
| * `hyperpolymath/echidnabot`: consumes `echidna.prove.result/1`; the | ||
| verdict's `level` should surface in its trust bridge once dispatch attaches | ||
| evidence. | ||
|
|
||
| == Open owner decisions (the selector questions this session could not ask) | ||
|
|
||
| [cols="1,5,3",options="header"] | ||
| |=== | ||
| | # | Question | Recommended | ||
| | D1 | Architecture for identity arbitration: hub and probe (A + B), super-language (C), or pairwise only (D)? | A + B | ||
| | D2 | Lexicon v2 format: extend the TOML tables in place (loader-compatible) or move `data/synonyms/` to Nickel or deed? | TOML in place | ||
| | D3 | First consequence-probe pair: Z3 ↔ CVC5 (runs in CI today) alongside the HOL4 ↔ HOL Light hub pilot, or wait for HOL binaries in CI? | Z3 ↔ CVC5 now | ||
| | D4 | May corpus-mined candidates set `semantic_class` automatically? | No: `evidence = none`, flag-only until curated or probe-tested | ||
| | D5 | Open the verisimdb consonance-subject issue (option E) now? | Yes | ||
| |=== | ||
|
|
||
| == Non-claims | ||
|
|
||
| No prover ran during the measurement. Every number above is lexical. Nothing | ||
| in this ADR raises any verdict above `belief`; the ECH-05 regression and | ||
| fidelity tests are untouched. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.