diff --git a/README.md b/README.md index 96d0df66..e716da76 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,76 @@ English establishes the operational base. Hebrew and Koine Greek bring the hidde ```bash pip install -e ".[dev]" -pytest tests/test_versor_closure.py # must pass before anything else -pytest tests/ +pytest tests/test_versor_closure.py # the core invariant — must pass first +pytest tests/ # full suite (~4 minutes, 1099 tests) ``` +### CLI + +The `core` CLI exposes curated entry points so reviewers can run any +subsystem in isolation. Highlights: + +```bash +core test --list-suites # list curated pytest suite aliases +core test --suite fast # ~2s iteration lane +core test --suite cognition # cognition pipeline lane +core test --suite algebra # versor / CGA / vault parity +core test --suite adr-0024 # Forward Semantic Control chain (98 tests) + +core demo phase6 # 3-condition comparative table (CORE vs baseline) +core demo phase5 # stratified 5-family mechanism-isolation +core demo all # both + combined summary +core demo list-results # index every JSON report with headline metrics + +core eval --list # discover eval lanes +core eval cognition # run a discovered lane +core trace "your text here" # one-turn field-telemetry trace +core pulse "What is truth?" # one full cognitive pulse +core bench --suite latency # benchmark harness +core doctor --packs --rust # environment + pack + Rust status +``` + +Every demo run rewrites `evals/forward_semantic_control/results/` +including an auto-refreshed `index.json` manifest — the single +place reviewers can read to see every available report. + +--- + +## Forward Semantic Control — The ADR-0024 Chain + +CORE generates text without sampling. The generation walk is +deterministic at the algebra level, but a deterministic walk over a +boundary-only candidate scorer can still emit tokens that are +inadmissible under the relation being asserted (e.g. answering a +*causes* question with the *means*-target). The ADR-0024 chain closes +that gap with five Architecture Decision Records and six phases of +implementation evidence. + +| Layer | What it guarantees | ADR | +|---|---|---| +| **AdmissibilityRegion** | A typed region (`allowed_indices`, `relation_blade`, `frame_versor`) carried alongside every generation step. | [0022](docs/decisions/ADR-0022-forward-semantic-control.md) | +| **Region intersection proof** | The admissible token set is honored at the language/salience intersection layer. | [0023](docs/decisions/ADR-0023-forward-semantic-control-proof.md) | +| **Inner-loop destination check** | Each candidate's `cga_inner(versor(candidate), relation_blade)` is checked at the destination; rejection appears in `rejected_attempts`; exhaustion raises a typed `InnerLoopExhaustion`. | [0024](docs/decisions/ADR-0024-inner-loop-admissibility.md) | +| **Rotor / frame admissibility** | The rotor's *effect* on the field state is additionally checked against `frame_versor` in `generate/rotor_admissibility.py` — separate from algebra closure (intentional). | [0025](docs/decisions/ADR-0025-rotor-frame-admissibility-design-note.md) | +| **Ranked-with-margin gate** | Static-threshold tuning fails geometrically under Cl(4,1) signature; replaced with a scale-invariant margin gate (admit iff `score(top) − score(second) ≥ δ`). | [0026](docs/decisions/ADR-0026-ranked-admissibility-with-margin.md) | + +The chain's three head-to-head claims, all CI-enforced: + +| Claim | Test contract | Live demo | +|---|---|---| +| **C1 — Replay determinism** | `core test --suite phase6 -k TestC1` | `core demo phase6` | +| **C2 — Traced rejection** | `core test --suite phase6 -k TestC2` | `core demo phase6` | +| **C3 — Coherent refusal** | `core test --suite phase6 -k TestC3` | `core demo phase6` | + +Full evidence: + +* Runtime contract: [`docs/runtime_contracts.md`](docs/runtime_contracts.md) — Refusal / Margin / Rotor admissibility sections +* Stratified findings: [`docs/evals/phase5_stratified_findings.md`](docs/evals/phase5_stratified_findings.md) — 5 failure-mode families, 20 cases, per-family pass rates +* Comparative demo: [`docs/evals/phase6_comparative_demo.md`](docs/evals/phase6_comparative_demo.md) — three head-to-head conditions vs in-system baseline +* Reports directory: `evals/forward_semantic_control/results/` + +--- + ## Architecture ``` diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index b8df1f68..b2f3406e 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -3,6 +3,51 @@ Tracks completion of the phased plan defined in `docs/capability_roadmap.md` (ADR-0016). Updated as work lands. +> **Naming note.** "Phase N" in this document refers to capability-roadmap +> phases (Phase 0 through Phase 5+). The ADR-0024 chain has its *own* +> six-phase plan (Phase 1 through Phase 6) which is tracked separately +> immediately below. Do not conflate the two. + +--- + +## ADR-0024 Chain — Forward Semantic Control Closure + +**Status:** Complete ✓ +**Closed:** 2026-05-17 + +A standalone six-phase plan that closes forward semantic control as a +deterministic, trace-evidenced, refuse-able mechanism. Distinct from +the capability-roadmap phases below. + +| Phase | Commit | Deliverable | Contract tests | +|---|---|---|---| +| 1 | `3940290` | Pack-grounded fixture rewrite + architectural finding | (rewrites) | +| 2 | `310793a` | Typed `InnerLoopExhaustion` + `RefusalReason` + trace fold | +10 | +| 3 | `639e107` | ADR-0026 ranked-with-margin gate (δ = 0.4 default) | +13 | +| 4 | `542e13d` | ADR-0025 rotor / frame admissibility (sibling module) | +11 | +| 5 | `b664984` | Stratified 5-family mechanism-isolation corpus + benign EXHAUSTION_CEILING corpus | +20 | +| 6 | `a076506` | Three-condition comparative demo (C1 replay / C2 traced rejection / C3 coherent refusal) | +17 | +| CLI | `36aad75` | Suite aliases (`adr-0024`, `refusal`, `margin`, `rotor`, …) + `core demo` subcommand + results manifest | +14 | + +ADRs moved to Accepted under this chain: 0024, 0025, 0026. +ADRs strengthened: 0022 (TBDs closed), 0023 (proof evidence expanded). + +Evidence locations: + +- Runtime contracts: `docs/runtime_contracts.md` — Refusal / Margin / Rotor admissibility sections +- Stratified findings: `docs/evals/phase5_stratified_findings.md` +- Comparative demo: `docs/evals/phase6_comparative_demo.md` +- Reports: `evals/forward_semantic_control/results/` (+ auto-refreshed `index.json`) +- ADR index: `docs/decisions/README.md` — "ADR-0024 chain" section + +How to verify on a fresh checkout: + +```bash +core test --suite adr-0024 # 98 contract tests across the chain (~2 min) +core demo all # phase5 + phase6 + combined summary (~40 s) +core demo list-results # index of every JSON report with headline metrics +``` + --- ## Phase 0 — Benchmark Methodology Lock-in diff --git a/docs/Whitepaper.md b/docs/Whitepaper.md index 47b60fdf..afe726d8 100644 --- a/docs/Whitepaper.md +++ b/docs/Whitepaper.md @@ -262,7 +262,102 @@ The `docs/DELETION_LOG.md` records every deleted subsystem and the algebraic rea --- -### XII. Extensions +### XII. Forward Semantic Control — Generation Without Sampling + +The CORE generation walk is deterministic at the algebra level — +every step is a versor product, every selection is the nearest-by- +`cga_inner` candidate. But a deterministic walk over a boundary-only +scorer can still emit tokens that are *inadmissible under the +relation being asserted*. Asking *"what does symbol cause?"* the +boundary may return *image* — geometrically nearest, but `means`- +related, not `causes`-related. The token is on the manifold; the +relation is wrong. + +The Forward Semantic Control chain (ADR-0022 through ADR-0026) closes +this gap without introducing sampling, sampling temperature, or any +stochastic mechanism whatsoever. The mechanism has five components: + +1. **AdmissibilityRegion** (ADR-0022). A typed region carried + alongside every generation step. Fields: `allowed_indices` (the + admissible token set), `relation_blade` (the multivector + characterizing the relation), and optionally `frame_versor` (the + rotor-side constraint). The region is constructed once per turn + from the proposition graph and held immutable through the walk. +2. **Region intersection at selection** (ADR-0023). At every + selection point, the language/salience candidate set is + intersected with `allowed_indices` before scoring. An empty + intersection raises `ValueError`; the walk routes to the + unknown-domain surface (honest refusal at the pre-walk boundary). +3. **Inner-loop destination check** (ADR-0024). The boundary + selection is re-evaluated against the relation blade via + `cga_inner(versor(candidate), relation_blade)`. Rejected + candidates are excluded and the walk re-selects; when every + candidate is rejected, generation raises `InnerLoopExhaustion` + carrying a typed `RefusalReason` (`INNER_LOOP_EXHAUSTION` or + `ROTOR_REJECTION`), the responsible region label, the step + index, and the full list of rejected attempts as evidence. + Refusal is not silent — it is structured data. +4. **Rotor / frame admissibility** (ADR-0025). When the region + carries a `frame_versor`, the rotor's *effect on the field + state* is additionally checked: `cga_inner(versor_apply(V, F), + frame_versor) > 0`. This check lives in + `generate/rotor_admissibility.py` — *not* in `algebra/versor.py`, + because admissibility is a pack-semantic test, not a closure + invariant. Putting it in algebra would couple closure to pack + state and structurally invite grade-projection "repair" of + inadmissible rotors. That coupling was rejected by name in the + ADR. +5. **Ranked-with-margin gate** (ADR-0026). A static + `admissibility_threshold` is brittle under the Cl(4,1) + Lorentzian signature: 23 of 85 tokens in the cognition pack have + *negative* self-`cga_inner`, so no scalar threshold separates + admissible from inadmissible across the corpus + (Phase 4 characterization confirms separation_quality < 0.8 at + every probed threshold). The chain replaces threshold tuning + with a scale-invariant margin test: admit iff + `score(top) − score(second) ≥ δ`. Default δ = 0.4, chosen from + the minimum observed margin in the Phase 3 adversarial corpus + and falsifiable against future cases. + +The chain is enforced by 98 CI-level contract tests +(`core test --suite adr-0024`). Three head-to-head claims are pinned +against an in-system ablation baseline: + +- **C1 — Replay determinism:** Trace hashes are byte-identical + across N reruns under both baseline and CORE; refusal events + themselves are replayable because `refusal_reason` is folded into + `trace_hash`. +- **C2 — Traced rejection:** When the boundary selection diverges + from the blade-aligned selection, the rejected token appears in + `rejected_attempts`. The rejection is *causally responsible* for + the selection difference, not just observable. +- **C3 — Coherent refusal:** When no candidate is admissible, the + walk raises `InnerLoopExhaustion` with a typed reason and + evidence list. The ablation baseline emits an inadmissible + candidate with `admitted = False` — observable but not + actionable. Typed refusal is *new in CORE*, not present in the + boundary-only baseline. + +This is what we mean by "deterministic cognition that can refuse": +the system never samples to recover from an inadmissible state — it +either selects an admissible candidate, or it reports honestly that +it cannot. + +A transformer LLM cannot exhibit these properties at the mechanism +level. Sampling has no place to attach an `AdmissibilityRegion`, +no way to make refusal first-class evidence, and no replay- +deterministic trace. The chain is the structural defense against +the confabulation failure mode at exactly the layer where it must +be defended — generation itself. + +Full evidence: `docs/runtime_contracts.md` (contracts), +`docs/evals/phase5_stratified_findings.md` (geometric +characterization), `docs/evals/phase6_comparative_demo.md` +(head-to-head demo). + +--- + +### XIII. Extensions **CORE-Logos** — The language articulation subsystem. Specified in the companion Yellow and White Addenda inherited from `core-ai`. The Logos defines the vocabulary manifold, the token projection law, the holonomy encoder, and the termination condition. diff --git a/docs/Yellowpaper.md b/docs/Yellowpaper.md index 2682d00e..9c62f1b5 100644 --- a/docs/Yellowpaper.md +++ b/docs/Yellowpaper.md @@ -387,6 +387,215 @@ Personas compose. Two persona motors can be combined into a single motor before --- +### IX-B. Forward Semantic Control — Formal Admissibility Specification + +This section provides the precise mathematical specification of the +Forward Semantic Control mechanism (ADRs 0022, 0023, 0024, 0025, +0026). The Whitepaper describes the architectural commitment; this +section is the formal contract. + +#### 1. AdmissibilityRegion + +An `AdmissibilityRegion` is the triple + +```text +R = (I, B, Φ) + +where + I ∈ ℕᵏ : the admissible token index set (k ≥ 1) + B ∈ Cl(4,1) : the relation blade (a multivector, not necessarily simple) + Φ ∈ Cl(4,1)* : an optional frame versor (None ⇒ no rotor constraint) +``` + +Module: `generate/admissibility.py::AdmissibilityRegion`. The region +is constructed once per turn from the proposition graph and is +held immutable for the duration of the generation walk. No +in-walk mutation of `R` is permitted. + +#### 2. Destination-side admissibility (ADR-0024) + +For a candidate token `t` with versor `V_t ∈ Cl(4,1)`, define the +*destination score* + +```text +σ_dest(t, R) = cga_inner(V_t, B) +``` + +In **threshold mode** (the back-compat default), `t` is *admitted* +iff + +```text +admit_threshold(t, R, τ) ⇔ σ_dest(t, R) > τ +``` + +where `τ ∈ ℝ` is the `admissibility_threshold` configured per turn. +In **margin mode** (ADR-0026), the admissibility test is on a *pair* +of ranked candidates rather than a single candidate. See §4. + +Module: `generate/admissibility.py::check_transition`. + +#### 3. Rotor-side admissibility (ADR-0025) + +When `R.Φ ≠ None`, the rotor that would advance the field state must +also be admissible. For a rotor `V` and current field state `F`, +define the *post-rotor field* + +```text +F' = versor_apply(V, F) = V · F · reverse(V) +``` + +and the *rotor score* + +```text +σ_rotor(V, F, Φ) = cga_inner(F', Φ) +``` + +The rotor is *admitted* iff + +```text +admit_rotor(V, F, Φ) ⇔ σ_rotor(V, F, Φ) > 0 +``` + +When `R.Φ = None` (or `||Φ|| < 10⁻⁸`), `admit_rotor` returns `True` +unconditionally with `σ_rotor = +∞` as the sentinel. + +Module: `generate/rotor_admissibility.py::check_rotor_admissibility`. + +**Architectural placement (load-bearing).** This check lives in +`generate/rotor_admissibility.py`, a sibling-but-separate module to +`generate/admissibility.py`. It is **not** placed in +`algebra/versor.py` (would couple algebra to pack-derived +admissibility state and structurally invite grade-projection +"repair" of inadmissible rotors) and **not** in +`field/propagate.py` (forbidden normalization/repair site per +`CLAUDE.md`). + +#### 4. Ranked-with-margin gate (ADR-0026) + +Given a candidate set `C ⊆ I` and the region `R`, compute the +ranked list + +```text +ranked(C, R) = sort_descending_by_score_then_index([ + (t, σ_dest(t, R)) for t in C +]) +``` + +with stable tie-break by index (strict `<` on integer index, never +floating-point comparison on score). Let `(t₁, σ₁), (t₂, σ₂), …` be +the ordered list. The margin verdict is + +```text +admit_margin(C, R, δ) ⇔ + |C| = 1 ∧ σ₁ > 0 + ∨ |C| ≥ 2 ∧ σ₁ > 0 ∧ (σ₁ − σ₂) ≥ δ +``` + +where `δ ∈ ℝ₊` is the `admissibility_margin`. Default `δ = 0.4`. + +The walk admits the top-ranked candidate `t₁` iff +`admit_margin(C, R, δ)` holds; otherwise the inner-loop raises +`InnerLoopExhaustion` with the full ranked list as evidence. + +Modules: +`generate/admissibility.py::rank_candidates_by_blade`, +`generate/admissibility.py::check_margin` (returns typed +`MarginVerdict`). + +**Why δ on the difference, not τ on the absolute score.** Under +the Cl(4,1) Lorentzian signature, self-`cga_inner` is signed: 23 of +85 tokens in `en_core_cognition_v1` have `σ_dest(t, V_t) < 0`. No +scalar `τ` separates admissible from inadmissible across the +corpus (`separation_quality < 0.8` at every probed `τ`, +characterized in `evals/forward_semantic_control/results/phase4_characterization_combined.json`). +A margin gate is scale-invariant under per-blade norm variation; +it survives where the static threshold fails. + +#### 5. Honest refusal (ADR-0024 Phase 2) + +When inner-loop admissibility leaves no admissible destination, or +when rotor-side admissibility refuses every candidate, the walk +raises `InnerLoopExhaustion`, a typed subclass of `ValueError` +carrying: + +```text +InnerLoopExhaustion( + reason : RefusalReason, + region_label : str, + step_index : int, # -1 = pre-walk empty intersection + # ≥0 = in-walk per-step exhaustion + rejected_attempts : tuple[(int, str, float), ...], +) +``` + +`RefusalReason` is an enum with stable string values: + +| Value | Meaning | +|---|---| +| `"inner_loop_exhaustion"` | Destination-side: no candidate passed `admit_threshold` / `admit_margin`. | +| `"rotor_rejection"` | Rotor-side: candidate passed destination admit, but `admit_rotor` returned `False`. | + +The reason value is folded into `compute_trace_hash` payload only +when non-empty, preserving byte-identical hashes for non-refused +turns (back-compat invariant) while making refusals themselves +replay-deterministic. + +Module: `generate/exhaustion.py`. Trace fold: +`core/cognition/trace.py::compute_trace_hash`. + +#### 6. Composition order at the generation seam + +The full per-step admissibility predicate is the conjunction: + +```text +admit_step(t, R, F, τ, δ) = + t ∈ I (region intersection, ADR-0023) + ∧ admit_destination(t, R, τ, δ) (destination, ADR-0024 / 0026) + ∧ admit_rotor(rotor_for(t), F, R.Φ) (rotor, ADR-0025) +``` + +where `admit_destination` is `admit_threshold` in threshold mode and +`admit_margin` in margin mode. The conjunction is evaluated +left-to-right and short-circuits at the first failing clause; the +clause that failed is encoded in the `RefusalReason` carried by any +subsequent `InnerLoopExhaustion`. + +Module: `generate/stream.py::generate` (the seam itself). + +#### 7. Replay determinism contract + +For any fixed `(state, vocab, persona, region, mode, τ, δ)`, the +output `GenerationResult` is bit-identical across reruns, including +the `admissibility_trace` and (when refused) the `RefusalReason`, +`region_label`, `step_index`, and `rejected_attempts` carried by +`InnerLoopExhaustion`. + +This contract is exercised by: + +| Lane | Replay tests | File | +|---|---|---| +| Inner-loop admit | 5-rerun byte identity | `tests/test_inner_loop_admissibility.py` | +| Margin gate | 3-rerun replay | `tests/test_margin_admissibility.py` | +| Rotor admissibility | 5-rerun admit + 5-rerun refuse | `tests/test_rotor_admissibility.py` | +| Phase 5 stratified | 3-rerun across 20 cases | `tests/test_phase5_corpus.py::TestReplayDeterminism` | +| Phase 6 demo C1 | 5-rerun on 8 cases, baseline + CORE | `tests/test_phase6_demo.py::TestC1ReplayDeterminism` | + +#### 8. Verification invariants added by the chain + +| Invariant | Expression | Tolerance | Test file | +|---|---|---|---| +| Refusal is typed | `isinstance(exc, ValueError) ∧ isinstance(exc, InnerLoopExhaustion)` | exact | `test_refusal_contract.py` | +| Reason is enumerated | `exc.reason ∈ RefusalReason` | exact | `test_refusal_contract.py` | +| Margin tie-break is stable | `rank_candidates_by_blade` returns deterministic ordering under exact tie | exact | `test_margin_admissibility.py` | +| Rotor closure preserved | `versor_condition(versor_apply(V, F)) < 1e-6` on admitted rotors | < 1e-6 | `test_rotor_admissibility.py` | +| Mechanism isolated (margin) | per-family `pass_rate_margin = 1.0` across 5 families | exact | `test_phase5_corpus.py` | +| Three-condition demo passes | `c1_pass ∧ c2_pass ∧ c3_pass` | exact | `test_phase6_demo.py` | + +These are structural contracts, not regression tests. A failing +invariant means the chain is broken, not the corpus. + +--- + ### X. Verification Invariants (The Implementation Gate) These are testable predicates. Every invariant has a corresponding test in `tests/`. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 4b3f93ff..a8aea7cf 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -24,6 +24,68 @@ ADRs record significant architectural decisions: what was decided, why, what alt | [ADR-0012](ADR-0012-core-ingest-governance-layer.md) | `core_ingest` Governance Layer | Accepted | | [ADR-0013](ADR-0013-sensorium-multimodal-protocol.md) | `sensorium/` Multimodal Protocol Layer | Accepted | | [ADR-0014](ADR-0014-train-learning-loop.md) | `train/` Learning Loop | Accepted (Stub) | +| [ADR-0015](ADR-0015-language-packs-and-holonomy-resonance.md) | Language Packs as Compiled Linguistic Manifolds | Accepted | +| [ADR-0016](ADR-0016-capability-roadmap.md) | Capability Roadmap and Eval Methodology | Accepted | +| [ADR-0017](ADR-0017-agency-scope.md) | Agency Scope: Responsive-with-Axiology | Accepted | +| [ADR-0018](ADR-0018-tool-use-scope.md) | Tool Use Scope: Typed Deterministic Operators | Accepted | +| [ADR-0019](ADR-0019-exact-vault-recall-acceleration.md) | Exact Vault Recall Acceleration | Accepted | +| [ADR-0020](ADR-0020-phase5-rust-parity-sequencing.md) | Phase 5 / Rust Parity Sequencing | Accepted (2026-05-16) | +| [ADR-0021](ADR-0021-epistemic-grade-policy.md) | Epistemic Grade Policy | Accepted | +| [ADR-0022](ADR-0022-forward-semantic-control.md) | Forward Semantic Control | Accepted (2026-05-17) | +| [ADR-0023](ADR-0023-forward-semantic-control-proof.md) | Forward Semantic Control: Proof Evidence | Accepted | +| [ADR-0024](ADR-0024-inner-loop-admissibility.md) | Inner-Loop Per-Rotor Admissibility | Accepted | +| [ADR-0025](ADR-0025-rotor-frame-admissibility-design-note.md) | Rotor / Frame Admissibility | Accepted (2026-05-17) | +| [ADR-0026](ADR-0026-ranked-admissibility-with-margin.md) | Ranked Admissibility with Margin | Accepted (2026-05-17) | + +--- + +## ADR-0024 chain — Forward Semantic Control closure + +ADR-0022 through ADR-0026 form a single coherent chain that closes +forward semantic control as a non-stochastic, replay-deterministic, +trace-evidenced mechanism. Read in order: + +1. **ADR-0022** — Forward Semantic Control. Establishes the + `AdmissibilityRegion` data structure (allowed indices, relation + blade, frame versor) and the contract that a region restricts the + admissible token set at generation time. +2. **ADR-0023** — Proof Evidence. Boundary-only proof that the + admissibility region is honored at the *region intersection* level + but not yet at the destination-token level. +3. **ADR-0024** — Inner-Loop Per-Rotor Admissibility. Adds the + destination-side check: each per-step selection is re-evaluated by + `cga_inner(versor(candidate), relation_blade) > threshold`, with + honest refusal (`InnerLoopExhaustion`) when every admissible + candidate is rejected. Six phases of implementation evidence + (Phases 1-6) plus typed `RefusalReason` taxonomy. +4. **ADR-0025** — Rotor / Frame Admissibility. Adds the rotor-side + check: when a region carries a `frame_versor`, the rotor's effect + on the field state (`versor_apply(V, F)`) is additionally checked + against the frame for positivity in CGA inner product. Lives in + `generate/rotor_admissibility.py` — a sibling-but-separate module, + not in `algebra/versor.py` (would couple algebra to pack state) + and not in `field/propagate.py` (forbidden normalization site). +5. **ADR-0026** — Ranked Admissibility with Margin. Replaces static + threshold tuning with a scale-invariant margin gate: admit iff + the top blade-score exceeds the second by ≥ δ. Defaults to + δ = 0.4. Falsifiable; characterization documented in + `docs/evals/phase5_stratified_findings.md`. + +Implementation evidence: + +| Phase | Commit | Tests | +|---|---|---| +| Phase 1 — pack-grounded fixtures | `3940290` | (rewrites) | +| Phase 2 — typed refusals + trace fold | `310793a` | +10 | +| Phase 3 — ranked-with-margin gate | `639e107` | +13 | +| Phase 4 — rotor / frame admissibility | `542e13d` | +11 | +| Phase 5 — stratified mechanism-isolation | `b664984` | +20 | +| Phase 6 — comparative demo vs baseline | `a076506` | +17 | +| CLI surface (suite aliases + `core demo`) | `36aad75` | +14 | + +Runtime contracts for the chain are pinned in +[`docs/runtime_contracts.md`](../runtime_contracts.md) (Refusal +contract, Margin contract, Rotor admissibility contract sections). --- diff --git a/docs/runtime_contracts.md b/docs/runtime_contracts.md index d5e0bd83..b3e4da82 100644 --- a/docs/runtime_contracts.md +++ b/docs/runtime_contracts.md @@ -94,6 +94,55 @@ future ADR can wire materialisation (e.g. propagate the typed exception to `ChatResponse.refusal_reason` or catch at the pipeline seam) without re-deriving the contract. +### Ranked-with-margin contract (ADR-0026 / Phase 3) + +The static `admissibility_threshold` documented above (ADR-0024 Phase 2) +is supplemented by a scale-invariant margin gate (ADR-0026 Phase 3). +The runtime selects mode via `RuntimeConfig.admissibility_mode`: + +```text +RuntimeConfig.admissibility_mode : "threshold" | "margin" (default: "threshold") +RuntimeConfig.admissibility_margin : float (default: 0.4) +``` + +In **threshold mode** (back-compat, ADR-0024): + +```text +admit iff cga_inner(versor(candidate), relation_blade) > admissibility_threshold +``` + +In **margin mode** (ADR-0026): + +```text +rank candidates by cga_inner(versor(candidate), relation_blade), descending, + stable tie-break by candidate index +admit iff (single candidate) + or (score(top) > 0 AND score(top) - score(second) >= admissibility_margin) +``` + +`generate.admissibility.rank_candidates_by_blade` returns the ranked +list with deterministic tie-break, and `generate.admissibility.check_margin` +returns a typed `MarginVerdict` (`admitted`, `top`, `second`, `gap`, +`reason`). The selection invariant is that the *score difference* is +the gate, not the absolute score — making the gate robust to per-blade +norm variation that defeated static threshold tuning on the +Phase 4 characterization corpus (see +`docs/evals/phase5_stratified_findings.md`). + +Refusal in margin mode is materialised through the same +`InnerLoopExhaustion` mechanism as threshold mode, with +`RefusalReason.INNER_LOOP_EXHAUSTION` carrying the full ranked +candidate list as evidence so the failure mode is "no candidate has +margin over its successor" rather than "no candidate exceeded +threshold T." + +The default δ = 0.4 was selected from the minimum observed margin in +the Phase 3 v2 corpus (0.456) and is *falsifiable*: any case +surfacing a blade-gap below δ where margin-mode refusal is the wrong +behavior must be reported as an ADR-0026 falsification rather than +silently patched per case. Phase 5's 20-case stratified corpus does +not falsify δ = 0.4. + ### Rotor admissibility contract (ADR-0025 / Phase 4) The destination-side admissibility documented above (token-side blade