From db6430ed4eedb9aa694d830ccadfec6a5c10ebd8 Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 14 Jul 2026 20:01:18 -0700 Subject: [PATCH] =?UTF-8?q?feat(adr-0242):=20macro-phase=20V2=E2=80=93V5?= =?UTF-8?q?=20+=20sensorium=20feed=20(Drive=20gap=20close)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parallel implementation of remaining Drive ADR-0242 vectors on PR #38: D3 V2 — multi_scale_energy: E_n(t)=E0·exp(-age/(F_n·τ0)), dyadic baseline, comparative helpers; not production FieldEnergyOperator default. D5 V4 — fibonacci_word_schedule: W0=B,W1=A,W_{n+1}=W_n W_{n-1}; telemetry only. D6 V5 — algebra/topological_reasoning quarantine + production AST pin. D7 — sensorium_wave_feed: packet→ψ, superpose, ρ via WaveManifold only. Serve quarantine extended for all new modules. Fidelity + ADR-0242 + runtime_contracts honesty pass. Suite: 118 related tests green. Single macro-phase commit per plan policy (no micro-PRs). --- algebra/topological_reasoning/README.md | 82 +++++++ algebra/topological_reasoning/__init__.py | 21 ++ core/physics/__init__.py | 12 + core/physics/fibonacci_word_schedule.py | 106 +++++++++ core/physics/multi_scale_energy.py | 178 ++++++++++++++ core/physics/sensorium_wave_feed.py | 141 +++++++++++ .../ADR-0242-atlas-packing-and-fibonacci.md | 28 ++- .../research/third-door-blueprint-fidelity.md | 7 +- docs/specs/runtime_contracts.md | 8 +- tests/test_adr_0241_sensorium_wave_feed.py | 222 ++++++++++++++++++ tests/test_adr_0242_fibonacci_word.py | 136 +++++++++++ tests/test_adr_0242_multi_scale_energy.py | 167 +++++++++++++ tests/test_adr_0242_topological_quarantine.py | 124 ++++++++++ tests/test_third_door_cohesion.py | 6 + 14 files changed, 1221 insertions(+), 17 deletions(-) create mode 100644 algebra/topological_reasoning/README.md create mode 100644 algebra/topological_reasoning/__init__.py create mode 100644 core/physics/fibonacci_word_schedule.py create mode 100644 core/physics/multi_scale_energy.py create mode 100644 core/physics/sensorium_wave_feed.py create mode 100644 tests/test_adr_0241_sensorium_wave_feed.py create mode 100644 tests/test_adr_0242_fibonacci_word.py create mode 100644 tests/test_adr_0242_multi_scale_energy.py create mode 100644 tests/test_adr_0242_topological_quarantine.py diff --git a/algebra/topological_reasoning/README.md b/algebra/topological_reasoning/README.md new file mode 100644 index 00000000..c9507e2c --- /dev/null +++ b/algebra/topological_reasoning/README.md @@ -0,0 +1,82 @@ +# Topological Reasoning — ADR-0242 Vector 5 (D6) Research Quarantine + +**Status**: 🔴 RESEARCH ONLY — blocked from production +**Authority**: ADR-0242 Vector 5 (topological anyon / braid holonomy) +**Related**: `docs/adr/ADR-0242-atlas-packing-and-fibonacci.md`, +`docs/analysis/fibonacci_applications_in_core_substrate.md` §2.2 + +--- + +## Purpose + +Isolated study surface for Fibonacci anyon fusion and braid holonomy as a +**topological composition research program**. The canonical fusion rule under +study is: + +\[ +\tau \otimes \tau = \mathbf{1} \oplus \tau +\] + +This package exists so research stubs, notes, and future proof-carrying +algebra can land **without** contaminating the live cognitive path: + +```text +listen → comprehend → recall → think → articulate → learn → replay +``` + +## Hard quarantine (do not violate) + +Until algebraic **and** numerical proofs exist, and until an explicit ADR +promotion gate is Accepted by human review, this package is **BLOCKED** from: + +| Surface | Rule | +|---------|------| +| Production runtime | No imports from serve / hot path | +| `chat/` | Not for import by chat or `chat/runtime.py` | +| Serve / FFI | Must not enter serve or FFI bindings | +| `core/physics/` | Not a production physics operator | +| `generate/` | Not for articulation / planner / cognitive turns | +| `vault/` | Not for vault standing, seal, or COHERENT promotion | +| `teaching/` | Not for reviewed teaching or pack mutation | +| GoldTether / κ paths | Not for production residual / κ optimization | +| `algebra` public surface | Not re-exported from `algebra/__init__.py` | + +Architectural pin: `tests/test_adr_0242_topological_quarantine.py` scans +production packages for any import of `topological_reasoning` and must find +none. + +## Sovereignty (ADR-0242) + +Fibonacci / topological operators may **never** dictate proposition truth, +safety policy, identity, or authorize autonomous COHERENT promotion. Active +reasoning remains governed by versor closure, exact CRDT recall, and +human-gated review. + +## What is allowed here + +- Research constants and docstring contracts (e.g. fusion rule labels) +- Future proof sketches, numerical experiments under tests/evals only when + explicitly gated +- Documentation of open questions and proof obligations + +## What is not allowed here + +- Production logic wired into cognition, serve, or vault truth +- Stochastic / approximate substitutes for exact CGA recall +- Hidden normalization or drift repair outside owned algebra boundaries +- Silent promotion of research results into COHERENT standing + +## API note + +The package may expose minimal docstring / constant stubs (e.g. `FUSION_RULE`). +No production operators, no side effects, no I/O. + +## Promotion path + +1. Algebraic + numerical proofs land and are reviewable. +2. ADR update records evidence and remaining risks. +3. Human Accept of a production promotion gate. +4. Only then may a **separate**, reviewed integration surface be designed — + still subject to serve quarantine and sovereignty invariant. + +Until then: this directory is a quarantine box, not a feature. diff --git a/algebra/topological_reasoning/__init__.py b/algebra/topological_reasoning/__init__.py new file mode 100644 index 00000000..7b5e36fe --- /dev/null +++ b/algebra/topological_reasoning/__init__.py @@ -0,0 +1,21 @@ +"""ADR-0242 V5 (D6) — topological anyon / braid holonomy research quarantine. + +Fibonacci anyon fusion research surface. BLOCKED from production, serve, FFI, +chat/runtime, vault COHERENT, teaching mutation, and GoldTether production +paths until algebraic and numerical proofs exist (see package README). + +This module intentionally re-exports nothing into ``algebra``'s public API +and must not be imported by production packages. +""" + +from __future__ import annotations + +# Canonical Fibonacci anyon fusion rule under study (research label only). +# τ ⊗ τ = 1 ⊕ τ — not a production operator; no evaluation semantics. +FUSION_RULE: str = "tau_otimes_tau_eq_1_oplus_tau" +"""Research label for the Fibonacci anyon fusion rule τ⊗τ = 1⊕τ. + +Docstring / constant only. Does not implement fusion, braiding, or holonomy. +""" + +__all__ = ["FUSION_RULE"] diff --git a/core/physics/__init__.py b/core/physics/__init__.py index a38601c5..70546831 100644 --- a/core/physics/__init__.py +++ b/core/physics/__init__.py @@ -101,6 +101,13 @@ from core.physics.fibonacci_search import ( fibonacci_section_search, propose_kappa_from_search, ) +from core.physics.multi_scale_energy import ( + comparative_residual_separation, + dyadic_tau_schedule, + multi_scale_energy_for_schedule, + multi_scale_energy_vector, + schedule_mid_span_fraction, +) __all__ = [ "SalienceOperator", "SalienceMap", "FieldRegion", @@ -150,4 +157,9 @@ __all__ = [ "fibonacci_section_search", "propose_kappa_from_search", "propose_kappa_line_search", + "comparative_residual_separation", + "dyadic_tau_schedule", + "multi_scale_energy_for_schedule", + "multi_scale_energy_vector", + "schedule_mid_span_fraction", ] diff --git a/core/physics/fibonacci_word_schedule.py b/core/physics/fibonacci_word_schedule.py new file mode 100644 index 00000000..a721d117 --- /dev/null +++ b/core/physics/fibonacci_word_schedule.py @@ -0,0 +1,106 @@ +"""core.physics.fibonacci_word_schedule — ADR-0242 V4 (D5) observability choreography. + +Fibonacci-word scheduler for telemetry / sealed-holdout sampling only. + +Drive recurrence +---------------- + W_0 = B + W_1 = A + W_{n+1} = W_n W_{n-1} (string concatenation) + +where: + * A — low-cost local measurement + * B — high-cost cross-band check + +Length formula +-------------- +With the standard Fibonacci sequence F_0 = 0, F_1 = 1, F_2 = 1, F_3 = 2, …: + + |W_n| = F_{n+1} for n >= 0 + (equivalently |W_0|=1, |W_1|=1, |W_2|=2, |W_3|=3, |W_4|=5, …) + +Sovereignty (ADR-0242 absolute invariant) +---------------------------------------- +This module is **outside the cognitive truth path**. It schedules +observability / telemetry actions only. It MUST NOT: + + * mutate vault standing or call VaultStore.store + * mutate field state or authorize COHERENT promotion + * dictate proposition truth, safety policy, or identity + * be imported from the serve hot path (A-04 quarantine) + +Pure and deterministic: no I/O, no randomness, no field/vault side effects. +""" + +from __future__ import annotations + +from enum import Enum +from typing import Iterator + + +class Action(str, Enum): + """Observability action labels (telemetry only; not cognitive truth).""" + + A = "A" # low-cost local measurement + B = "B" # high-cost cross-band check + + +def _require_nonneg_int(n: int, *, name: str = "n") -> int: + if not isinstance(n, int) or isinstance(n, bool): + raise TypeError(f"{name} must be an int, got {type(n).__name__}") + if n < 0: + raise ValueError(f"{name} must be non-negative, got {n}") + return n + + +def fibonacci_word(n: int) -> str: + """Return the Fibonacci word W_n as a string of 'A'/'B' characters. + + W_0 = \"B\", W_1 = \"A\", W_{k+1} = W_k + W_{k-1}. + Length |W_n| = F_{n+1} with F_0=0, F_1=1. + """ + n = _require_nonneg_int(n) + if n == 0: + return Action.B.value + if n == 1: + return Action.A.value + # Iterative doubling: O(n) concatenations, O(F_{n+1}) total chars. + prev = Action.B.value # W_0 + curr = Action.A.value # W_1 + for _ in range(2, n + 1): + prev, curr = curr, curr + prev + return curr + + +def schedule_actions(n: int) -> tuple[str, ...]: + """Return the action sequence for W_n as an immutable tuple of \"A\"/\"B\". + + Same recurrence and length as :func:`fibonacci_word`; form convenient for + iteration without splitting a string. + """ + word = fibonacci_word(n) + return tuple(word) + + +def iter_schedule_actions(n: int) -> Iterator[str]: + """Iterate actions of W_n without building an intermediate tuple.""" + yield from fibonacci_word(n) + + +def word_length(n: int) -> int: + """Return |W_n| = F_{n+1} (F_0=0, F_1=1) without building the word.""" + n = _require_nonneg_int(n) + # F_{n+1}: a,b walk F_0=0, F_1=1 for (n+1) steps → a = F_{n+1} + a, b = 0, 1 + for _ in range(n + 1): + a, b = b, a + b + return a + + +__all__ = [ + "Action", + "fibonacci_word", + "schedule_actions", + "iter_schedule_actions", + "word_length", +] diff --git a/core/physics/multi_scale_energy.py b/core/physics/multi_scale_energy.py new file mode 100644 index 00000000..2efd7c24 --- /dev/null +++ b/core/physics/multi_scale_energy.py @@ -0,0 +1,178 @@ +"""ADR-0242 V2 — multi-scale temporal energy basis (research prototype). + +Drive formula: + + E_n(t) = E_n(t_0) * exp(-(t - t_0) / (F_n * τ_0)) + +with Fibonacci scale factors F_n (n ≥ 1) and base time constant τ_0. + +This module is **research-only**: +- pure helpers for comparative study vs a dyadic (2^{n-1} τ_0) baseline +- **not** a production default inside ``FieldEnergyOperator.compute`` +- serve-quarantined (A-04): must not be imported from ``chat/runtime.py`` + +Reuses ``fibonacci_number`` / ``fibonacci_tau_schedule`` — no parallel Fibonacci. +""" + +from __future__ import annotations + +from math import exp, isfinite +from typing import Sequence + +from core.physics.fibonacci_search import fibonacci_number +from core.physics.wave_energy_boundary import fibonacci_tau_schedule + +_DEFAULT_TAU0 = 1.0 + + +def _validate_tau0(tau0: float) -> float: + t0 = float(tau0) + if not (t0 > 0.0) or not isfinite(t0): + raise ValueError("tau0 must be a positive finite scalar") + return t0 + + +def _validate_levels(levels: int) -> int: + n = int(levels) + if n < 1: + raise ValueError("levels must be >= 1") + return n + + +def _validate_age(age: float) -> float: + a = float(age) + if a < 0.0 or not isfinite(a): + raise ValueError("age must be a non-negative finite scalar") + return a + + +def _validate_e0(e0: float) -> float: + e = float(e0) + if not isfinite(e): + raise ValueError("e0 must be a finite scalar") + return e + + +def dyadic_tau_schedule( + tau0: float = _DEFAULT_TAU0, + *, + levels: int = 8, +) -> tuple[float, ...]: + """Dyadic comparison baseline τ_n = 2^{n-1} · τ_0 for n = 1..levels. + + ADR-0242 Phase 2 comparative hypothesis baseline (not a production default). + """ + t0 = _validate_tau0(tau0) + n = _validate_levels(levels) + return tuple(float(t0 * (2 ** (i - 1))) for i in range(1, n + 1)) + + +def multi_scale_energy_for_schedule( + e0: float, + age: float, + taus: Sequence[float], +) -> tuple[float, ...]: + """Apply E = e0 · exp(-age / τ) for each positive finite τ in ``taus``. + + ``age`` is (t − t_0). ``e0`` is the shared E_n(t_0) research default. + """ + e = _validate_e0(e0) + a = _validate_age(age) + if not taus: + raise ValueError("taus must be non-empty") + out: list[float] = [] + for raw in taus: + tau = float(raw) + if not (tau > 0.0) or not isfinite(tau): + raise ValueError("each tau must be a positive finite scalar") + out.append(float(e * exp(-a / tau))) + return tuple(out) + + +def multi_scale_energy_vector( + e0: float, + age: float, + *, + tau0: float = _DEFAULT_TAU0, + levels: int = 8, +) -> tuple[float, ...]: + """Fibonacci multi-scale energies E_n for n = 1..levels. + + Drive form with shared E_n(t_0) = e0: + + E_n = e0 * exp(-age / (F_n * tau0)) + + Equivalent to ``multi_scale_energy_for_schedule`` over + ``fibonacci_tau_schedule(tau0, levels=levels)``. + """ + t0 = _validate_tau0(tau0) + n = _validate_levels(levels) + # Explicit F_n path keeps the Drive formula visible at the callsite layer. + e = _validate_e0(e0) + a = _validate_age(age) + return tuple( + float(e * exp(-a / float(fibonacci_number(i) * t0))) + for i in range(1, n + 1) + ) + + +def comparative_residual_separation( + e0: float, + age: float, + *, + tau0: float = _DEFAULT_TAU0, + levels: int = 8, +) -> dict[str, object]: + """Deterministic Fibonacci vs dyadic multi-scale energy comparison. + + Pure research helper — no I/O. Returns both schedules, both energy + vectors, and per-index energy gaps (fib − dyadic). Promotion of + Fibonacci multi-band energy into production requires evidence from + this (or richer) comparative surface. + """ + t0 = _validate_tau0(tau0) + n = _validate_levels(levels) + fib_taus = fibonacci_tau_schedule(t0, levels=n) + dyad_taus = dyadic_tau_schedule(t0, levels=n) + fib_e = multi_scale_energy_for_schedule(e0, age, fib_taus) + dyad_e = multi_scale_energy_for_schedule(e0, age, dyad_taus) + gaps = tuple(float(f - d) for f, d in zip(fib_e, dyad_e, strict=True)) + return { + "tau0": t0, + "levels": n, + "age": float(age), + "e0": float(e0), + "fibonacci_taus": fib_taus, + "dyadic_taus": dyad_taus, + "fibonacci_energies": fib_e, + "dyadic_energies": dyad_e, + "energy_gap_fib_minus_dyadic": gaps, + } + + +def schedule_mid_span_fraction(taus: Sequence[float], *, index: int | None = None) -> float: + """Fraction of max(τ) occupied by τ at mid (or given) index. + + Used by comparative pins: Fibonacci mid-scale bands sit further along + the normalized span than pure dyadic 2^{n-1} (slower φ-growth). + """ + if not taus: + raise ValueError("taus must be non-empty") + vals = tuple(float(t) for t in taus) + for t in vals: + if not (t > 0.0) or not isfinite(t): + raise ValueError("each tau must be a positive finite scalar") + peak = max(vals) + i = len(vals) // 2 if index is None else int(index) + if i < 0 or i >= len(vals): + raise ValueError("index out of range for taus") + return float(vals[i] / peak) + + +__all__ = [ + "comparative_residual_separation", + "dyadic_tau_schedule", + "multi_scale_energy_for_schedule", + "multi_scale_energy_vector", + "schedule_mid_span_fraction", +] diff --git a/core/physics/sensorium_wave_feed.py b/core/physics/sensorium_wave_feed.py new file mode 100644 index 00000000..f4ae5f26 --- /dev/null +++ b/core/physics/sensorium_wave_feed.py @@ -0,0 +1,141 @@ +"""D7 sensorium → ψ feed (I-04 boundary). + +Thin construction-boundary adapter: modality surface packets become Cl(4,1) +wave fields for algebraic multimodal resonance. + +Real modality compilers remain in ``sensorium/*``. This module only standardizes +the feed into the wave substrate: + + * :class:`ModalityPacket` (or dict) — modality id + 32-float coefficients + * :func:`compile_packet_to_psi` — validate / lift to shape ``(32,)`` + * :func:`superpose_packets` — ``ψ_total = Σ ψ_i`` + * :func:`phase_correlate` — delegates **only** to + :meth:`WaveManifold.phase_correlation` (metric-exact ρ; no cosine / ANN) + +Honest test fixtures via :func:`fake_deterministic_packet` use closed rotors +from :func:`algebra.rotor.make_rotor_from_angle` when live compilers are not +under test. That is a fixture, not a claim that audio/vision compilers ran. + +Off-serve: must not be imported by ``chat/runtime.py`` (A-04 quarantine). +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Mapping, Sequence, Union + +import numpy as np + +from algebra.cl41 import N_COMPONENTS +from algebra.rotor import make_rotor_from_angle +from core.physics.wave_manifold import WaveManifold + +PacketLike = Union["ModalityPacket", Mapping[str, Any]] + + +@dataclass(frozen=True, slots=True) +class ModalityPacket: + """Construction-boundary packet: modality tag + 32 Cl(4,1) coefficients. + + After compile, the field has no modality concept (Logos recovery). + ``modality_id`` is provenance only. + """ + + modality_id: str + coefficients: np.ndarray # shape (N_COMPONENTS,) + + def __post_init__(self) -> None: + mid = str(self.modality_id).strip() + if not mid: + raise ValueError("modality_id must be non-empty") + arr = np.asarray(self.coefficients, dtype=np.float64).reshape(-1) + if arr.shape != (N_COMPONENTS,): + raise ValueError( + f"coefficients must have shape ({N_COMPONENTS},); got {arr.shape}" + ) + object.__setattr__(self, "modality_id", mid) + object.__setattr__(self, "coefficients", arr.copy()) + + +def _coerce_packet(packet: PacketLike) -> ModalityPacket: + if isinstance(packet, ModalityPacket): + return packet + if isinstance(packet, Mapping): + mid = packet.get("modality_id", packet.get("modality")) + coeffs = packet.get("coefficients") + if coeffs is None: + coeffs = packet.get("coeffs") + if coeffs is None: + coeffs = packet.get("psi") + if mid is None or coeffs is None: + raise ValueError( + "packet mapping requires modality_id (or modality) and " + "coefficients (or coeffs / psi)" + ) + return ModalityPacket(modality_id=str(mid), coefficients=np.asarray(coeffs)) + raise TypeError( + f"packet must be ModalityPacket or mapping; got {type(packet).__name__}" + ) + + +def compile_packet_to_psi(packet: PacketLike) -> np.ndarray: + """Lift a modality packet to a wave field ψ of shape ``(32,)``. + + Construction-boundary only: validates shape/dtype and returns a fresh + float64 copy. Does not repair non-unit packets (no hidden unitize). + """ + p = _coerce_packet(packet) + return p.coefficients.astype(np.float64, copy=True) + + +def superpose_packets(packets: Sequence[PacketLike]) -> np.ndarray: + """Linear superposition ``ψ_total = Σ_i compile_packet_to_psi(packet_i)``. + + Empty input refuses (no confabulated zero field as resonance truth). + """ + if not packets: + raise ValueError("superpose_packets: empty packet list") + total = np.zeros(N_COMPONENTS, dtype=np.float64) + for packet in packets: + total = total + compile_packet_to_psi(packet) + return total + + +def phase_correlate( + psi_a: np.ndarray, + psi_b: np.ndarray, + *, + manifold: WaveManifold | None = None, +) -> float: + """Algebraic multimodal resonance ρ(A,B) for I-04. + + Delegates solely to :meth:`WaveManifold.phase_correlation`. + Forbidden: cosine similarity, ANN, sklearn neighbors, embedding ranking. + """ + m = manifold if manifold is not None else WaveManifold() + return float(m.phase_correlation(psi_a, psi_b)) + + +def fake_deterministic_packet( + modality_id: str, + *, + angle: float = 0.3, + plane: int = 6, +) -> ModalityPacket: + """Honest deterministic fixture when real modality compilers are absent. + + Builds a closed unit rotor via :func:`make_rotor_from_angle`. This is a + test/construction fixture — not a live audio/vision compile path. + """ + coeffs = make_rotor_from_angle(float(angle), bivector_idx=int(plane)) + return ModalityPacket(modality_id=modality_id, coefficients=coeffs) + + +__all__ = [ + "ModalityPacket", + "PacketLike", + "compile_packet_to_psi", + "superpose_packets", + "phase_correlate", + "fake_deterministic_packet", +] diff --git a/docs/adr/ADR-0242-atlas-packing-and-fibonacci.md b/docs/adr/ADR-0242-atlas-packing-and-fibonacci.md index 78f27254..f547c182 100644 --- a/docs/adr/ADR-0242-atlas-packing-and-fibonacci.md +++ b/docs/adr/ADR-0242-atlas-packing-and-fibonacci.md @@ -1,6 +1,6 @@ # ADR-0242: Deterministic Fibonacci Operators and Evidence-Gated Optimization -**Status**: Proposed — V1 cert discipline + V3 packing landed; V2/V4/V5 staged; **not** self-Accepted (Joshua review). +**Status**: Proposed — V1 cert + V3 packing + V2 research helpers + V4 word schedule + V5 quarantine + sensorium feed landed; ready for Joshua review (do not self-accept). **Date**: 2026-07-13 (Drive authority); in-repo expansion 2026-07-15 **Deciders**: Joshua Shay + multi-model R&D **Traceability**: Drive ADR-0242 (`15_NECCPy-tEWGfYi_BNqawm8GytUTMkz1DsOqGVMXhI`), PR #37/#38, cohesion plan @@ -41,7 +41,7 @@ Module: `core/physics/fibonacci_search.py` - success → proposed κ = minimizer (telemetry; no auto state mutation) - failure → **baseline κ = 1.0** -### Vector 2 — Multi-scale temporal basis (research) 🟡 +### Vector 2 — Multi-scale temporal basis (research helpers) 🟢 research / 🟡 production Drive: @@ -49,8 +49,9 @@ Drive: E_n(t) = E_n(t_0)\,\exp\bigl(-(t-t_0)/(F_n\tau_0)\bigr) \] -Landed progressive form: `fibonacci_tau_schedule` / `recency_band_index` in `wave_energy_boundary.py` (constants table). -**Not** yet production default inside `FieldEnergyOperator`. Promotion requires comparative benchmark vs dyadic \(2^n\tau_0\) (Drive comparative hypothesis). +- `wave_energy_boundary.fibonacci_tau_schedule` / `recency_band_index` (constants table) +- `multi_scale_energy.py`: `multi_scale_energy_vector`, `dyadic_tau_schedule`, `comparative_residual_separation` +**Not** production default inside `FieldEnergyOperator`. Flip requires comparative benchmark + Joshua gate. ### Vector 3 — Golden-Angle mode allocator 🟢 @@ -62,15 +63,18 @@ Module: `core/physics/atlas_packing.py` - Reconstruction-over-storage: `ALLOCATOR_VERSION = golden_angle_v1` + `allocator_layout_descriptor` - Not holographic seals (null points ≠ closed unit versors) -### Vector 4 — Fibonacci-word observability choreography 🔴 staged +### Vector 4 — Fibonacci-word observability choreography 🟢 + +Module: `core/physics/fibonacci_word_schedule.py` Drive: \(W_0=B, W_1=A, W_{n+1}=W_n W_{n-1}\) for telemetry / sealed-holdout sampling. -**Outside cognitive truth path.** Not yet implemented (plan D5). +**Outside cognitive truth path** — pure schedule only; no vault/field mutation. -### Vector 5 — Topological anyon / braid holonomy 🔴 research quarantine +### Vector 5 — Topological anyon / braid holonomy 🟢 quarantine box -Drive: isolated `algebra/topological_reasoning/` study; blocked from production. -Not implemented (plan D6). Must not enter serve/FFI until proofs exist. +Package: `algebra/topological_reasoning/` (README + `FUSION_RULE` stub). +Production AST pin: `chat/`, `core/physics/`, `generate/`, `vault/`, `teaching/` must not import it. +No fusion/braid logic until proofs + human Accept. --- @@ -79,10 +83,10 @@ Not implemented (plan D6). Must not enter serve/FFI until proofs exist. | Phase | Vector | Status | |-------|--------|--------| | 1 | V1 search + κ cert gate | 🟢 | -| 2 | V2 multi-scale energy study | 🟡 table only | +| 2 | V2 multi-scale energy study | 🟢 helpers; 🟡 not production default | | 3 | V3 packing | 🟢 | -| 4 | V4 word scheduler | 🔴 | -| 5 | V5 anyons | 🔴 quarantine | +| 4 | V4 word scheduler | 🟢 | +| 5 | V5 anyons | 🟢 quarantine only (no logic) | --- diff --git a/docs/research/third-door-blueprint-fidelity.md b/docs/research/third-door-blueprint-fidelity.md index a3d44174..72e21fb6 100644 --- a/docs/research/third-door-blueprint-fidelity.md +++ b/docs/research/third-door-blueprint-fidelity.md @@ -302,9 +302,10 @@ PY | Golden-Angle atlas packing \(d_{\min}=0.12\) (V3) | 🟢 ADR-0242 (`atlas_packing`; CGA null-point \(d\); `golden_angle_v1`) | | Fibonacci section search cert/failure (V1) | 🟢 ADR-0242 (`FibonacciSearchCertificate` \| `OptimizationFailure`; dual-run digest) | | κ cert gate fail → baseline 1.0 (V1b) | 🟢 `propose_kappa_from_search` / `goldtether.propose_kappa_line_search` | -| Multi-scale \(\tau_n=F_n\tau_0\) (V2) | 🟡 table only; production multi-band \(E_n(t)\) not default | -| Fibonacci-word scheduler (V4) | 🔴 staged | -| Fibonacci anyons (V5) | 🔴 research quarantine | +| Multi-scale \(\tau_n=F_n\tau_0\) + \(E_n(t)\) helpers (V2) | 🟢 research API (`multi_scale_energy`); production energy default unchanged | +| Fibonacci-word scheduler (V4) | 🟢 `fibonacci_word_schedule` (telemetry only) | +| Fibonacci anyons (V5) | 🟢 quarantine package only; zero production imports | +| Sensorium → ψ feed (I-04) | 🟢 `sensorium_wave_feed` (fake packets + real \(\rho\)) | | 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) | diff --git a/docs/specs/runtime_contracts.md b/docs/specs/runtime_contracts.md index e89bcc42..5f5464a5 100644 --- a/docs/specs/runtime_contracts.md +++ b/docs/specs/runtime_contracts.md @@ -866,10 +866,14 @@ wrong=0 serve entry path (AST-pinned in `tests/test_third_door_cohesion.py`): |--------|------| | `core.physics.wave_manifold` | Cl(4,1) wave field \(\psi\), leakage, polar conjugacy, chiral, \(\rho\) | | `core.physics.holographic_vault` | Durable standing-wave spectrum via `VaultStore` | -| `core.physics.atlas_packing` | Golden-Angle mode packing (ADR-0242) | -| `core.physics.fibonacci_search` | Fixed-budget unimodal section search (ADR-0242) | +| `core.physics.atlas_packing` | Golden-Angle mode packing (ADR-0242 V3) | +| `core.physics.fibonacci_search` | Cert-gated Fibonacci section search (ADR-0242 V1) | +| `core.physics.fibonacci_word_schedule` | Fibonacci-word observability choreography (ADR-0242 V4; telemetry only) | +| `core.physics.multi_scale_energy` | Multi-band \(E_n(t)\) research helpers (ADR-0242 V2; not serve) | +| `core.physics.sensorium_wave_feed` | Sensorium → \(\psi\) construction feed (I-04) | | `core.contemplation.wave_seam` | P9 Trace A SPECULATIVE seal + hypothesis/evidence reconstruct | | `core.physics.wave_energy_boundary` | P10 Trace B residual→energy / \(\tau_n\) / crystallization | +| `algebra.topological_reasoning` | ADR-0242 V5 research quarantine only — never serve/production | Wiring any of these into serve requires an explicit ADR amendment and a failing-to-green containment test change in the same PR. diff --git a/tests/test_adr_0241_sensorium_wave_feed.py b/tests/test_adr_0241_sensorium_wave_feed.py new file mode 100644 index 00000000..59ae7a85 --- /dev/null +++ b/tests/test_adr_0241_sensorium_wave_feed.py @@ -0,0 +1,222 @@ +"""D7 sensorium → ψ feed (I-04 boundary) pins. + +ADR-0241 cohesion: modality packets compile to Cl(4,1) wave fields; +multimodal resonance uses WaveManifold.phase_correlation only. + +Honest fake packets when real compilers are not under test. +No cosine / ANN / sklearn neighbors. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import numpy as np +import pytest + +from algebra.cl41 import N_COMPONENTS +from algebra.rotor import make_rotor_from_angle +from algebra.versor import versor_condition +from core.physics.sensorium_wave_feed import ( + ModalityPacket, + compile_packet_to_psi, + fake_deterministic_packet, + phase_correlate, + superpose_packets, +) +from core.physics.wave_manifold import WaveManifold + +_ROOT = Path(__file__).resolve().parents[1] +_MODULE = _ROOT / "core/physics/sensorium_wave_feed.py" +_CLOSURE = 1e-6 + + +def _closed(angle: float = 0.3, plane: int = 6) -> np.ndarray: + return make_rotor_from_angle(angle, bivector_idx=plane) + + +# --- compile / packet -------------------------------------------------------- + + +def test_compile_packet_to_psi_from_modality_packet(): + coeffs = _closed(0.41, plane=7) + packet = ModalityPacket(modality_id="vision", coefficients=coeffs) + psi = compile_packet_to_psi(packet) + assert psi.shape == (N_COMPONENTS,) + assert psi.dtype == np.float64 + assert float(np.linalg.norm(psi - coeffs)) < 1e-15 + # Fresh copy — not the same buffer + assert psi is not packet.coefficients + + +def test_compile_packet_to_psi_from_dict(): + coeffs = _closed(0.22, plane=8) + psi = compile_packet_to_psi( + {"modality_id": "audio", "coefficients": coeffs.tolist()} + ) + assert psi.shape == (32,) + assert float(np.linalg.norm(psi - coeffs)) < 1e-12 + + +def test_compile_packet_accepts_modality_and_psi_keys(): + coeffs = _closed(0.15, plane=6) + psi = compile_packet_to_psi({"modality": "text", "psi": coeffs}) + assert float(np.linalg.norm(psi - coeffs)) < 1e-15 + + +def test_compile_packet_rejects_wrong_shape(): + with pytest.raises(ValueError, match="shape"): + ModalityPacket(modality_id="x", coefficients=np.zeros(16)) + + +def test_compile_packet_rejects_empty_modality_id(): + with pytest.raises(ValueError, match="modality_id"): + ModalityPacket(modality_id=" ", coefficients=_closed()) + + +def test_compile_packet_rejects_incomplete_dict(): + with pytest.raises(ValueError, match="modality_id"): + compile_packet_to_psi({"coefficients": _closed()}) + + +# --- superpose --------------------------------------------------------------- + + +def test_superpose_packets_is_sum_of_compiled(): + a = fake_deterministic_packet("audio", angle=0.2, plane=6) + b = fake_deterministic_packet("vision", angle=0.55, plane=8) + total = superpose_packets([a, b]) + expected = compile_packet_to_psi(a) + compile_packet_to_psi(b) + assert float(np.linalg.norm(total - expected)) < 1e-15 + + +def test_superpose_packets_empty_refused(): + with pytest.raises(ValueError, match="empty"): + superpose_packets([]) + + +# --- phase correlate (I-04) -------------------------------------------------- + + +def test_phase_correlate_delegates_to_wave_manifold(): + a = _closed(0.2, plane=6) + b = _closed(0.55, plane=8) + M = WaveManifold() + rho_direct = M.phase_correlation(a, b) + rho_feed = phase_correlate(a, b, manifold=M) + assert abs(rho_feed - rho_direct) < 1e-15 + + +def test_phase_correlate_symmetric(): + a = compile_packet_to_psi(fake_deterministic_packet("text", angle=0.3, plane=6)) + b = compile_packet_to_psi(fake_deterministic_packet("audio", angle=0.7, plane=9)) + assert abs(phase_correlate(a, b) - phase_correlate(b, a)) < 1e-12 + assert phase_correlate(a, a) > 0.5 + + +def test_cross_modal_fake_packets_phase_correlate(): + """I-04 feed path: two modalities → ψ → algebraic ρ (not cosine).""" + audio = fake_deterministic_packet("audio", angle=0.25, plane=6) + vision = fake_deterministic_packet("vision", angle=0.25, plane=6) + # Same closed rotor → strong self-like correlation across modality tags + rho_same = phase_correlate( + compile_packet_to_psi(audio), + compile_packet_to_psi(vision), + ) + assert rho_same > 0.5 + + other = fake_deterministic_packet("vision", angle=1.1, plane=10) + rho_diff = phase_correlate( + compile_packet_to_psi(audio), + compile_packet_to_psi(other), + ) + # Distinct planes/angles are not required to be lower, but path must run. + assert isinstance(rho_diff, float) + + +# --- fake deterministic fixtures --------------------------------------------- + + +def test_fake_deterministic_packet_closed_and_stable(): + p1 = fake_deterministic_packet("sensorimotor", angle=0.4, plane=7) + p2 = fake_deterministic_packet("sensorimotor", angle=0.4, plane=7) + assert p1.modality_id == "sensorimotor" + assert float(np.linalg.norm(p1.coefficients - p2.coefficients)) == 0.0 + assert versor_condition(p1.coefficients) < _CLOSURE + + +def test_fake_deterministic_matches_make_rotor(): + angle, plane = 0.37, 11 + packet = fake_deterministic_packet("motor", angle=angle, plane=plane) + expected = make_rotor_from_angle(angle, bivector_idx=plane) + assert float(np.linalg.norm(packet.coefficients - expected)) < 1e-15 + + +# --- hygiene: no approx neighbors / cosine ----------------------------------- + + +def test_module_forbids_approx_neighbor_and_cosine_imports(): + """I-04: no faiss / hnsw / annoy / sklearn / cosine_similarity stack.""" + src = _MODULE.read_text(encoding="utf-8") + tree = ast.parse(src) + banned_roots = { + "faiss", + "hnswlib", + "annoy", + "sklearn", + "scipy", + "sklearn.neighbors", + } + banned_names = { + "cosine_similarity", + "NearestNeighbors", + "cosine", + "cdist", + } + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + root = alias.name.split(".")[0] + assert root not in banned_roots, f"banned import {alias.name}" + assert alias.name not in banned_roots + if isinstance(node, ast.ImportFrom) and node.module: + root = node.module.split(".")[0] + assert root not in banned_roots, f"banned from {node.module}" + assert node.module not in banned_roots + for alias in node.names: + assert alias.name not in banned_names + if isinstance(node, ast.Attribute): + assert node.attr not in banned_names + if isinstance(node, ast.Name): + assert node.id not in banned_names + # Source-level ban on cosine similarity wording as implementation path + assert "cosine_similarity" not in src + assert "NearestNeighbors" not in src + + +def test_phase_correlate_source_only_calls_phase_correlation(): + """phase_correlate body must call WaveManifold.phase_correlation only.""" + src = _MODULE.read_text(encoding="utf-8") + tree = ast.parse(src) + func = None + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef) and node.name == "phase_correlate": + func = node + break + assert func is not None, "phase_correlate not found" + call_attrs: list[str] = [] + for node in ast.walk(func): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): + call_attrs.append(node.func.attr) + assert "phase_correlation" in call_attrs + # No alternate resonance / similarity calls inside the function + forbidden = { + "cosine_similarity", + "resonant_recall", + "resonant_reconstruct", + "dot", + "norm", + } + for attr in call_attrs: + assert attr not in forbidden, f"phase_correlate must not call .{attr}" diff --git a/tests/test_adr_0242_fibonacci_word.py b/tests/test_adr_0242_fibonacci_word.py new file mode 100644 index 00000000..908de980 --- /dev/null +++ b/tests/test_adr_0242_fibonacci_word.py @@ -0,0 +1,136 @@ +"""ADR-0242 V4 (D5) — Fibonacci-word observability scheduler. + +Telemetry-only; dual-run deterministic; cannot mutate cognitive truth. +""" + +from __future__ import annotations + +import pytest + +from core.physics.fibonacci_word_schedule import ( + Action, + fibonacci_word, + iter_schedule_actions, + schedule_actions, + word_length, +) + + +# Expected words from Drive: W0=B, W1=A, W_{n+1}=W_n W_{n-1} +_EXPECTED = { + 0: "B", + 1: "A", + 2: "AB", + 3: "ABA", + 4: "ABAAB", + 5: "ABAABABA", + 6: "ABAABABAABAAB", +} + + +def test_w0_is_b(): + assert fibonacci_word(0) == "B" + assert schedule_actions(0) == ("B",) + + +def test_w1_is_a(): + assert fibonacci_word(1) == "A" + assert schedule_actions(1) == ("A",) + + +@pytest.mark.parametrize("n,expected", sorted(_EXPECTED.items())) +def test_fibonacci_word_table(n: int, expected: str): + assert fibonacci_word(n) == expected + + +@pytest.mark.parametrize("n,expected", sorted(_EXPECTED.items())) +def test_schedule_actions_matches_word(n: int, expected: str): + actions = schedule_actions(n) + assert actions == tuple(expected) + assert all(a in (Action.A.value, Action.B.value) for a in actions) + + +def test_w2_ab_w3_aba_w4_abaab(): + assert fibonacci_word(2) == "AB" + assert fibonacci_word(3) == "ABA" + assert fibonacci_word(4) == "ABAAB" + + +def test_recurrence_w_n_concat(): + """W_{n+1} == W_n + W_{n-1} for several n.""" + for n in range(1, 10): + assert fibonacci_word(n + 1) == fibonacci_word(n) + fibonacci_word(n - 1) + + +def test_length_equals_fib_n_plus_1(): + """|W_n| = F_{n+1} with F_0=0, F_1=1, F_2=1, F_3=2, F_4=3, F_5=5, …""" + # F_{n+1} table for n=0..8 → 1,1,2,3,5,8,13,21,34 + fib_np1 = [1, 1, 2, 3, 5, 8, 13, 21, 34] + for n, expected_len in enumerate(fib_np1): + word = fibonacci_word(n) + assert len(word) == expected_len + assert word_length(n) == expected_len + assert len(schedule_actions(n)) == expected_len + + +def test_dual_run_identical(): + """Deterministic: two independent evaluations produce byte-identical results.""" + for n in range(0, 12): + a = fibonacci_word(n) + b = fibonacci_word(n) + assert a == b + assert schedule_actions(n) == schedule_actions(n) + assert tuple(iter_schedule_actions(n)) == schedule_actions(n) + + +def test_action_enum_values(): + assert Action.A.value == "A" + assert Action.B.value == "B" + assert Action.A == "A" + assert Action.B == "B" + + +def test_rejects_negative_n(): + with pytest.raises(ValueError, match="non-negative"): + fibonacci_word(-1) + with pytest.raises(ValueError, match="non-negative"): + schedule_actions(-1) + with pytest.raises(ValueError, match="non-negative"): + word_length(-1) + + +def test_rejects_non_int_n(): + with pytest.raises(TypeError): + fibonacci_word(1.5) # type: ignore[arg-type] + with pytest.raises(TypeError): + fibonacci_word(True) # type: ignore[arg-type] + + +def test_only_ab_alphabet(): + for n in range(0, 14): + word = fibonacci_word(n) + assert set(word) <= {"A", "B"} + if n == 0: + assert set(word) == {"B"} + elif n == 1: + assert set(word) == {"A"} + else: + assert set(word) == {"A", "B"} + + +def test_module_is_pure_no_side_effect_imports(): + """Sovereignty pin: module must not pull vault/field mutation surfaces.""" + import ast + from pathlib import Path + + src = Path("core/physics/fibonacci_word_schedule.py").read_text() + tree = ast.parse(src) + forbidden = {"vault", "field", "store", "VaultStore", "generate", "chat"} + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + root = alias.name.split(".")[0] + assert root not in forbidden, alias.name + if isinstance(node, ast.ImportFrom) and node.module: + root = node.module.split(".")[0] + assert root not in forbidden, node.module diff --git a/tests/test_adr_0242_multi_scale_energy.py b/tests/test_adr_0242_multi_scale_energy.py new file mode 100644 index 00000000..229b1cc8 --- /dev/null +++ b/tests/test_adr_0242_multi_scale_energy.py @@ -0,0 +1,167 @@ +"""ADR-0242 V2 — multi-scale temporal energy basis (research prototype). + +Pins Drive form E_n = E0 · exp(-age / (F_n · τ_0)), dyadic baseline comparison, +determinism, and serve quarantine. Does **not** change FieldEnergyOperator defaults. +""" + +from __future__ import annotations + +import ast +from math import exp +from pathlib import Path + +import pytest + +from core.physics.fibonacci_search import fibonacci_number +from core.physics.multi_scale_energy import ( + comparative_residual_separation, + dyadic_tau_schedule, + multi_scale_energy_for_schedule, + multi_scale_energy_vector, + schedule_mid_span_fraction, +) +from core.physics.wave_energy_boundary import fibonacci_tau_schedule + +_ROOT = Path(__file__).resolve().parents[1] + + +# --- Schedules -------------------------------------------------------------- + + +def test_dyadic_tau_schedule_powers_of_two(): + # τ_n = 2^{n-1} · τ_0 for n = 1..5 → 1, 2, 4, 8, 16 when τ_0=1 + assert dyadic_tau_schedule(tau0=1.0, levels=5) == (1.0, 2.0, 4.0, 8.0, 16.0) + assert dyadic_tau_schedule(tau0=0.5, levels=4) == (0.5, 1.0, 2.0, 4.0) + + +def test_dyadic_tau_schedule_rejects_bad_inputs(): + with pytest.raises(ValueError): + dyadic_tau_schedule(tau0=0.0, levels=3) + with pytest.raises(ValueError): + dyadic_tau_schedule(tau0=1.0, levels=0) + + +def test_fibonacci_tau_matches_fibonacci_number(): + taus = fibonacci_tau_schedule(tau0=1.0, levels=6) + expected = tuple(float(fibonacci_number(i)) for i in range(1, 7)) + assert taus == expected + + +# --- Drive energy formula --------------------------------------------------- + + +def test_multi_scale_energy_vector_matches_drive_formula(): + e0, age, tau0, levels = 2.0, 3.0, 1.0, 5 + got = multi_scale_energy_vector(e0, age, tau0=tau0, levels=levels) + expected = tuple( + e0 * exp(-age / (fibonacci_number(i) * tau0)) for i in range(1, levels + 1) + ) + assert len(got) == levels + for a, b in zip(got, expected, strict=True): + assert a == pytest.approx(b, rel=0.0, abs=1e-15) + + +def test_multi_scale_energy_matches_schedule_helper(): + e0, age, tau0, levels = 1.0, 2.5, 0.5, 6 + via_vector = multi_scale_energy_vector(e0, age, tau0=tau0, levels=levels) + via_schedule = multi_scale_energy_for_schedule( + e0, age, fibonacci_tau_schedule(tau0=tau0, levels=levels) + ) + assert via_vector == via_schedule + + +def test_decay_larger_age_yields_smaller_energy(): + young = multi_scale_energy_vector(1.0, age=1.0, tau0=1.0, levels=5) + old = multi_scale_energy_vector(1.0, age=10.0, tau0=1.0, levels=5) + assert all(o < y for o, y in zip(old, young, strict=True)) + # age=0 → full e0 at every scale + zero = multi_scale_energy_vector(1.25, age=0.0, tau0=1.0, levels=4) + assert zero == (1.25, 1.25, 1.25, 1.25) + + +def test_larger_tau_scale_retains_more_energy(): + # Within a Fibonacci vector, coarser scales (larger F_n) decay slower. + vec = multi_scale_energy_vector(1.0, age=5.0, tau0=1.0, levels=8) + # F_1=1, F_8=21 → last component strictly larger residual energy + assert vec[-1] > vec[0] + + +def test_multi_scale_energy_rejects_bad_inputs(): + with pytest.raises(ValueError): + multi_scale_energy_vector(1.0, age=-1.0) + with pytest.raises(ValueError): + multi_scale_energy_vector(float("nan"), age=1.0) + with pytest.raises(ValueError): + multi_scale_energy_for_schedule(1.0, 1.0, ()) + with pytest.raises(ValueError): + multi_scale_energy_for_schedule(1.0, 1.0, (1.0, 0.0)) + + +# --- Determinism + comparative surface -------------------------------------- + + +def test_deterministic_dual_run(): + kwargs = dict(e0=1.0, age=4.0, tau0=1.0, levels=8) + a = multi_scale_energy_vector(**kwargs) + b = multi_scale_energy_vector(**kwargs) + assert a == b + ca = comparative_residual_separation(**kwargs) + cb = comparative_residual_separation(**kwargs) + assert ca == cb + + +def test_fibonacci_bands_longer_than_dyadic_mid_scale(): + """Mid-scale Fibonacci bands occupy a larger fraction of total span. + + Absolute τ: F_n grows as ~φ^n while dyadic is 2^{n-1}, so dyadic absolute + τ is larger late. Comparatively, φ-growth places the mid-index band further + along the *normalized* hierarchy (span fraction) than pure dyadic — the + structural property the V2 research pin checks. + """ + levels = 8 + tau0 = 1.0 + fib = fibonacci_tau_schedule(tau0=tau0, levels=levels) + dyad = dyadic_tau_schedule(tau0=tau0, levels=levels) + mid = levels // 2 + fib_frac = schedule_mid_span_fraction(fib, index=mid) + dyad_frac = schedule_mid_span_fraction(dyad, index=mid) + assert fib_frac > dyad_frac + # Absolute mid τ still follows F_5=5 vs 2^4=16 + assert fib[mid] == 5.0 + assert dyad[mid] == 16.0 + + +def test_comparative_residual_separation_shape(): + report = comparative_residual_separation(1.0, age=3.0, tau0=1.0, levels=5) + assert report["levels"] == 5 + assert len(report["fibonacci_taus"]) == 5 + assert len(report["dyadic_taus"]) == 5 + assert len(report["fibonacci_energies"]) == 5 + assert len(report["dyadic_energies"]) == 5 + assert len(report["energy_gap_fib_minus_dyadic"]) == 5 + # age=0 → identical unit energies regardless of schedule + zero = comparative_residual_separation(1.0, age=0.0, tau0=1.0, levels=4) + assert zero["fibonacci_energies"] == (1.0, 1.0, 1.0, 1.0) + assert zero["dyadic_energies"] == (1.0, 1.0, 1.0, 1.0) + assert zero["energy_gap_fib_minus_dyadic"] == (0.0, 0.0, 0.0, 0.0) + + +# --- Serve quarantine (A-04) ------------------------------------------------ + + +def test_serve_runtime_does_not_import_multi_scale_energy(): + tree = ast.parse((_ROOT / "chat/runtime.py").read_text(encoding="utf-8")) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module: + assert "multi_scale_energy" not in node.module + assert "wave_energy_boundary" not in node.module + if isinstance(node, ast.Import): + for alias in node.names: + assert "multi_scale_energy" not in alias.name + + +def test_field_energy_operator_untouched_by_multi_scale_module(): + """Production energy operator must not import the research multi-scale path.""" + energy_src = (_ROOT / "core/physics/energy.py").read_text(encoding="utf-8") + assert "multi_scale_energy" not in energy_src + assert "fibonacci_tau_schedule" not in energy_src diff --git a/tests/test_adr_0242_topological_quarantine.py b/tests/test_adr_0242_topological_quarantine.py new file mode 100644 index 00000000..d87dce88 --- /dev/null +++ b/tests/test_adr_0242_topological_quarantine.py @@ -0,0 +1,124 @@ +"""ADR-0242 V5 (D6) — topological_reasoning research quarantine pins. + +Authority: docs/adr/ADR-0242-atlas-packing-and-fibonacci.md Vector 5. +Package may exist under algebra/topological_reasoning/ for isolated study. +Production packages must not import it. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import pytest + +_ROOT = Path(__file__).resolve().parents[1] + +# Production surfaces that must never import the research quarantine package. +_PRODUCTION_PACKAGES = ( + "chat", + "core/physics", + "generate", + "vault", + "teaching", +) + +_BANNED_MARKERS = ( + "topological_reasoning", + "algebra.topological_reasoning", +) + + +def _iter_python_files(package_rel: str) -> list[Path]: + base = _ROOT / package_rel + if not base.is_dir(): + return [] + return sorted(p for p in base.rglob("*.py") if p.is_file()) + + +def _import_mentions_topological(tree: ast.AST) -> list[str]: + """Return import module strings that reference topological_reasoning.""" + hits: list[str] = [] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + name = alias.name + if any(m in name for m in _BANNED_MARKERS): + hits.append(name) + elif isinstance(node, ast.ImportFrom): + mod = node.module or "" + if any(m in mod for m in _BANNED_MARKERS): + hits.append(mod) + # from algebra import topological_reasoning + if mod == "algebra" or mod.endswith(".algebra"): + for alias in node.names: + if alias.name == "topological_reasoning" or ( + alias.name and "topological_reasoning" in alias.name + ): + hits.append(f"{mod}.{alias.name}") + return hits + + +def test_topological_reasoning_package_imports_in_isolation() -> None: + """Package is importable on its own without production wiring.""" + import algebra.topological_reasoning as tr + + assert hasattr(tr, "FUSION_RULE") + assert isinstance(tr.FUSION_RULE, str) + assert tr.FUSION_RULE == "tau_otimes_tau_eq_1_oplus_tau" + # Research label only — no callable production fusion API required. + assert "FUSION_RULE" in tr.__all__ + + +def test_algebra_public_import_still_works() -> None: + """Quarantine package must not break the algebra package surface.""" + import algebra + from algebra import versor_condition, word_transition_rotor + + assert callable(versor_condition) + assert callable(word_transition_rotor) + # Research package is not re-exported on algebra's public surface. + assert not hasattr(algebra, "topological_reasoning") or "topological_reasoning" not in getattr( + algebra, "__all__", () + ) + + +def test_topological_reasoning_package_directory_may_exist() -> None: + """Algebraic research quarantine box is allowed to exist on disk.""" + pkg = _ROOT / "algebra" / "topological_reasoning" + assert pkg.is_dir() + assert (pkg / "__init__.py").is_file() + assert (pkg / "README.md").is_file() + + +@pytest.mark.parametrize("package_rel", _PRODUCTION_PACKAGES) +def test_production_packages_do_not_import_topological_reasoning( + package_rel: str, +) -> None: + """Architectural: production trees must not import topological_reasoning.""" + files = _iter_python_files(package_rel) + assert files, f"expected python sources under {package_rel}" + + violations: list[str] = [] + for path in files: + # Never scan the quarantine package itself (it lives under algebra/). + rel = path.relative_to(_ROOT).as_posix() + if "topological_reasoning" in rel.split("/"): + continue + try: + src = path.read_text(encoding="utf-8") + except OSError as exc: + violations.append(f"{rel}: unreadable ({exc})") + continue + try: + tree = ast.parse(src, filename=rel) + except SyntaxError as exc: + violations.append(f"{rel}: syntax error ({exc})") + continue + for hit in _import_mentions_topological(tree): + violations.append(f"{rel}: imports {hit}") + + assert not violations, ( + "ADR-0242 V5 quarantine violated — production import(s) of " + "topological_reasoning:\n" + "\n".join(violations) + ) diff --git a/tests/test_third_door_cohesion.py b/tests/test_third_door_cohesion.py index dcba364e..7b2c88dc 100644 --- a/tests/test_third_door_cohesion.py +++ b/tests/test_third_door_cohesion.py @@ -60,17 +60,23 @@ def test_phase0_a04_serve_path_quarantines_wave_and_fibonacci(): "wave_manifold", "holographic_vault", "fibonacci_search", + "fibonacci_word_schedule", "atlas_packing", "wave_seam", # P9 Trace A — contemplation only, never serve "wave_energy_boundary", # P10 Trace B — energy/τ gate, never serve + "multi_scale_energy", # ADR-0242 V2 research multi-band E_n(t), never serve + "sensorium_wave_feed", # D7 I-04 sensorium→ψ feed, never serve } banned_substrings = ( "wave_manifold", "holographic_vault", "fibonacci_search", + "fibonacci_word_schedule", "atlas_packing", "wave_seam", "wave_energy_boundary", + "multi_scale_energy", + "sensorium_wave_feed", ) for node in ast.walk(tree): if isinstance(node, ast.Import):