diff --git a/core/cli_test.py b/core/cli_test.py index e210ef8b..192aa1d7 100644 --- a/core/cli_test.py +++ b/core/cli_test.py @@ -173,6 +173,16 @@ TEST_SUITES: dict[str, tuple[str, ...]] = { # observed red. See docs/analysis/logos-substrate-collapse-2026-07-28.md. # Bit-exact comparison, no tolerances, ~2.1s. "tests/test_manifold_collapse_floor.py", + # FA-1 verdict / G-25 — the CORE-Logos validation-gate claim, measured and refused. + # On a ground with ZERO collisions and a genuinely closed loop, cross-language + # holonomy separates aligned from meaning-breaking clauses at AUC 0.557 (chance is + # 0.500) and the ADR's own word-order test holds 64.4% of the time: NO-GO against a + # criterion registered before the run. Pinned because the interesting failure mode + # is the GOOD one — a future encoding making the gate real while four documents still + # say it was refused. Two sabotages observed red (thresholds lowered to chance -> + # verdict flips; geodesic reverted to the shipped overwrite -> G4 fails). Cost 16s, + # stated rather than estimated, for the tripwire on the system's central design claim. + "tests/test_fa1_gate_verdict.py", # PR-11 / G-5 / R-9 — the soak's evidence is committed, pinned, and CURRENT. # Two failure modes, deliberately separated: the digest catches a regression, # and the attested-source hashes catch STALENESS — evidence that no longer diff --git a/docs/adr/ADR-0005-language-pack-contract.md b/docs/adr/ADR-0005-language-pack-contract.md index 78215497..e13ce154 100644 --- a/docs/adr/ADR-0005-language-pack-contract.md +++ b/docs/adr/ADR-0005-language-pack-contract.md @@ -91,6 +91,24 @@ only when it passes the following gates in order: 7. **Alignment gate** — pack publishes anchor records verifiable against trilingual template 8. **Coverage gate** — probe set covers minimum lexical and morphological surface +> **Amendment · 2026-07-28 — what gate 7 actually verified, measured.** The alignment gate +> presupposes that publishing anchor records establishes cross-language alignment. Measured at +> `472fc0a8`, the mechanism behind those records **destroyed** what it claimed to align: +> `packs.compiler._blend_feature_versors` ignored its strength argument and returned the target +> verbatim, so an "aligned" Hebrew token was *overwritten* by its Greek or English partner +> rather than drawn toward it, and mounting the depth packs removed 37 distinct coordinates +> (`docs/analysis/logos-substrate-collapse-2026-07-28.md`). The eight gates are also not +> enforced as a sequence anywhere in the loader — `gate_engaged` is a single manifest boolean +> consulted by `sensorium/registry.py`, and the seven other gates have no code that runs them. +> +> Two things follow, and only two. **(a)** Alignment must be *interpolation on the versor +> group*, never replacement — `algebra/rotor.py` has shipped that operation since the algebra +> layer's first commit. **(b)** Gate 7's stronger sibling — that alignment establishes +> resonance strong enough to *validate meaning* (ADR-0015's Crown Proof) — was measured and +> refused: `docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md`. The alignment data itself +> stands: 24 aligned concepts across three languages, and Hebrew root folding into geometry is +> unaffected. What is retired is the claim that alignment, by existing, proves anything. + ## Design Rules ### 1. Lemma-first diff --git a/docs/adr/ADR-0015-language-packs-and-holonomy-resonance.md b/docs/adr/ADR-0015-language-packs-and-holonomy-resonance.md index 2a6587c3..15f29aa2 100644 --- a/docs/adr/ADR-0015-language-packs-and-holonomy-resonance.md +++ b/docs/adr/ADR-0015-language-packs-and-holonomy-resonance.md @@ -1,10 +1,44 @@ # ADR-0015 — Language Packs as Compiled Linguistic Manifolds -**Status:** Accepted +**Status:** Accepted — **Crown Proof section AMENDED 2026-07-28: measured and not supported** **Date:** 2026-05-13 --- +> ## Amendment · 2026-07-28 · the Crown Proof was tested and failed +> +> This ADR's "Crown Proof: Holonomy Resonance" section makes cross-language holonomy +> resonance **the validation gate of meaning** — *"This is the CORE-Logos proof"* — and the +> Consequences section lists *"Establishes holonomy-level resonance as the validation gate"* +> as a positive. **That claim is retired.** It was measured against a criterion registered +> before the run (`docs/analysis/fa1-holonomy-gate-preregistration.md`) and refused: +> +> * **AUC 0.557** separating aligned clauses from meaning-breaking ones (chance = 0.500, +> required ≥ 0.80), over 1,016 aligned pairs and 58,375 mechanically-generated negatives; +> * **64.4%** word-order sensitivity against this ADR's own stated test (*"word-order changes +> should change holonomy"*, required ≥ 0.90) — a third of permutations close the loop +> *tighter* than the correct order; +> * measured on a ground with **zero coordinate collisions** and with a **genuinely closed +> loop**, both of which had to be built first — the shipped compiler collapsed 37–53 +> coordinates and the shipped `holonomy_encode` never closed +> (`docs/analysis/logos-substrate-collapse-2026-07-28.md`). +> +> **Diagnosis:** the encoding reacts more strongly to reordering a clause (median deviation +> 41.2) than to changing what the clause is *about* (32.2). It measures path shape, not +> content. The negative classes do order themselves correctly by meaning-distance, so the +> geometry is not noise — it is a weak correlation where the design asked for a gate. +> +> **What survives, unamended:** the three-language architecture with distinct roles, the pack +> contract, the compiled-manifold discipline, morphology as structure, and Hebrew root folding +> into geometry at compile time. What is struck is only the claim that holonomy closure +> *validates* meaning. +> +> **Verdict:** `docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md` · +> **Tripwire:** `tests/test_fa1_gate_verdict.py` — if a future encoding makes the gate real, +> that test fails, and this amendment is withdrawn in the same commit that replaces it with a +> proof. The hypothesis was honestly stated and honestly testable; that is why it could be +> settled at all. + ## Context CORE's language philosophy is not localization. English, Hebrew, and Koine Greek diff --git a/docs/analysis/fa1-holonomy-gate-preregistration.md b/docs/analysis/fa1-holonomy-gate-preregistration.md index 7ad8b11d..9a8d963d 100644 --- a/docs/analysis/fa1-holonomy-gate-preregistration.md +++ b/docs/analysis/fa1-holonomy-gate-preregistration.md @@ -108,6 +108,23 @@ repaired design earns its place there. clause of triple *j ≠ i*. This is the strongest negative class: same corpus, same lengths, same construction, different meaning. +**Amendment A1 (recorded before the run, no numbers seen).** R3 resolves more of the corpus +than this registration assumed: **24 aligned concepts**, of which 6 carry all three languages +and 18 carry two. Clause sets are therefore all `C(24,3) = 2,024` combinations rather than the +"20–40 triples" estimated above — a larger corpus than registered, in the direction that makes +the test harder to pass, not easier. + +That makes negative class 3 complete-enumeration quadratic (≈4 M pairs). It is bounded by a +rule stated here, before any measurement: **for each aligned pair, the cross negatives are the +first 5 clause sets following it in the deterministic sorted enumeration that share no concept +with it.** Position in a sorted enumeration of concept keys is unrelated to meaning, so the +bound is unbiased; it is deterministic, so the experiment is reproducible; and the count of +generated negatives is reported, so nothing is dropped in silence. Classes 1 and 2 remain +**complete** — every valid substitution, every non-identity permutation. + +The G2 threshold is **not** relaxed to compensate. A bound on how many negatives are drawn +does not change how well the aligned pairs must separate from them. + ## 4. The criterion — committed, and deliberately unforgiving Let `d(A,B)` be the loop-closure deviation of the cross-language loop (transport along clause diff --git a/docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md b/docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md new file mode 100644 index 00000000..5591b757 --- /dev/null +++ b/docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md @@ -0,0 +1,126 @@ +# FA-1 verdict: **NO-GO** — cross-language holonomy does not discriminate meaning + +**Date:** 2026-07-28 · **Criterion registered before the run** at `94f05ba8` +(`docs/analysis/fa1-holonomy-gate-preregistration.md`, amendment A1 recorded before any +number existed) · Runner: `evals/logos/fa1_gate.py` · Deterministic, re-run bit-identical. + +> **G4 held.** The repairs work: the ground is clean and the loop closes. The design's +> claim is what failed. Per the registration, this is a full-credit result — *"the outcome +> that would retire the largest piece of unearned design in the system, and it is worth as +> much as a GO."* + +--- + +## 1. The result + +| Gate | Requirement | Observed | | +|---|---|---|---| +| **G1** separation, all negatives | AUC ≥ 0.80 | **0.557** | ✗ | +| **G2** hardest class (cross-pair) | AUC ≥ 0.75 | **0.664** | ✗ | +| **G3** word-order sensitivity | ≥ 0.90 | **0.644** | ✗ | +| **G4** no collapse re-entry | `coordinates_lost == 0` | **0** | ✓ | + +**VERDICT: NO-GO.** AUC 0.557 against a chance floor of 0.500. + +**Controls, reported per anti-gaming rule 4.** Max self-loop deviation `d(A,A) = 3.6e-06` +across all 1,016 aligned pairs — the instrument can report closure, so a null result is a +measurement and not a broken metric. Corpus: **83 authored edges, 59 resolved under R3, 24 +concepts** (6 tri-language, 18 bi-language), **2,024 clause sets**, **1,016 aligned pairs**, +**58,375 negatives** (50,748 lexical · 5,080 word-order · 2,547 cross-pair). + +## 2. The signal is real, in the right direction, and far too weak + +The negative classes order themselves exactly as meaning-distance would predict: + +| Class | median `d` | AUC vs aligned | +|---|---|---| +| **aligned** (same content, two languages) | **27.20** | — | +| lexical substitution (one word changed) | 32.17 | 0.547 | +| word-order permutation (same words, reordered) | 41.17 | 0.602 | +| cross-pair (entirely different content) | 53.23 | 0.664 | + +Aligned pairs *do* close tighter than negatives, and the further a negative is from the +original meaning the wider its loop opens. The geometry is not noise. But the distributions +overlap so heavily that a classifier built on `d` is barely better than a coin — and the +design does not ask for a correlation, it asks for **a validation gate**. A gate at AUC 0.557 +would refuse correct articulations and admit wrong ones at nearly the same rate. + +## 3. Why it fails — the diagnosis, not just the number + +**The encoding is more sensitive to permutation than to substitution.** Reordering the same +three words moves `d` from 27.2 to 41.2. Replacing one of the three words with a *different +concept* moves it only to 32.2. A quantity that reacts more strongly to shuffling a clause +than to changing what the clause is about is measuring **path shape, not content**. + +That follows from the algebra rather than from any defect: `F(X)` is an ordered geometric +product, so permutation acts on it through non-commutativity — a first-order effect — while +substituting one of three factors perturbs it only through that factor's own difference. +Word order is structurally louder than word identity, and meaning lives mostly in identity. + +And even the order sensitivity is unreliable per-pair: **G3 = 0.644**, so a third of all +permutations make the loop close *tighter* than the correctly-ordered clause. ADR-0015's own +stated test — *"word-order changes should change holonomy"* — is satisfied only in the trivial +sense that the number moves. It does not move in a consistent direction. + +## 4. What this does and does not settle + +**Settled.** Cross-language holonomy closure, as ADR-0005/0015 specify it, is **not** a +validation gate of meaning — measured on a ground with zero coordinate collisions, with a +genuinely closed loop, with mechanically-generated negatives in three classes and no +cherry-picking, at a corpus 1,000× the size of the June measurement. The two conditions that +made the June negative uninterpretable were removed and the answer did not change; it became +*more* precise. + +**Not settled, and deliberately not tested here.** The cross-language alignment strength is +`0.10` — the value the compiler declares — so aligned tokens across languages sit only 10% of +the way toward each other. A stronger coupling would raise separation, and at strength `1.0` +it would reach it trivially, by re-collapsing the manifold into exactly the state FA-1 found +and G4 forbids. Somewhere between those lies a question worth asking, and **this experiment +does not ask it**, because selecting a coupling *after* seeing the registered value fail is +the definition of the tuning this registration's anti-gaming rules forbid. + +If it is ever asked, it must be its own pre-registration, and it must carry a criterion that +G4 cannot be traded against: **separation must improve faster than distinctness degrades**, +with both measured, or the "improvement" is just collapse arriving by a slower road. + +**Also not settled:** the token-level question. Whether *individual* aligned versors sit +closer than unaligned ones on the repaired ground is a narrower claim than the clause gate, +and the four tests that nominally proved it are decoration +(`docs/analysis/logos-substrate-collapse-2026-07-28.md` §3). It is worth measuring honestly. +It is not this result and cannot be used to soften it. + +## 5. Consequences — what changes + +1. **The validation-gate claim is retired.** ADR-0005 and ADR-0015 are amended: cross-language + holonomy resonance is recorded as **measured and not supported**, with this verdict as the + authority. The phrase *"This is the CORE-Logos proof"* does not survive the measurement it + invited. The ADRs are amended, not deleted — the design was a real hypothesis, honestly + stated, and it was tested. +2. **The depth packs keep a smaller, earned role**: lexical and morphological resources with + real cross-language concept alignment (24 concepts, 59 resolved edges), and Hebrew root + folding into geometry at compile time (`triliteral:`), which is untouched by this verdict. +3. **The keel's L3 takes the NO-GO branch.** `logos/` is admitted as lexicon + morphology. + K4's meaning criterion must come from somewhere other than cross-language closure — which + is now a known constraint on the perception design rather than an assumption inside it. +4. **The repairs are worth carrying anyway, and independently of this verdict.** R1 (geodesic + blending) removes 53 coordinate collisions from the six-pack mount and 37 from the + trilingual one; R3 connects 39 of the 63 dead alignment edges. A ground that keeps its + distinctions is worth having whether or not holonomy is its gate — the collapse harmed + English's own vocabulary, and no theory of meaning was required to see that. + +## 6. What this cost, and what it bought + +One session. It closed the largest open design question in the system with a measurement +instead of a belief, and it did so by first finding that the question had never been askable: +the ground was collapsed and the loop was open, so the June negative measured neither the +design nor the geometry. + +The register of geometric-substrate verdicts now reads: wedge **C3-decoration**, operator +ablation **identical-to-baseline**, ADR-0252 §5 **NO-GO**, cross-language holonomy **NO-GO on +a clean ground**. Four independent negatives is not a run of bad luck; it is a boundary. The +honest reading is that this substrate's value has not been demonstrated in *reasoning* — while +its value in *representation* (exact recall, versor conditioning, determinism) is measured and +holds. The keel should be built on what is measured. + +**Reproduce:** `uv run python -m evals.logos.fa1_gate` · pinned by +`tests/test_fa1_gate_verdict.py` · raw output in this document's §1–2 tables. diff --git a/docs/assessment/30-gap-register.md b/docs/assessment/30-gap-register.md index 67dd6e1b..80fd89c6 100644 --- a/docs/assessment/30-gap-register.md +++ b/docs/assessment/30-gap-register.md @@ -203,7 +203,9 @@ Own `use_salience`, the two underived constants, the self-narrowing budget feedb - **G-20 · The `refusal_reason` materialisation** — typed refusal evidence exists and is discarded at the public `str` boundary; the plumbing for materialisation already landed. Cross-listed as H-3. **Authority:** small ADR (anticipated by the ADR-0024 chain). - **G-25 · CORE-Logos — the designed foundation pillar is seeded, not built** *(new, 2026-07-28 — Tier A; FA-1 of the Foundations Audit)* — ADR-0005/0015 make cross-language holonomy resonance **the validation gate of meaning** (*"holonomy(hebrew) ≈ holonomy(greek) ≈ holonomy(english)… This is the CORE-Logos proof"*), with HE roots as depth anchors and alignment-as-resonance. Measured at `339bfd37`: the alignment corpus is **11 edges / 9 HE morphology rows** (the ADR's own John1:1/Gen1:1 *first cases*, never grown); the serving-path role is an HE-morphology **veto that no-ops on English** (`pipeline.py:544`); the recall bridge is `allow_cross_language_recall` — **mystery ON-flag #1 from the flag register, now identified as the pillar's own switch**, gating `recall_top_k=3 vs 0` with no recorded rationale; the `hebrew_greek_textual_reasoning` domain is registered and produced **zero** curriculum bands; and **no path consults cross-language holonomy as a gate** — template match became the de-facto criterion of meaning instead. Root folding into vectors (`triliteral:` tokens) is real, so the first read was **architected, seeded exactly as designed, wired at three mutually-unaware points, load-bearing nowhere**. - **UPGRADED 2026-07-28 by measurement at `472fc0a8` — the pillar is not under-built, it is destroyed at compile time.** Two claims in the paragraph above are struck: the corpus is **83 edges, not 11** (63 of them resolve to nothing, including all edges of the two packs serving actually grounds HE/GRC against), and the "live holonomy machinery" is not in `core/physics/digest.py` (a field digest cycle that passes the value through) — it is `algebra/holonomy.py`, whose reverse walk was **deleted at `fca6216e` with the docstring describing it left in place**, so the object never closes and `alpha` is inert while a caller passes it. The dominant defect: **`packs.compiler._blend_feature_versors` ignores its strength argument and returns the target verbatim**, so all three "nudges" are total overwrites — mounting the depth packs **removes 37 distinct coordinates** (239 surfaces → 202), collapsing nine English question words (`ask/question/what/who/where/how/why/when/which`) onto one point and making `דבר`, `λόγος`, `דברים` and `word` bit-identical. English alone is collision-free, so the loss is *created by the mount*. Four token-level "resonance proofs" are green because the compared arrays are the same array. **The cure was already one layer down and never called:** `algebra/rotor.py` has shipped `rotor_power` + `word_transition_rotor` — the group geodesic — since the algebra layer's first commit; a naive lerp leaves the versor group and `VocabManifold.update()` correctly refuses it, which is what the overwrite was an escape from. **Instrumented:** `evals/logos/manifold_collapse.py` + `tests/test_manifold_collapse_floor.py` (smoke, both directions, bit-exact). **Evidence:** `docs/analysis/logos-substrate-collapse-2026-07-28.md`. This is the missing half of every "geometry adds nothing" verdict: no operator can distinguish what its ground does not distinguish. **Authority:** repair is admitted to the keel (K1/K3), not patched here; the pre-registered gate experiment is `docs/analysis/fa1-holonomy-gate-preregistration.md`. + **UPGRADED 2026-07-28 by measurement at `472fc0a8` — the pillar is not under-built, it is destroyed at compile time.** Two claims in the paragraph above are struck: the corpus is **83 edges, not 11** (63 of them resolve to nothing, including all edges of the two packs serving actually grounds HE/GRC against), and the "live holonomy machinery" is not in `core/physics/digest.py` (a field digest cycle that passes the value through) — it is `algebra/holonomy.py`, whose reverse walk was **deleted at `fca6216e` with the docstring describing it left in place**, so the object never closes and `alpha` is inert while a caller passes it. The dominant defect: **`packs.compiler._blend_feature_versors` ignores its strength argument and returns the target verbatim**, so all three "nudges" are total overwrites — mounting the depth packs **removes 37 distinct coordinates** (239 surfaces → 202), collapsing nine English question words (`ask/question/what/who/where/how/why/when/which`) onto one point and making `דבר`, `λόγος`, `דברים` and `word` bit-identical. English alone is collision-free, so the loss is *created by the mount*. Four token-level "resonance proofs" are green because the compared arrays are the same array. **The cure was already one layer down and never called:** `algebra/rotor.py` has shipped `rotor_power` + `word_transition_rotor` — the group geodesic — since the algebra layer's first commit; a naive lerp leaves the versor group and `VocabManifold.update()` correctly refuses it, which is what the overwrite was an escape from. **Instrumented:** `evals/logos/manifold_collapse.py` + `tests/test_manifold_collapse_floor.py` (smoke, both directions, bit-exact). **Evidence:** `docs/analysis/logos-substrate-collapse-2026-07-28.md`. This is the missing half of every "geometry adds nothing" verdict: no operator can distinguish what its ground does not distinguish. **Authority:** repair is admitted to the keel (K1/K3), not patched here. + + **RULED 2026-07-28 — the gate experiment ran and the design claim is retired.** On a ground with **zero** coordinate collisions (the geodesic repair works) and a genuinely closed loop, cross-language holonomy separates aligned clauses from meaning-breaking ones at **AUC 0.557** against a chance floor of 0.500 (bar: 0.80), and ADR-0015's own word-order test holds **64.4%** of the time (bar: 0.90). **NO-GO**, criterion registered before the run, 1,016 aligned pairs and 58,375 mechanically-generated negatives across three classes. Diagnosis: the encoding reacts more strongly to *reordering* a clause (median 41.2) than to *changing what it is about* (32.2) — it measures path shape, not content — though the negative classes do order correctly by meaning-distance, so the geometry is weak signal, not noise. **ADR-0005 and ADR-0015 amended** to record the refusal; `tests/test_fa1_gate_verdict.py` is the tripwire that fires if a future encoding makes it real. `allow_cross_language_recall` ruled by measurement (it gates vault-recall depth, has no cross-language path, reaches only telemetry) — and G-25's own claim that it was "the pillar's own switch" is **corrected**: that read the name, not the call graph. **Sixth defect found in passing:** `en_collapse_anchors_v1` cannot be loaded at all (`role: "collapse_anchor"` is not a `LanguageRole` member) yet is registered, consumed via a raw path that bypasses the loader, and is the target of all 24 still-unresolved alignment edges. **Verdict:** `docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md`. **G-25 status: L2 defective-but-repairable; the validation-gate claim retired.** - **G-24 · The perception layer inverts its own ratified design, and no served byte is geometric** *(new, 2026-07-28 — Tier A; the arc-closing diagnosis)* — Measured at `0e1be8be`: the entire serving path (`meaning_graph/reader.py` 490 lines, `proof_chain/` 3,916, both surface composers) contains **zero** geometric references — comprehension is a template parser feeding an ROBDD engine. **ADR-0252 §2 stage 1 prescribes** *"containment / transfer / accumulation / comparison … **Not surface-slot regexes**"* — the implementation is surface-slot regexes over categorical/member/ordering/propositional. The corpus confirms the inversion independently: on `holdout_dev/v1`, **read rate 1.28%** (93.16% `no_template_match`), with possession (18.4% of sentences), transfer verbs (14.6%) and quantity comparison (10.0%) unrepresented while `if/then` (0.1% of sentences) holds 21% of the inventory. Geometry participates in exactly two cognitive mechanisms — salience→attention (bypassed by every licensed lane) and `relation_compiler`'s Hamiltonian ground-state solver (off-serving, fed by a 1.0% math reader) — both stranded behind enumerative front ends that cannot generalize by construction, which is why capability lifts have arrived only as overfit slivers. **Not packs** (0.67%), **not wiring** (failures precede every flag). Full findings F-A…F-E and the remediation plan: `docs/plans/2026-07-28-perception-arc.md`. **AMENDED same day:** F-E's "never tested" was wrong — the operator branch has **three negative results** (field wedge 2026-06-04 → C3 decoration; relational operator ablation 2026-07-19 → identical to baseline; §5 → NO-GO), all found only by import-graph census (373 of 666 modules unreachable from serving). Convergent mechanism: geometry isomorphic to the arithmetic reproduces it exactly and adds nothing; the binding constraint in all three was the reader. **Authority:** the Foundations Audit (`docs/plans/2026-07-28-foundations-audit.md`), which now precedes; reopening criterion recorded in its §4. diff --git a/docs/plans/2026-07-28-foundations-audit.md b/docs/plans/2026-07-28-foundations-audit.md index 98ec2a40..dab67eb0 100644 --- a/docs/plans/2026-07-28-foundations-audit.md +++ b/docs/plans/2026-07-28-foundations-audit.md @@ -30,7 +30,7 @@ The suspicion that CORE-Logos is not integrated as designed was tested against t | Alignment data | ~~**Seed-scale: 11 edges**~~ → **83 edges authored, 20 resolve** | Four packs carry `alignment.jsonl`. The two the serving path actually grounds HE/GRC against (`he_core_cognition_v1` 22, `grc_logos_cognition_v1` 41) contribute **zero** — a hardcoded three-entry prefix map drops every one of their edges. The corpus was grown 7.5×; the growth was never connected. | | EN lexicon tagging | Present | 91 entries carrying `logos.core` semantic domains | | Serving-path role | **Veto only, no-ops on English** | `pipeline.py:544-583` — `evaluate_logos_on_text` can force a refusal against observed HE plural morphology; *"English-only turns with no HE surface → no-op"* | -| Recall bridge | **ON, undocumented** | `allow_cross_language_recall` — one of the two default-ON flags with no recorded rationale (flag register §1) — gates `recall_top_k=3 vs 0`. **The pillar's own switch was mystery flag #1** | +| Recall bridge | ~~**ON, undocumented — the pillar's own switch**~~ → **misnamed; not the pillar's switch at all** | `allow_cross_language_recall` gates `recall_top_k=3 vs 0` into `vault.recall(state.F, top_k)` — a nearest-versor search over stored field states with **no language argument, no pack, no cross-language path**. Its output feeds the generation walk, which is telemetry-only (`runtime_contracts.md:44`). Ruled 2026-07-28: stays ON, reclassified DEPLOYMENT. **This row's original claim read the flag's name instead of its call graph — the exact error this audit exists to find, committed by the audit.** | | Curriculum | Registered, bandless | `hebrew_greek_textual_reasoning` in `DOMAIN_PACKS` with corpora; produced **zero** curriculum bands at the PR-14 measurement | | Validation-gate role | **Never assumed** | ADR-0015 §"Establishes holonomy-level resonance as the validation gate" — no serving or licensing path consults cross-language holonomy; template match became the de-facto gate instead | @@ -70,6 +70,34 @@ Bottom-up. A layer's audit is complete only when each component in it has all fo **Instrument** (from the census at `339bfd37`): 666 modules, **293 reachable from serving, 373 dark**. Every dark module gets exactly one of: *seam scheduled* · *deliberately-dark with recorded reason* · *deletion candidate*. The census script becomes a pinned lane so this number is a ratchet, not a snapshot. +### FA-1 · CLOSED 2026-07-28 — all four work-order items discharged + +| Item | Outcome | +|---|---| +| 1 · map the designed logos contract | Done as the defect map: `docs/analysis/logos-substrate-collapse-2026-07-28.md` — six defects, each with its site, its measurement, and its cure | +| 2 · decide the holonomy-gate question | **NO-GO**, pre-registered, G4 held: `docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md`. AUC 0.557 (chance 0.500, bar 0.80); word-order sensitivity 64.4% against the ADR's own test. ADR-0005 and ADR-0015 amended; tripwire pinned | +| 3 · rule `allow_cross_language_recall` | **Ruled by measurement:** it gates vault-recall depth, has no cross-language path, and reaches only telemetry. Stays ON, reclassified DEPLOYMENT, with the flip condition recorded (`docs/specs/flag_register.md` §1) | +| 4 · grow-or-declare the alignment corpus | **Answered before it started:** 83 edges authored, 63 resolving to nothing. Not a growth problem — a connection problem | + +**L2 verdict: DEFECTIVE (repairable), and its central design claim RETIRED.** The repairs are +real and measured — the geodesic blend removes every coordinate collision (37 on the +trilingual mount, 53 on the six-pack logos mount), and mount-wide edge resolution connects 39 +of the 63 dead edges. What does not survive is the claim that the repaired ground *validates* +meaning by holonomy closure. + +**A sixth defect, found while building the experiment:** `en_collapse_anchors_v1` declares +`role: "collapse_anchor"`, which is not a member of `packs.schema.LanguageRole`, so +`load_pack()` raises before reading it. It is listed in `chat/pack_resolver.py`, consumed by +`chat/pack_grounding.py` **by opening `lexicon.jsonl` on a raw path** — bypassing the loader +that would reject it — and is the target of all 24 still-unresolved alignment edges. The enum +carries a comment recording this exact failure once before (ADR-0097, `domain_seed`): a +registry widened by hand falls behind by hand. + +**What this changes downstream.** L3's audit no longer waits on an open question — it inherits +a ruled one. A reader over this ground cannot get its meaning criterion from cross-language +closure, which converts the 1.28% read rate from *"a reader problem we might solve with better +grounding"* into *"a reader problem, full stop."* That is a smaller and more honest question. + ## 3. Sequencing **FA-1 (logos, L2)** → **FA-0 (L0 closure — cheap, likely already masterful, and everything cites it)** → **FA-2 (L1 physics role-audit)** → **FA-3 (L3 perception, absorbing the Perception Arc phases, now on a verified ground)** → **FA-4 (L4)** → **FA-5 (L5)** → close with the full articulable map: every component, four articulations, evidence-linked. diff --git a/docs/specs/flag_register.md b/docs/specs/flag_register.md index cbea7d5a..fdf10fd1 100644 --- a/docs/specs/flag_register.md +++ b/docs/specs/flag_register.md @@ -34,14 +34,22 @@ These four are live in every default `RuntimeConfig`. They are the only flags wh | flag | class | governing ADR | recorded rationale | what would turn it OFF | |---|---|---|---|---| -| `allow_cross_language_recall` | CAPABILITY | **none** | **none — no comment block in `core/config.py`** | *no criterion recorded* | +| `allow_cross_language_recall` | ~~CAPABILITY~~ → **DEPLOYMENT (telemetry depth)** | **FA-1 verdict, 2026-07-28** | see the ruling below | flips if walk output is ever routed into the user-facing surface | | `use_salience` | CAPABILITY | **none** | **none — no comment block in `core/config.py`** | *no criterion recorded* | | `discourse_planner` | CAPABILITY | **none** | yes — builds a deterministic `DiscoursePlan`; BRIEF mode is byte-identical to single-clause output | evidence that multi-clause rendering degrades a served lane | | `deduction_serving_enabled` | POSTURE | **ADR-0256** | yes — ROBDD entailment intercepts argument-shaped turns; 716/716 `wrong=0` | a `wrong>0` result on the sealed band, which revokes the license by construction | **Finding (measured 2026-07-28).** Of the four flags that are ON in production, **one** has a governing ADR and **two have no recorded reason of any kind** — no ADR, no comment, no criterion. In an architecture whose entire posture is *earned* licenses and *refuse-don't-guess*, two permanently-on capability flags with no recorded decision is the mirror image of the hesitancy problem this register was built to find: **accumulated permissiveness**. -This is registered, not fixed. Writing a rationale for `allow_cross_language_recall` after the fact would be inventing a decision that was never made, which is worse than recording that it is missing. **Owed:** a one-line rationale-or-flip for each, from whoever knows why they are on. Until then their "what flips it" column stays honest. +This was registered, not fixed. Writing a rationale after the fact would be inventing a decision that was never made, which is worse than recording that it is missing. **Owed:** a one-line rationale-or-flip for each, from whoever knows why they are on. `use_salience` remains owed; the other has since been **ruled by measurement** rather than by recollection: + +> ### Ruling · `allow_cross_language_recall` · 2026-07-28 (FA-1 work-order item 3) +> +> **It does not do what its name says, and its blast radius is telemetry.** The flag has exactly one consumer: `chat/runtime.py:2844`, which passes `recall_top_k = 3 if allow_cross_language_recall else 0` into `generate()`. That parameter reaches `generate.stream._recall_state`, which calls `vault.recall(state.F, top_k)` — a **nearest-versor search over stored field states by CGA inner product**. It has no language argument, no pack, and no cross-language path of any kind. The flag sets vault-recall *depth*, and the word "cross_language" in its name describes nothing in the code it controls. +> +> Its effect is also bounded away from the user. `_recall_state`'s own INV-24 note records that recalled versors become rotor transitions on the **generation walk**, which feeds `walk_surface` — *"retained telemetry/evidence"* per `docs/specs/runtime_contracts.md:44` — while the user-facing surface comes from `realize(proposition, vocab)`. So the flag adjusts how much recalled context colours an evidence trace, not what anyone is told. +> +> **Ruled: stays ON, reclassified DEPLOYMENT, renamed in the keel.** Turning it off would reduce evidence richness to buy nothing, and it is not the CORE-Logos switch. **This also corrects the Foundations Audit's own characterisation of it as "the pillar's own switch"** — that read the name, not the call graph, which is the error the audit exists to find. **What flips it:** the condition `_recall_state` states itself — if walk output is ever routed into the user-facing surface, this becomes a CAPABILITY flag, must pass `min_status=COHERENT`, and needs a fresh ruling. **A second-order gap, closed with this register.** `tests/test_adr_status_governance.py::test_default_on_flag_is_not_governed_by_a_proposed_adr` is parameterized over *(default-on flag × cited ADR)*. Three of the four ON flags cite no ADR, so they generate **zero** cases — the pin covers exactly one flag. Its non-vacuity guard asserts that *some* flag cites an ADR, which stays green even if the parametrization drops to zero. That guard is tightened alongside this register. diff --git a/evals/logos/fa1_gate.py b/evals/logos/fa1_gate.py new file mode 100644 index 00000000..b63ebb2c --- /dev/null +++ b/evals/logos/fa1_gate.py @@ -0,0 +1,273 @@ +"""FA-1 — the pre-registered holonomy-gate experiment. + +Criterion, corpus rules, negative classes and anti-gaming rules are registered in +`docs/analysis/fa1-holonomy-gate-preregistration.md` — committed at `94f05ba8`, and amended +once (A1, the corpus bound) before any discrimination number existed. This module executes +that registration; every threshold it applies is quoted from it, and none is computed here. + +Run: ``uv run python -m evals.logos.fa1_gate`` +""" + +from __future__ import annotations + +import json +from collections import defaultdict +from dataclasses import dataclass +from itertools import combinations, permutations + +import numpy as np + +from algebra.cl41 import geometric_product, reverse as cl_reverse +from algebra.versor import unitize_versor +from alignment.graph import load_alignment +from evals.logos.manifold_collapse import _census +from evals.logos.repaired_ground import IDENTITY, LOGOS_MOUNT, forward_walk, repaired_compiler +from packs.compiler import load_mounted_packs, load_pack_entries + +#: Packs whose alignment.jsonl is read. Every edge they author participates; none is dropped +#: by hand. An edge whose endpoints do not resolve inside LOGOS_MOUNT is counted and reported. +ALIGNED_PACKS = ("he_logos_micro_v1", "grc_logos_micro_v1", "he_core_cognition_v1", "grc_logos_cognition_v1") + +#: Three content words is ADR-0015's own canonical clause shape ("Logos, beginning, truth"), +#: and the shortest length at which word order can be broken in more than one way. +CLAUSE_LEN = 3 + +#: Pre-registered thresholds — anti-gaming rule 1 forbids editing these after numbers exist. +G1_AUC = 0.80 +G2_AUC = 0.75 +G3_ORDER_SENSITIVITY = 0.90 + +#: Amendment A1: cross-pair negatives per aligned pair, drawn deterministically from the +#: sorted enumeration. Classes 1 and 2 stay complete. +CROSS_NEGATIVES_PER_PAIR = 5 + + +@dataclass(frozen=True) +class Concept: + """One aligned meaning and its realisation in each language that carries it.""" + + key: str + surfaces: tuple[tuple[str, str], ...] # (language, surface), sorted — hashable + + @property + def by_language(self) -> dict[str, str]: + return dict(self.surfaces) + + @property + def languages(self) -> frozenset[str]: + return frozenset(language for language, _ in self.surfaces) + + +def _relation_stem(relation: str) -> str: + """`cross_lang.logos.illumination.pointed.en` → `cross_lang.logos.illumination`.""" + return relation.replace(".pointed", "").replace(".en_collapse", "").replace(".en", "") + + +def build_concepts() -> tuple[list[Concept], dict]: + """Group every resolvable alignment edge into meanings. Mechanical; nothing hand-picked. + + Where a language offers several surfaces for one meaning (`אור` / `אוֹר`), the + lexicographically first is taken — one rule, applied uniformly, fixed before the run. + """ + surface_of: dict[str, tuple[str, str]] = {} + for pack_id in LOGOS_MOUNT: + for entry in load_pack_entries(pack_id): + surface_of[entry.entry_id] = (entry.language, entry.surface) + + grouped: dict[str, dict[str, set[str]]] = defaultdict(lambda: defaultdict(set)) + edges = unresolved = 0 + for pack_id in ALIGNED_PACKS: + for edge in load_alignment(pack_id).aligned_pairs("cross_lang"): + edges += 1 + source, target = surface_of.get(edge.source_id), surface_of.get(edge.target_id) + if source is None or target is None: + unresolved += 1 + continue + stem = _relation_stem(edge.relation) + grouped[stem][source[0]].add(source[1]) + grouped[stem][target[0]].add(target[1]) + + concepts = [ + Concept(key=stem, surfaces=tuple(sorted((lang, sorted(surfaces)[0]) for lang, surfaces in langs.items()))) + for stem, langs in sorted(grouped.items()) + if len(langs) >= 2 + ] + census = { + "edges_authored": edges, + "edges_unresolved_under_R3": unresolved, + "concepts": len(concepts), + "trilingual_concepts": sum(1 for concept in concepts if len(concept.languages) >= 3), + "clause_sets": len(list(combinations(range(len(concepts)), CLAUSE_LEN))), + } + return concepts, census + + +def _auc(positive: list[float], negative: list[float]) -> float: + """P(a random aligned pair deviates LESS than a random negative). Ties count a half. + + Computed by sorting rather than by the O(n·m) double loop — the corpus is ~10^5 negatives + and the naive form is the difference between seconds and an hour. + """ + if not positive or not negative: + return float("nan") + pos = np.sort(np.asarray(positive, dtype=np.float64)) + neg = np.asarray(negative, dtype=np.float64) + strictly_less = np.searchsorted(pos, neg, side="left").sum() # positives below each negative + less_or_equal = np.searchsorted(pos, neg, side="right").sum() + ties = less_or_equal - strictly_less + return float((strictly_less + 0.5 * ties) / (len(pos) * len(neg))) + + +def run() -> dict: + concepts, census = build_concepts() + index_of = {concept: position for position, concept in enumerate(concepts)} + + with repaired_compiler(LOGOS_MOUNT): + manifold = load_mounted_packs(LOGOS_MOUNT) + surfaces, distinct, groups = _census(manifold) + + walk_cache: dict[tuple[tuple[str, ...], str], np.ndarray | None] = {} + + def walk(sequence: tuple[Concept, ...], language: str) -> np.ndarray | None: + """`F(X)` for one clause, memoised — the same ordered tuple recurs constantly.""" + key = (tuple(concept.key for concept in sequence), language) + if key not in walk_cache: + try: + versors = [manifold.get_versor(concept.by_language[language]) for concept in sequence] + except KeyError: + walk_cache[key] = None + else: + walk_cache[key] = forward_walk(versors) + return walk_cache[key] + + def deviation(sequence_a: tuple[Concept, ...], la: str, sequence_b: tuple[Concept, ...], lb: str) -> float | None: + """`d(A,B) = ‖unitize(F(A)·reverse(F(B))) − 1‖`, versor sign-ambiguity resolved.""" + forward, backward = walk(sequence_a, la), walk(sequence_b, lb) + if forward is None or backward is None: + return None + holonomy = unitize_versor(geometric_product(forward, cl_reverse(backward))) + return float(min(np.linalg.norm(holonomy - IDENTITY), np.linalg.norm(holonomy + IDENTITY))) + + # ---- the aligned corpus: every C(24,3) concept set, every language pair carrying it + aligned: list[float] = [] + pairs: list[tuple[tuple[Concept, ...], str, str]] = [] + clause_sets = list(combinations(concepts, CLAUSE_LEN)) + for concept_set in clause_sets: + shared = sorted(frozenset.intersection(*(concept.languages for concept in concept_set))) + for position, la in enumerate(shared): + for lb in shared[position + 1 :]: + value = deviation(concept_set, la, concept_set, lb) + if value is None: + continue + aligned.append(value) + pairs.append((concept_set, la, lb)) + + # ---- class 1, lexical substitution (complete: every position × every replacement) + n_lexical: list[float] = [] + for concept_set, la, lb in pairs: + for position in range(CLAUSE_LEN): + for replacement in concepts: + if replacement in concept_set or lb not in replacement.languages: + continue + broken = concept_set[:position] + (replacement,) + concept_set[position + 1 :] + value = deviation(concept_set, la, broken, lb) + if value is not None: + n_lexical.append(value) + + # ---- class 2, word order (complete: every non-identity permutation of the return clause) + n_order: list[float] = [] + order_sensitive = order_total = 0 + for index, (concept_set, la, lb) in enumerate(pairs): + base = aligned[index] + for permuted in permutations(concept_set): + if permuted == concept_set: + continue + value = deviation(concept_set, la, permuted, lb) + if value is None: + continue + n_order.append(value) + order_total += 1 + order_sensitive += int(value > base) + + # ---- class 3, cross-pairing (Amendment A1: first 5 disjoint sets in sorted order) + n_cross: list[float] = [] + cross_offered = 0 + for concept_set, la, lb in pairs: + start = max(index_of[concept] for concept in concept_set) + taken = 0 + for other in clause_sets: + if taken >= CROSS_NEGATIVES_PER_PAIR: + break + if min(index_of[concept] for concept in other) <= start: + continue + if set(other) & set(concept_set): + continue + cross_offered += 1 + value = deviation(concept_set, la, other, lb) + if value is None: + continue + n_cross.append(value) + taken += 1 + + # ---- controls, reported regardless of outcome (anti-gaming rule 4) + self_loops = [ + value + for value in (deviation(concept_set, la, concept_set, la) for concept_set, la, _ in pairs) + if value is not None + ] + + negatives = n_lexical + n_order + n_cross + g1 = _auc(aligned, negatives) + g2 = _auc(aligned, n_cross) + g3 = order_sensitive / order_total if order_total else float("nan") + g4_lost = surfaces - distinct + + verdict: dict = { + "corpus": census, + "ground_after_repair": { + "mount": list(LOGOS_MOUNT), + "surfaces": surfaces, + "distinct_coordinates": distinct, + "coordinates_lost": g4_lost, + "collision_groups": len(groups), + }, + "controls": { + "max_self_loop_deviation": max(self_loops) if self_loops else float("nan"), + "aligned_pairs": len(aligned), + "negatives_generated": { + "lexical_substitution": len(n_lexical), + "word_order": len(n_order), + "cross_pair": len(n_cross), + "cross_pair_candidates_offered": cross_offered, + }, + }, + "measurements": { + "median_aligned": float(np.median(aligned)) if aligned else float("nan"), + "median_negative": float(np.median(negatives)) if negatives else float("nan"), + # Per-class, for diagnosis. These are the pre-registered classes reported + # separately; no criterion is attached to them that is not already in §4. + "median_lexical": float(np.median(n_lexical)) if n_lexical else float("nan"), + "median_word_order": float(np.median(n_order)) if n_order else float("nan"), + "median_cross_pair": float(np.median(n_cross)) if n_cross else float("nan"), + "auc_vs_lexical": _auc(aligned, n_lexical), + "auc_vs_word_order": _auc(aligned, n_order), + "auc_vs_cross_pair": _auc(aligned, n_cross), + }, + "criterion": { + "G1_separation": {"required": f"AUC >= {G1_AUC}", "observed": g1, "pass": bool(g1 >= G1_AUC)}, + "G2_cross_pair": {"required": f"AUC >= {G2_AUC}", "observed": g2, "pass": bool(g2 >= G2_AUC)}, + "G3_word_order": {"required": f">= {G3_ORDER_SENSITIVITY}", "observed": g3, "pass": bool(g3 >= G3_ORDER_SENSITIVITY)}, + "G4_no_collapse": {"required": "coordinates_lost == 0", "observed": g4_lost, "pass": bool(g4_lost == 0)}, + }, + } + verdict["VERDICT"] = "GO" if all(gate["pass"] for gate in verdict["criterion"].values()) else "NO-GO" + return verdict + + +def main() -> int: + print(json.dumps(run(), indent=2, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/evals/logos/repaired_ground.py b/evals/logos/repaired_ground.py new file mode 100644 index 00000000..c2de4c64 --- /dev/null +++ b/evals/logos/repaired_ground.py @@ -0,0 +1,154 @@ +"""The repaired semantic ground — R1/R2/R3 of the FA-1 pre-registration. + +`docs/analysis/fa1-holonomy-gate-preregistration.md` registers three repairs and the +criterion that judges them. This module implements the repairs; it takes no verdict. + +Nothing here modifies the serving compiler. R1 and R3 are applied by *substituting two +module-level functions* inside a context manager, against the real compile path, so the +experiment measures the shipped pipeline with two operations corrected rather than a +re-implementation that could drift from it. The caches are cleared on entry and on exit, +so a repaired ground never leaks into anything else in the process. + + R1 blend on the group instead of overwriting — `algebra/rotor.py`'s geodesic + R2 close the loop around the *pair* — `loop_deviation` below + R3 resolve alignment edges against the packs actually mounted, not an id-prefix table +""" + +from __future__ import annotations + +from contextlib import contextmanager +from typing import Iterator + +import numpy as np + +from algebra.cl41 import geometric_product, reverse as cl_reverse +from algebra.rotor import rotor_power, word_transition_rotor +from algebra.versor import unitize_versor +from packs import compiler as _compiler + +#: The six packs that carry logos data. The serving path grounds Hebrew and Greek against +#: the `*_cognition_v1` pair (`chat/pack_grounding.py:56-57`); the micro packs are the +#: ADR-0015 seed. R3 exists so both participate — 63 of the 83 authored edges live in the +#: cognition packs and currently resolve to nothing. +LOGOS_MOUNT: tuple[str, ...] = ( + "en_minimal_v1", + "en_core_cognition_v1", + "he_logos_micro_v1", + "he_core_cognition_v1", + "grc_logos_micro_v1", + "grc_logos_cognition_v1", +) + +#: `en_collapse_anchors_v1` is DELIBERATELY ABSENT and cannot be added: its manifest declares +#: `role: "collapse_anchor"`, which is not a member of `packs.schema.LanguageRole`, so +#: `load_pack()` raises before the lexicon is read. `chat/pack_grounding.py` consumes it by +#: opening `lexicon.jsonl` on a raw path — bypassing the loader that would reject it — and 24 +#: alignment edges point into it, all of which therefore resolve to nothing. The enum carries +#: a comment recording this exact failure once before (ADR-0097, `domain_seed`); a registry +#: widened by hand falls behind by hand. Reported by the gate as `edges_unresolved_under_R3` +#: rather than silently excluded. + +#: Identity of Cl(4,1): the scalar 1. A closed loop returns here. +IDENTITY = np.zeros(32, dtype=np.float64) +IDENTITY[0] = 1.0 + + +# --------------------------------------------------------------------------- +# R1 — interpolation that stays on the versor group +# --------------------------------------------------------------------------- + +def geodesic_blend(source: np.ndarray, target: np.ndarray, strength: float) -> np.ndarray: + """Move `source` a fraction `strength` along the group geodesic toward `target`. + + The operation the compiler's `_blend_feature_versors` was named and parametrised for. + A linear combination of two versors is not a versor — the group is not closed under + addition — which is why `VocabManifold.update()` refuses one, and why returning the + target verbatim became the escape. `R^t · source`, with `R` the transition rotor, is + lawful at every `t`: it stays on the group by construction. + + Endpoints are exact: `t=0` returns the source, `t=1` the target. + """ + strength = max(0.0, min(1.0, float(strength))) + src = np.asarray(source, dtype=np.float64) + if strength <= 0.0: + return np.asarray(source, dtype=np.float32).copy() + tgt = np.asarray(target, dtype=np.float64) + if strength >= 1.0: + return np.asarray(target, dtype=np.float32).copy() + moved = geometric_product(rotor_power(word_transition_rotor(src, tgt), strength), src) + return np.asarray(unitize_versor(moved), dtype=np.float32) + + +def _clear_pack_caches() -> None: + _compiler._load_pack_cached.cache_clear() + _compiler._load_mounted_packs_cached.cache_clear() + _compiler._load_pack_entries_cached.cache_clear() + + +@contextmanager +def repaired_compiler(pack_ids: tuple[str, ...] = LOGOS_MOUNT) -> Iterator[None]: + """Compile with R1 (geodesic blending) and R3 (mount-wide edge resolution) in force. + + R3 replaces the hardcoded `{"he": …, "grc": …, "en": …}` prefix table with the packs + actually being mounted. Entry ids are pack-local, so a global prefix table can only + ever address the three packs it names; every edge belonging to any other pack of the + same language resolves to a pack that has never heard of it and is dropped in silence. + """ + original_blend = _compiler._blend_feature_versors + original_infer = _compiler._infer_foreign_pack_ids + + def _infer_from_mount(home_pack_id: str, _graph) -> list[str]: # noqa: ANN001 - private type + # The graph is ignored on purpose: membership of the mount, not the shape of the + # edge ids, decides which packs an edge may address. + return [pack_id for pack_id in pack_ids if pack_id != home_pack_id] + + _compiler._blend_feature_versors = geodesic_blend + _compiler._infer_foreign_pack_ids = _infer_from_mount + _clear_pack_caches() + try: + yield + finally: + _compiler._blend_feature_versors = original_blend + _compiler._infer_foreign_pack_ids = original_infer + _clear_pack_caches() + + +# --------------------------------------------------------------------------- +# R2 — the loop, closed around a pair of representations +# --------------------------------------------------------------------------- + +def forward_walk(versors: list[np.ndarray]) -> np.ndarray: + """`F(X) = v₁ · v₂ · … · vₙ` — the ordered geometric product of a clause's versors. + + No per-token position rotor. The shipped encoder conjugates each token by its absolute + index, which was introduced so path order would survive degenerate scalar/vector test + fixtures; across languages, clauses differ in length, so that schedule injects a length + artefact into every cross-language comparison (RQ2 of the 2026-06-14 finding). Order + sensitivity does not depend on it: the geometric product is non-commutative. + """ + if not versors: + raise ValueError("cannot walk an empty clause") + walk = unitize_versor(np.asarray(versors[0], dtype=np.float64)) + for versor in versors[1:]: + walk = geometric_product(walk, unitize_versor(np.asarray(versor, dtype=np.float64))) + walk = unitize_versor(walk) + return walk + + +def closed_holonomy(clause_a: list[np.ndarray], clause_b: list[np.ndarray]) -> np.ndarray: + """`H(A,B) = unitize(F(A) · reverse(F(B)))` — transport out along A, return along B.""" + return unitize_versor(geometric_product(forward_walk(clause_a), cl_reverse(forward_walk(clause_b)))) + + +def loop_deviation(clause_a: list[np.ndarray], clause_b: list[np.ndarray]) -> float: + """`d(A,B) = ‖H(A,B) − 1‖` — the single declared metric of the FA-1 criterion. + + Zero when the loop closes. The null hypothesis is the identity, so there is no + similarity threshold to calibrate and no metric to select after seeing the numbers — + the failure mode of the June measurement, forbidden here by construction. + + Sign note: `H` and `−H` describe the same transport (versors are defined up to sign), + so the deviation is taken to the nearer of `±1`. + """ + holonomy = closed_holonomy(clause_a, clause_b) + return float(min(np.linalg.norm(holonomy - IDENTITY), np.linalg.norm(holonomy + IDENTITY))) diff --git a/tests/test_fa1_gate_verdict.py b/tests/test_fa1_gate_verdict.py new file mode 100644 index 00000000..bacba8e4 --- /dev/null +++ b/tests/test_fa1_gate_verdict.py @@ -0,0 +1,125 @@ +"""FA-1's verdict, pinned as a tripwire (G-25, L2, the CORE-Logos validation-gate claim). + +Measured 2026-07-28 against the criterion registered at `94f05ba8`: on a semantic ground with +**zero** coordinate collisions and a genuinely closed loop, cross-language holonomy separates +aligned clauses from meaning-breaking ones at **AUC 0.557** — a chance floor is 0.500. The +design's own word-order test holds only **64.4%** of the time. **NO-GO.** + +Full result and diagnosis: `docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md`. + +This pin exists for the reason the 2026-06-14 holonomy finding gave for its own tripwires: a +negative result that nothing checks decays into folklore, and the *interesting* failure mode +is the good one — a future encoding that makes the gate real, landing silently while four +documents still say it was measured and refused. If any of these assertions goes red because +the numbers improved, that is not a regression: replace the guard with a proof, amend the ADRs +back, and update the verdict document in the same commit. + +Both directions are pinned. The verdict may not silently become GO, and the measured values +may not silently drift either — the gate is deterministic, so any movement means the ground, +the encoder, or the corpus changed, and each of those is a decision someone should be making +on purpose. + +Cost: ~16s. Paid deliberately for the tripwire on the system's central design claim, the way +the daemon pin's 9.4s was paid — stated here rather than estimated. +""" + +from __future__ import annotations + +import pytest + +from evals.logos.fa1_gate import G1_AUC, G2_AUC, G3_ORDER_SENSITIVITY, run + +#: Measured 2026-07-28 at `94f05ba8`+1, deterministic and re-run bit-identical. +RECORDED = { + "G1_separation": 0.5570894467955959, + "G2_cross_pair": 0.6641552204384346, + "G3_word_order": 0.6444881889763779, + "G4_no_collapse": 0, +} +RECORDED_ALIGNED_PAIRS = 1016 +RECORDED_CONCEPTS = 24 +RECORDED_UNRESOLVED_EDGES = 24 # every one of them into the unloadable collapse-anchor pack + + +@pytest.fixture(scope="module") +def verdict() -> dict: + return run() + + +def test_the_repairs_still_work(verdict: dict) -> None: + """G4 and the controls. If these fail, the NO-GO below is uninterpretable. + + This is the half of the result that SUCCEEDED: the geodesic blend keeps every distinction + (0 lost coordinates where the shipped compiler loses 53 on this mount), and the closed loop + reports closure on a self-pair. A null result measured with a broken instrument constrains + nothing, which is exactly why the June measurement had to be re-run. + """ + assert verdict["ground_after_repair"]["coordinates_lost"] == 0, ( + "G4 FAILED: the repaired ground now collapses coordinates. Every discrimination number " + "in this file is measured on a different ground than the verdict document describes." + ) + assert verdict["controls"]["max_self_loop_deviation"] < 1e-4, ( + "the loop no longer closes on a self-pair — the metric is broken, not the hypothesis" + ) + assert verdict["controls"]["aligned_pairs"] == RECORDED_ALIGNED_PAIRS, ( + f"corpus changed: {verdict['controls']['aligned_pairs']} aligned pairs vs " + f"{RECORDED_ALIGNED_PAIRS} recorded — re-baseline deliberately, in the verdict doc too" + ) + + +def test_the_verdict_is_still_no_go(verdict: dict) -> None: + """The tripwire. Red here means the design became real — go read the failure message.""" + assert verdict["VERDICT"] == "NO-GO", ( + "FA-1's holonomy gate now PASSES. This is good news and it is not a test failure to " + "silence: replace this guard with a proof, amend ADR-0005/0015 back (they were amended " + "to record the negative), and rewrite " + "docs/analysis/fa1-holonomy-gate-verdict-2026-07-28.md in the same commit." + ) + criterion = verdict["criterion"] + assert not criterion["G1_separation"]["pass"], f"G1 now passes at {criterion['G1_separation']['observed']}" + assert not criterion["G2_cross_pair"]["pass"], f"G2 now passes at {criterion['G2_cross_pair']['observed']}" + assert not criterion["G3_word_order"]["pass"], f"G3 now passes at {criterion['G3_word_order']['observed']}" + + +def test_the_recorded_numbers_have_not_drifted(verdict: dict) -> None: + """Deterministic gate, exact record. Movement in either direction is a reviewed decision.""" + for gate, expected in RECORDED.items(): + observed = verdict["criterion"][gate]["observed"] + assert observed == pytest.approx(expected, abs=1e-9), ( + f"{gate} moved: {observed} vs {expected} recorded. The gate is deterministic, so " + "the ground, the encoder, or the corpus changed. Update this constant and the " + "verdict document together — two records that disagree is the G-22 disease." + ) + assert verdict["corpus"]["concepts"] == RECORDED_CONCEPTS + assert verdict["corpus"]["edges_unresolved_under_R3"] == RECORDED_UNRESOLVED_EDGES, ( + "the unresolved-edge count moved. All 24 point into en_collapse_anchors_v1, which " + "cannot load because its manifest declares role='collapse_anchor' and that is not a " + "member of packs.schema.LanguageRole. If this dropped to 0 the enum was widened — good; " + "record it, because those edges then join the corpus and every number above moves." + ) + + +def test_the_gate_is_not_vacuous(verdict: dict) -> None: + """A criterion that cannot pass proves nothing (R-11's lesson, PR-12's hollow guard). + + The thresholds must be reachable and the classes must be ordered by meaning-distance — + otherwise "NO-GO" would be an artefact of an impossible bar rather than a measurement. + """ + assert 0.5 <= G1_AUC <= 1.0 and 0.5 <= G2_AUC <= 1.0 and 0.5 <= G3_ORDER_SENSITIVITY <= 1.0, ( + "a threshold outside [0.5, 1.0] would be unreachable or trivially met by chance" + ) + measurements = verdict["measurements"] + assert measurements["median_aligned"] < measurements["median_negative"], ( + "aligned pairs no longer close tighter than negatives — the signal reversed sign, which " + "is a different (and larger) finding than the recorded weak-but-correct-direction one" + ) + # The classes order themselves by distance from the original meaning. This is the part of + # the design that DID hold, and it is what makes the null a measurement rather than noise. + assert ( + measurements["median_lexical"] < measurements["median_word_order"] < measurements["median_cross_pair"] + ), ( + "the negative classes no longer order by meaning-distance " + f"(lexical={measurements['median_lexical']}, order={measurements['median_word_order']}, " + f"cross={measurements['median_cross_pair']}) — re-diagnose before trusting the verdict" + ) + assert verdict["controls"]["negatives_generated"]["cross_pair"] > 0, "no negatives generated"