docs: ADR-0024 chain coverage across README, ADR index, contracts, papers

Patent-grade precision pass over the doc surface so every claim
about the Forward Semantic Control chain is backed by a file path,
test count, or commit hash.

Updates by file:

README.md
  - Modernize Quick Start: add `core test --suite adr-0024`,
    `core demo phase6 / phase5 / all / list-results`, full CLI map.
  - New "Forward Semantic Control — The ADR-0024 Chain" section
    with layer/ADR mapping and CI-enforced C1/C2/C3 claims table.
  - Cross-links to runtime_contracts.md, phase5_stratified_findings,
    phase6_comparative_demo, and the central results directory.

docs/decisions/README.md
  - Index was stale at ADR-0014.  Add ADR-0015 through ADR-0026
    with accurate Accepted statuses.
  - New "ADR-0024 chain — Forward Semantic Control closure" section
    laying out the five-ADR / six-commit dependency order with test
    counts per phase.

docs/runtime_contracts.md
  - Add "Ranked-with-margin contract (ADR-0026 / Phase 3)" section
    between the existing Phase 2 refusal and Phase 4 rotor sections.
  - Documents threshold-mode vs margin-mode behaviour, δ = 0.4
    default, falsifiability gate, and Cl(4,1) signature motivation.

docs/PROGRESS.md
  - Add naming-note disambiguation: capability-roadmap "Phase N"
    vs ADR-0024 chain "Phase N" are distinct.
  - New top-of-document "ADR-0024 Chain — Forward Semantic Control
    Closure" section with per-phase commit + test-count table and
    a single-command verification path.

docs/Whitepaper.md
  - New Section XII "Forward Semantic Control — Generation Without
    Sampling" before Extensions.  Five-component description of the
    mechanism (region, intersection, destination check, rotor check,
    margin gate) with explicit "what a sampling LLM cannot exhibit"
    contrast.  Existing Section XII renumbered to XIII.

docs/Yellowpaper.md
  - New Section IX-B "Forward Semantic Control — Formal Admissibility
    Specification" with eight subsections covering:
      1. AdmissibilityRegion typed triple (I, B, Φ)
      2. Destination-side admissibility (σ_dest, admit_threshold)
      3. Rotor-side admissibility (σ_rotor, admit_rotor)
      4. Ranked-with-margin gate (admit_margin, deterministic
         tie-break by index, default δ = 0.4)
      5. Honest refusal (InnerLoopExhaustion typed evidence,
         RefusalReason enum, trace fold)
      6. Composition order at the generation seam
         (admit_step = intersection ∧ destination ∧ rotor)
      7. Replay determinism contract (5 test lanes pinning byte
         identity across reruns)
      8. Verification invariants table (6 new structural contracts)
  - Patent-grade: every predicate is named, every module is path-
    referenced, every test is file-referenced, the load-bearing
    architectural placement decision (rotor admissibility lives in
    generate/, NOT algebra/, NOT field/) is stated by name with
    its rejection reasoning.

No code changes; tests untouched (1099 passed, 2 skipped baseline
from commit 36aad75 still holds).
This commit is contained in:
Shay 2026-05-17 16:28:54 -07:00
parent 36aad75202
commit 3005cd6f9a
6 changed files with 529 additions and 3 deletions

View file

@ -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
```

View file

@ -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

View file

@ -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.

View file

@ -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/`.

View file

@ -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).
---

View file

@ -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