diff --git a/core/physics/chiral_gate.py b/core/physics/chiral_gate.py new file mode 100644 index 00000000..a877491a --- /dev/null +++ b/core/physics/chiral_gate.py @@ -0,0 +1,122 @@ +"""Chiral orientation gate — sgn(Q) preservation (ADR-0241 §2.4C / core_ha §5.2). + +The topological spinor charge Q = ⟨ψ I₅ ψ̃⟩₀ anchors the global orientation of +the cognitive manifold: "preserving the sign and magnitude of Q_top across all +scales … prevent[s] mirror-image inversions" (ADR-0241 §2.4C), and the +core_ha unification memo pins the invariant safeguard as sgn(Q) = const +(§5.2). The substrate already carries a verified non-vacuous READOUT +(:meth:`WaveManifold.chiral_charge` — conserved exactly under ψ → Rψ), but +its only consumer took ``abs(...)``, discarding the sign. This module is the +missing ENFORCEMENT: latch the orientation on the first non-vacuous reading, +fail closed on any materially re-emerging flip. + +Honesty pins (#19 family): + * Even field-states have Q structurally ~0 — no orientation is defined and + the gate never fabricates one (``vacuous``; the retired grade-5 gate is + not revived). Serve-path even versors therefore never latch: wiring this + gate into GoldTether is behaviorally inert for today's serve fields. + * |Q| may legitimately pass below the floor (superposition mixtures); the + latch persists and only a materially re-emerging opposite sign violates. + +Tier-1 module: imported by ``goldtether`` (sanctioned serve substrate). +Algebra-native only; no teaching/vault imports. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Literal + +import numpy as np + +from core.physics.wave_manifold import WaveManifold + +#: Below this |Q|, orientation is undefined (even-field honesty; #19 family). +DEFAULT_Q_FLOOR = 0.1 + + +class ChiralOrientationError(ValueError): + """Fail-closed chiral orientation violation (mirror inversion). + + A sign flip of Q is unreachable under sanctioned rotor transport (Q is + strictly conserved under left unitary multiplication), so a materially + flipped sign is evidence of a non-unitary / corrupting transition and the + gate refuses it rather than averaging it away. + """ + + def __init__(self, reason: str, **disclosure: Any) -> None: + self.reason = reason + self.disclosure = dict(disclosure) + super().__init__(f"chiral_orientation refused [{reason}]: {self.disclosure}") + + +@dataclass(frozen=True, slots=True) +class ChiralObservation: + """One gate reading with an honest epistemic verdict.""" + + q: float + sign: int # −1 / +1, or 0 when |q| < q_floor (no orientation defined) + latched_sign: int # 0 until the first non-vacuous reading + verdict: Literal["vacuous", "latched", "conserved"] + + def as_dict(self) -> dict[str, Any]: + return { + "q": self.q, + "sign": self.sign, + "latched_sign": self.latched_sign, + "verdict": self.verdict, + } + + +@dataclass(slots=True) +class ChiralOrientationGate: + """Monitor-style latch for the global chiral orientation sign. + + * First reading with |Q| ≥ ``q_floor`` latches ``sgn(Q)``. + * Later readings: same sign → ``conserved``; |Q| < floor → ``vacuous`` + (latch persists); opposite sign at material |Q| → raises + :class:`ChiralOrientationError` (fail-closed; never averages). + """ + + q_floor: float = DEFAULT_Q_FLOOR + latched_sign: int = 0 + _wave: WaveManifold = field(default_factory=WaveManifold) + + def observe(self, psi: np.ndarray) -> ChiralObservation: + """Read Q from the field and enforce sign preservation.""" + return self.observe_q(self._wave.chiral_charge(psi)) + + def observe_q(self, q: float) -> ChiralObservation: + """Enforce sign preservation on a precomputed charge readout.""" + q = float(q) + sign = 0 if abs(q) < self.q_floor else (1 if q > 0.0 else -1) + + if sign == 0: + # No orientation defined at this reading; latch (if any) persists. + return ChiralObservation( + q=q, sign=0, latched_sign=self.latched_sign, verdict="vacuous" + ) + if self.latched_sign == 0: + self.latched_sign = sign + return ChiralObservation( + q=q, sign=sign, latched_sign=sign, verdict="latched" + ) + if sign == self.latched_sign: + return ChiralObservation( + q=q, sign=sign, latched_sign=self.latched_sign, verdict="conserved" + ) + raise ChiralOrientationError( + "orientation_flip", + q=q, + sign=sign, + latched_sign=self.latched_sign, + q_floor=self.q_floor, + ) + + +__all__ = [ + "DEFAULT_Q_FLOOR", + "ChiralObservation", + "ChiralOrientationError", + "ChiralOrientationGate", +] diff --git a/core/physics/goldtether.py b/core/physics/goldtether.py index f456a3fc..9e62a3f2 100644 --- a/core/physics/goldtether.py +++ b/core/physics/goldtether.py @@ -32,6 +32,7 @@ from algebra.backend import geometric_product, versor_condition from algebra.cl41 import N_COMPONENTS, reverse from algebra.rotor import rotor_power, word_transition_rotor from algebra.versor import versor_unit_residual +from core.physics.chiral_gate import ChiralOrientationGate from core.physics.wave_manifold import WaveManifold _CLOSURE_TOL = 1e-6 @@ -152,6 +153,13 @@ class GoldTetherMonitor: r_floor: float = 0.1 r_critical: float = 1.0 gold_invariants: list = field(default_factory=_primal_gold_invariants, compare=False) + # Chiral orientation enforcement (ADR-0241 §2.4C / core_ha §5.2): + # sgn(Q) latches on the first non-vacuous spinor reading and a materially + # re-emerging flip fails closed. Even serve field-states are vacuous + # (Q ≈ 0), so the gate never latches on today's serve path — inert there. + chiral_gate: "ChiralOrientationGate" = field( + default_factory=lambda: ChiralOrientationGate(), compare=False + ) @property def supervised_autonomy_level(self) -> float: @@ -227,7 +235,12 @@ class GoldTetherMonitor: # path can move the residual without a second API. wave = WaveManifold() drift = wave.measure_unitary_residual(F_arr) - chiral = abs(float(wave.chiral_charge(F_arr))) + # Signed charge feeds the fail-closed orientation gate (sgn(Q)=const, + # ADR-0241 §2.4C / core_ha §5.2). The residual term keeps its + # magnitude-only semantics unchanged below. + q_signed = float(wave.chiral_charge(F_arr)) + self.chiral_gate.observe_q(q_signed) + chiral = abs(q_signed) drift_term = ( (drift + chiral) / self.epsilon_drift if self.epsilon_drift > 0.0 diff --git a/docs/research/adr-0241-0242-blueprint-integration-plan.md b/docs/research/adr-0241-0242-blueprint-integration-plan.md index 7a960d05..d54f4476 100644 --- a/docs/research/adr-0241-0242-blueprint-integration-plan.md +++ b/docs/research/adr-0241-0242-blueprint-integration-plan.md @@ -52,7 +52,7 @@ The recurring pattern across all four docs: the blueprints were written when the ### 🔨 MISSING PIECE — capability the vision specifies but the code does **not** yet enforce; **build anew** so we don't go backwards | Missing invariant | Current state | Build | |---|---|---| -| **Chiral SIGN-preservation gate** (ADR-0241 §2.4C + core_ha §5.2: `sgn(∫⟨ψIψ̃⟩₀)=const`, mirror-inversion protection) | `chiral_charge` is a verified non-vacuous **readout**, but its only consumer (`goldtether.py:230`) takes `abs(...)` — **discarding the sign** — and it's ~0 on serve-path even-versors. No fail-closed `sgn(Q)=const` gate exists. | A `chiral_orientation_gate` that latches the initial sign and fails closed on flip during transport; wire the *signed* charge (not `abs`) where a non-vacuous spinor path exists. Preserves the "topologically secure alignment" the blueprint promises. | +| **Chiral SIGN-preservation gate** (ADR-0241 §2.4C + core_ha §5.2: `sgn(∫⟨ψIψ̃⟩₀)=const`, mirror-inversion protection) | ✅ **BUILT** (`feat/chiral-sign-preservation-gate`, stacked on PR #40): `core/physics/chiral_gate.py` — `ChiralOrientationGate` latches `sgn(Q)` on the first non-vacuous reading, fails closed (`ChiralOrientationError`) on a materially re-emerging flip; wired at the enforcement point (`goldtether_residual` now feeds the *signed* charge to the gate; residual keeps magnitude semantics byte-identical). Even serve fields stay vacuous → inert on today's serve path (no #19 revival). Pins: `tests/test_chiral_orientation_gate.py` (7). | — (was: latch initial sign + fail closed on flip; wire signed charge) | | **CRDT delta-sync semilattice** (core_ha §2/§3 tombstone → "commutative/associative/idempotent Delta-CRDT in `core/sync/`") | `core/sync/` is a journal/object-store (no `merge`/idempotent/semilattice semantics found). Determinism today = bit-exact `array_codec` + single-writer `VaultStore` (Shape B+), **not** multi-writer CRDT merge. | Decide: either build the CRDT (needed for genuine multi-agent/multi-writer "one continuous life") **or** correct the doc to state determinism is single-writer bit-exact. Don't leave the claim floating. | ### ⛔ SCRAP / DO-NOT-BUILD — correctly rejected over-claims @@ -83,7 +83,7 @@ The recurring pattern across all four docs: the blueprints were written when the 1. **DONE** — Finding #2 serve-boundary reconciliation (de-barrel + transitive guard + A-04/ledger correction). Green. 2. **Next fix-forward (LOW, batch):** downgrade anyon ledger row; `EpistemicStatus` boundary note (or relocate to shared kernel); Fibonacci `minimizer` docstring; `ClosureViolationException` naming note. -3. **Build-anew (capability):** chiral **sign-preservation gate** (§5.2) — the one true missing safeguard; TDD a `sgn(Q)=const` fail-closed gate + signed wiring. +3. **DONE** — chiral **sign-preservation gate** (§5.2) built TDD (RED→GREEN) on `feat/chiral-sign-preservation-gate`: gate primitive + goldtether wiring + 7 pins; 99 goldtether-consumer tests green. 4. **Decision + build/doc:** CRDT-vs-bit-exact determinism story for `core/sync` (affects multi-agent "one continuous life"). 5. **Get ADR-0242 memo export** → close its fidelity slice (evidence-gated-optimization vs the cert impl; confirm no further deviations). 6. Staged/gated items (Rust parity D9, sensorium compilers, continuous integrals) remain post-Accept backlog. diff --git a/docs/research/third-door-blueprint-fidelity.md b/docs/research/third-door-blueprint-fidelity.md index 97622888..88152d1d 100644 --- a/docs/research/third-door-blueprint-fidelity.md +++ b/docs/research/third-door-blueprint-fidelity.md @@ -309,6 +309,7 @@ PY | Physics Cl(4,1) via `algebra.backend` (P11a) | 🟢 wave/goldtether/trajectory/procrustes/surprise/vault/packing; AST pin; Rust when `CORE_BACKEND=rust` | | Contemplation Trace A SPECULATIVE holographic seal (P9) | 🟢 `core/contemplation/wave_seam.py` (hypothesis vs COHERENT evidence) | | Energy boundary + multi-scale τ (P10 Trace B) | 🟢 `wave_energy_boundary` (wave residual → energy/trajectory; τ_n=F_n·τ_0; E0–E1 crystallization) | +| Chiral orientation sign-gate \(\mathrm{sgn}(Q)=\text{const}\) (§2.4C / core_ha §5.2 mirror-inversion safeguard) | 🟢 `chiral_gate.ChiralOrientationGate` — latch on first non-vacuous \(Q\), fail-closed `ChiralOrientationError` on flip; wired via signed charge in `goldtether_residual` (residual value unchanged; even fields vacuous → serve-inert, no #19 revival). Pins: `test_chiral_orientation_gate.py` | ### Subsumption map (Slice 2–3) | Operator | Delegation | diff --git a/tests/test_chiral_orientation_gate.py b/tests/test_chiral_orientation_gate.py new file mode 100644 index 00000000..e7c472b0 --- /dev/null +++ b/tests/test_chiral_orientation_gate.py @@ -0,0 +1,170 @@ +"""ADR-0241 §2.4C / core_ha §5.2 — chiral orientation gate (sgn(Q) = const). + +RED until core/physics/chiral_gate.py lands. + +The blueprint's mirror-inversion safeguard: the global chiral anomaly sign of +the spinor field must stay constant — "preserving the sign … anchors the +global orientation of the cognitive manifold, preventing mirror-image +inversions." The substrate has a verified non-vacuous READOUT +(WaveManifold.chiral_charge; Q = ⟨ψ I₅ ψ̃⟩₀), but its only consumer +(GoldTetherMonitor.goldtether_residual) takes abs(), discarding the sign — +so orientation was measured, never enforced (integration-plan missing piece). + +Fixture algebra (grade-1 v, central I₅ with I₅² = −1, Ĩ₅ = I₅): + Q(v + v·I₅) = −2 v² and Q(v − v·I₅) = +2 v² +so the mirror pair is a genuine sign flip — and it is unreachable by any +rotor transport (Q is strictly conserved under ψ → Rψ), i.e. it models +exactly the corruption class the gate exists to catch. +""" + +from __future__ import annotations + +import numpy as np +import pytest + +from algebra.cl41 import N_COMPONENTS, geometric_product +from algebra.rotor import make_rotor_from_angle +from core.physics.chiral_gate import ( + ChiralObservation, + ChiralOrientationError, + ChiralOrientationGate, +) +from core.physics.goldtether import GoldTetherMonitor +from core.physics.wave_manifold import WaveManifold, _I5 + + +def _e(i: int, val: float = 1.0) -> np.ndarray: + v = np.zeros(N_COMPONENTS, dtype=np.float64) + v[i] = val + return v + + +def _spinor_pair() -> tuple[np.ndarray, np.ndarray]: + """(ψ, mirror-ψ) with Q(ψ) = −2.5 and Q(mirror) = +2.5.""" + v = _e(1) + 0.5 * _e(3) + psi = v + geometric_product(v, _I5) + mirror = v - geometric_product(v, _I5) + return psi, mirror + + +# --- Latch + conservation ---------------------------------------------------- + + +def test_gate_latches_then_reports_conserved_under_left_spinor_transport(): + gate = ChiralOrientationGate() + M = WaveManifold() + psi, _ = _spinor_pair() + + first = gate.observe(psi) + assert isinstance(first, ChiralObservation) + assert first.verdict == "latched" + assert first.sign == -1 + assert first.latched_sign == -1 + assert abs(first.q) > 0.1 + + R = make_rotor_from_angle(0.4, bivector_idx=7) + psi_next = M.left_spinor_step(psi, R) + second = gate.observe(psi_next) + assert second.verdict == "conserved" + assert second.sign == -1 + assert abs(second.q - first.q) < 1e-9 # Q strictly conserved + + +def test_gate_fails_closed_on_mirror_inversion(): + gate = ChiralOrientationGate() + psi, mirror = _spinor_pair() + gate.observe(psi) + + with pytest.raises(ChiralOrientationError, match="orientation_flip"): + gate.observe(mirror) + + +# --- Even-field honesty (#19 family) ------------------------------------------ + + +def test_gate_honest_on_even_versor_never_latches(): + """Even field-states have Q ≈ 0: no orientation is defined, the gate must + not fabricate one, and repeated vacuous observations are not violations + (does not revive the retired #19 gate).""" + gate = ChiralOrientationGate() + for angle, plane in ((0.9, 11), (0.3, 6), (0.55, 8)): + obs = gate.observe(make_rotor_from_angle(angle, bivector_idx=plane)) + assert obs.verdict == "vacuous" + assert obs.sign == 0 + assert obs.latched_sign == 0 + + +def test_gate_vacuous_after_latch_is_not_violation_but_flip_reemergence_is(): + """|Q| may legitimately pass below the floor (superposition states); the + latch persists, and a materially re-emerging FLIPPED sign still fails.""" + gate = ChiralOrientationGate() + psi, mirror = _spinor_pair() + + gate.observe(psi) + even = make_rotor_from_angle(0.7, bivector_idx=9) + mid = gate.observe(even) + assert mid.verdict == "vacuous" + assert mid.latched_sign == -1 # latch persists + + same = gate.observe(psi) + assert same.verdict == "conserved" + + with pytest.raises(ChiralOrientationError, match="orientation_flip"): + gate.observe(mirror) + + +# --- Determinism --------------------------------------------------------------- + + +def test_gate_determinism(): + psi, _ = _spinor_pair() + even = make_rotor_from_angle(0.2, bivector_idx=6) + seq = (psi, even, psi) + + def run() -> tuple[str, ...]: + gate = ChiralOrientationGate() + return tuple(gate.observe(x).verdict for x in seq) + + assert run() == run() == ("latched", "vacuous", "conserved") + + +# --- GoldTether wiring (the enforcement point) --------------------------------- + + +def test_goldtether_residual_latches_and_fails_closed_on_flip(): + monitor = GoldTetherMonitor() + psi, mirror = _spinor_pair() + + r = monitor.goldtether_residual(psi) + assert np.isfinite(r) + assert monitor.chiral_gate.latched_sign == -1 + + with pytest.raises(ChiralOrientationError, match="orientation_flip"): + monitor.goldtether_residual(mirror) + + +def test_goldtether_residual_unchanged_and_inert_on_even_serve_fields(): + """Serve-path even field-states are vacuous: the gate never latches and + the residual VALUE is byte-identical to the pre-gate computation + (drift + abs(chiral) magnitude semantics untouched).""" + monitor = GoldTetherMonitor() + wave = WaveManifold() + F = make_rotor_from_angle(0.42, bivector_idx=8) + + r = monitor.goldtether_residual(F) + assert monitor.chiral_gate.latched_sign == 0 + + # Replicate the pre-gate residual formula exactly (ADR-0238 §2.3): the + # gate must not perturb the value. Fresh monitors seed primal gold + # invariants, so the geometric term is live. + drift = wave.measure_unitary_residual(F) + chiral = abs(float(wave.chiral_charge(F))) + drift_term = (drift + chiral) / monitor.epsilon_drift + scale = float(np.linalg.norm(F)) + min_dist = min( + float(np.linalg.norm(F - np.asarray(inv, dtype=np.float64))) + for inv in monitor.gold_invariants + ) + geo_term = min_dist / scale + expected = monitor.w_drift * drift_term + (1.0 - monitor.w_drift) * geo_term + assert abs(r - expected) < 1e-15