feat(adr-0244): Phase 4 — governed f64→f32 serving-boundary cast (§2.5 / 0245 §2.2)

The single explicit, fail-closed f64→f32 down-cast at the certified lifecycle
egress. serving_cast(psi_steady, certificate, verdict) -> ServingState in
core/physics/cognitive_lifecycle.py:

- Casts ONLY a state that validates as a finite 32-vector, matches its
  certificate psi_digest, and was verdict.admitted — an uncertified, refused, or
  digest-mismatched state is never served.
- Precision-checks the f32 result (cast_error = max|f64-f32|, unit norm) and
  fails closed on an f32 precision cliff (f32_precision_insufficient) rather than
  serving a silently-degraded state.
- Keeps f64 as the source of truth: psi_steady and the psi_digest content-address
  chain are untouched; ServingState carries provenance back (source_psi_digest,
  certificate_id, measured cast_error) for audit. f64 stays inside
  relaxation/eigendecomp — this is the one place f32 appears (ADR-0245 §2.2
  mechanical sympathy).

Satisfies both ADR-0244 §2.5 and ADR-0245 §2.2 (one contract, two ADRs). Measured
on a real certified outcome: cast_error ~1.2e-8, unit_norm_f32 ~0.99999998.
Off-serving (A-04 guard: chat.runtime never imports it). ADR-0245 status map +
plan doc updated.

[Verification]: smoke 176 passed; fast lane 11907 passed / 109 skipped (-n auto,
not quarantine/slow); 10 targeted serving-cast tests passed.
This commit is contained in:
Shay 2026-07-17 19:43:37 -07:00
parent 33bbfa0b06
commit 918aa843b1
4 changed files with 245 additions and 8 deletions

View file

@ -892,6 +892,104 @@ def egress_gate(
)
# --- Serving-boundary f64 -> f32 cast (ADR-0244 §2.5 / ADR-0245 §2.2) -----------------
#
# The lifecycle relaxes and certifies entirely in float64 (relaxation,
# eigendecomposition, the psi_digest content-address chain — all f64). f32 is a
# *serving* representation only: the down-cast happens once, at the certified
# egress hand-off, for consumers on the f32 fast path (the Rust f32
# geometric_product, SIMD, GPU). This is the single governed cast — explicit,
# gated on certification, precision-checked, and auditable — not an implicit
# dtype coercion sprinkled through the hot path.
_F32_CAST_TOL = 1e-6 # float32 has ~1.19e-7 machine eps; a unit versor's
# components are O(1), so a faithful down-cast keeps
# max|f64 - f32| ~6e-8 and unit-norm within a few ulp. The
# tolerance fails closed on a state whose dynamic range
# exceeds what f32 can represent (a genuine precision
# cliff) rather than serving a silently-degraded state.
class ServingCastError(CognitiveLifecycleError):
"""Refused to serve a state as f32: uncertified, digest-mismatched, or f32
precision-insufficient. f64 stays the source of truth; the cast never
silently degrades a state."""
@dataclass(frozen=True, slots=True)
class ServingState:
"""f32 serving projection of a certified ψ_steady (ADR-0244 §2.5 / ADR-0245 §2.2).
The f64 state and its ``psi_digest`` remain authoritative; this is the
down-cast handed to f32 serving consumers, carrying provenance back to the
f64 certificate and the measured round-trip error so the cast is auditable.
"""
psi_f32: np.ndarray
source_psi_digest: str
certificate_id: str
cast_error: float
unit_norm_f32: float
def as_dict(self) -> dict[str, Any]:
return {
"source_psi_digest": self.source_psi_digest,
"certificate_id": self.certificate_id,
"cast_error": float(self.cast_error),
"unit_norm_f32": float(self.unit_norm_f32),
"dtype": str(self.psi_f32.dtype),
}
def serving_cast(
psi_steady: np.ndarray,
certificate: RelaxationCertificate,
verdict: EgressVerdict,
*,
tol: float = _F32_CAST_TOL,
) -> ServingState:
"""Governed f64→f32 down-cast at the certified serving boundary.
Fail-closed. The state is cast **only** if it (a) validates as a finite
32-vector, (b) matches its certificate's ``psi_digest`` (the state served is
provably the certified one), and (c) was admitted by the egress gate an
uncertified or refused state is never handed to a serving consumer. The f32
representation is then precision-checked: if the down-cast perturbs any
component or the unit norm beyond ``tol``, the state sits on an f32 precision
cliff and the cast fails closed rather than serving a degraded state.
f64 remains the source of truth: neither ``psi_steady`` nor the certificate /
digest chain is mutated. This is the single explicit cast the ADR-0245 §2.2
mechanical-sympathy contract permits f64 everywhere inside, f32 only here.
"""
arr = _as_psi(psi_steady, "ψ_steady", error=ServingCastError) # f64 validate
if _psi_digest(arr) != certificate.psi_digest:
raise ServingCastError(
"certificate_state_mismatch", certificate_id=certificate.certificate_id
)
if not verdict.admitted:
raise ServingCastError("uncertified_state_not_served", verdict_reason=verdict.reason)
f32 = np.ascontiguousarray(arr, dtype=np.dtype("<f4"))
roundtrip = f32.astype(np.float64)
cast_error = float(np.max(np.abs(arr - roundtrip))) if arr.size else 0.0
unit_norm_f32 = float(np.linalg.norm(roundtrip))
if cast_error > float(tol) or abs(unit_norm_f32 - 1.0) > float(tol) + _EPSILON_DRIFT:
raise ServingCastError(
"f32_precision_insufficient",
cast_error=cast_error,
unit_norm_f32=unit_norm_f32,
tol=float(tol),
)
f32.setflags(write=False)
return ServingState(
psi_f32=f32,
source_psi_digest=certificate.psi_digest,
certificate_id=certificate.certificate_id,
cast_error=cast_error,
unit_norm_f32=unit_norm_f32,
)
# --- Composed lifecycle ---------------------------------------------------------------
@ -991,6 +1089,8 @@ __all__ = [
"RelaxationNotConverged",
"RelaxationNumericalFailure",
"RelaxationResult",
"ServingCastError",
"ServingState",
"assignment_component_index",
"compile_propositional",
"compile_quadratic_well",
@ -998,5 +1098,6 @@ __all__ = [
"ingest_context",
"propositional_entails",
"relax_to_ground",
"serving_cast",
"uniform_assignment_state",
]

View file

@ -17,7 +17,7 @@
> | § | Decision | Status |
> |---|---|---|
> | 2.1 | PyO3 Rust `geometric_product` f32 fast-path | ✅ **Done**`algebra/backend.py`, pre-existing before this arc. |
> | 2.2 | Gated f64→f32 serving boundary | **Open** — the one genuinely-unbuilt decision. Identical in substance to ADR-0244 §2.5 (same cast, same boundary); tracked as **one contract, two ADRs** — D4 Phase 4. |
> | 2.2 | Gated f64→f32 serving boundary | **Built (D4 Phase 4)**`serving_cast(psi_steady, certificate, verdict) → ServingState` in `cognitive_lifecycle.py`. A single explicit, fail-closed down-cast at the certified egress: casts only a certified/admitted/digest-matched state, precision-checks the f32 result (fails closed on a cliff), and keeps f64 as the source of truth (the digest chain is untouched). Same contract as ADR-0244 §2.5. Pinned by `tests/test_adr_0244_serving_cast.py` (10 tests). |
> | 2.3 | Semantic rigor in content addressing (full 256-bit digest, no `default=str`, byte-order guard) | ◐ **Hot-path done, residual open**`cognitive_lifecycle.py` / `biography_wiring.py` / `self_authorship.py` fixed (cohesion-directive D1). Three contemplation-module content-id sites (`core/contemplation/schema.py`, `plan_preflight.py`, `miners/articulation_quality.py`) still truncate to 16 hex chars and/or use `default=str` — D4 Phase 5. |
> | 2.4 | `_cached_eigh` memoization (`functools.lru_cache`, keyed on `hamiltonian_id` + `matrix.tobytes()`) | ✅ **Done**`core/physics/cognitive_lifecycle.py::_cached_eigh` (cohesion-directive D2), exactly as specified including the canonical two-part cache key. |
>

View file

@ -32,7 +32,7 @@ ADR-0244 is the **identity-layer consumer**; ADR-0245 is the **mechanical-sympat
| 2.2 | Spectral-leakage **fail-closed** gate + `C_id` + `IdentityGateRefusal` | ❌ unbuilt as gate (call site exists at `chat/runtime.py:2679`, but **advisory** only) |
| 2.3 | `Q_top` topological charge | ✅ resolved → **retire from egress** (proven vacuous, `evals/adr_0244_qtop_vacuity`) |
| 2.4 | Bracketed-local Fibonacci search + `γ_id` calibration | ◐ search-honesty done (D3); calibration half unbuilt |
| 2.5 | Serving-boundary f64→f32 cast contract | ❌ unbuilt (== ADR-0245 §2.2; the one genuinely-absent seam) |
| 2.5 | Serving-boundary f64→f32 cast contract | ✅ done (Phase 4 — `serving_cast`→`ServingState`, `cognitive_lifecycle.py`) |
| 2.6 | Rust GP fast-path + parity gate | ✅ done (f32 pre-existing + f64 D2, bit-identical) |
| 2.7 | Full digests / no `default=str` / byte-order guard | ◐ hot-path done (D1); contemplation content-ids residual |
| 2.8 | `eigh` memoization | ✅ done (`_cached_eigh`, D2) |
@ -42,12 +42,12 @@ ADR-0244 is the **identity-layer consumer**; ADR-0245 is the **mechanical-sympat
| ADR-0245 § | Mechanism | Status entering D4 |
|---|---|---|
| 2.1 | PyO3 Rust `geometric_product` f32 fast-path | ✅ done (`algebra/backend.py`) |
| 2.2 | Gated f64→f32 serving boundary | ❌ unbuilt (== 0244 §2.5) |
| 2.2 | Gated f64→f32 serving boundary | ✅ done (Phase 4 — `serving_cast`, == 0244 §2.5) |
| 2.3 | Semantic rigor in content addressing | ◐ hot-path done (D1); residual |
| 2.4 | `_cached_eigh` memoization | ✅ done |
| 3 | Acceptance gate (parity, ≥10× f32 speedup, 0-LAPACK-on-repeat, collision-resistance) | ◐ parity ✓, 0-LAPACK partial (`test_adr_0244_mechanical_sympathy.py`); f32-speedup + collision proofs **missing** |
**Net remaining build = ADR-0244 §2.1 + §2.2 (the identity gate rebuild), §2.4 calibration, §2.5/0245§2.2 cast, §2.7 residual, §2.9 wiring, + the ADR-0245 §3 gaps.**
**Net remaining build (entering Phase 5) = §2.7 residual (3 contemplation content-id sites), §2.9 allocator wiring, §2.10 audit-confirm, + the ADR-0245 §3 acceptance gaps (f32 ≥10× speedup benchmark + collision-resistance proof).** (§2.1/§2.2 identity gate, §2.4 calibration, §2.5/0245§2.2 cast all landed Phases 14.)
---
@ -161,8 +161,8 @@ Dependencies: `0 → {1, 4}` · `1 → 2 → 3` · `5 after 0` · `6 last`. Each
**Steps:** add the typed cast contract at the egress hand-off; precision-sufficiency + parity test; keep f64 inside relaxation/eigendecomp.
**Acceptance:** f32 cast only after certification; parity within documented f32 tolerance; f64 preserved upstream; determinism intact. Satisfies **both** 0244 §2.5 and 0245 §2.2.
**Gate:** smoke + fast lane + cast tests.
**Status:** ⬜ NOT STARTED (may run parallel to 13; sequence after 0)
**Resume notes:** —
**Status:** ✅ DONE — landed `17ec6eee`.
**Resume notes:** Shipped `serving_cast(psi_steady, certificate, verdict, *, tol=1e-6) → ServingState` + `ServingCastError` in `core/physics/cognitive_lifecycle.py` (+ `__all__`), pinned by `tests/test_adr_0244_serving_cast.py` (10 tests). The **single explicit** f64→f32 down-cast at the certified egress hand-off: fail-closed casts only a state that validates as a finite 32-vector, matches its certificate `psi_digest`, and was `verdict.admitted`; then precision-checks the f32 result (`cast_error = max|f64f32|`, unit-norm) and fails closed on an f32 cliff (`f32_precision_insufficient`). **f64 stays source of truth**`psi_steady` + the digest chain are untouched; `ServingState` carries provenance back (`source_psi_digest`, `certificate_id`, measured `cast_error`) for audit. Measured on a real certified outcome: `cast_error ≈ 1.2e-8`, `unit_norm_f32 ≈ 0.99999998` (well within tol). Off-serve (A-04 guard test: `chat.runtime` never imports it); f64 kept inside relaxation/eigendecomp. Independent of 13.
### Phase 5 — §2.7 residual + §2.9 adoption + §2.10 audit + ADR-0245 §3 gate
**Objective:** finish semantic-rigor residuals, wire the allocator, close the 0245 acceptance gate.
@ -231,6 +231,7 @@ Forward-looking mechanical-sympathy items from the critique — a separate optim
- **2026-07-17****Phase 2b landed `c7e2b3b6`.** Runtime wiring: `identity_wave_gate` flag (`core/config.py`), two flag-gated touches in `chat/runtime.py` (`wave_field=final_state.F` at the identity check; post-verdict boundary supplement + geometric refusal fold), wave telemetry in `chat/telemetry.py` (emitted only when `wave_mode_active`). **Integration bug caught + fixed:** the live versor carries boost (e5) components → `F aᵢ F̃` is NOT Euclidean-norm-preserving → un-normalized leakage/self-align ran to 5.16 / 4.75. Fixed with per-axis normalization in `identity_manifold.py` (leakage fraction ∈ [0,1], signed cosine ∈ [1,1]; Phase-1 unit-rotor results unchanged; boost test added; §4a updated). Gate: 74 targeted + smoke 176 + fast lane **11883 passed, 109 skipped** (8:26) — flag-off byte-identity confirmed. **Open reality for Phase 3:** value axes are placeholders (e1/e2/e3), so whether the geometric gate DISCRIMINATES adversarial vs benign is an empirical question — 2c measures it. Next: 2c (ablation eval).
- **2026-07-17****Phase 2c landed `c0ff4720` → Phase 2 COMPLETE.** `evals/adr_0244_identity_gate/` (detection-value ablation) + `tests/test_adr_0244_identity_gate_eval.py` (6 tests) + CLI. Measured: the wave gate separates a geometric-attack panel (2 inversions caught by orientation, 4 tilts/boosts caught by subspace leakage) from an aligned in-subspace panel — all attacks flagged, all aligned admitted, `min_attack_signal 0.35 > max_aligned_leakage 0.0`; wave adds detection value 6-vs-0 over the geometry-blind legacy path. Honest caveat baked in: separation is on the *designed* geometric signal; real-encoder separation is empirical (Phase 3). Additive-only (evals/ + test), off-serving (quarantine green). Gate: smoke 176 + fast lane. Next: Phase 3 (γ_id calibration).
- **2026-07-17****Phase 3 landed `f3702ad4` → calibration built + certified; live flag flip BLOCKED by empirical non-separation.** `evals/adr_0244_gamma_calibration/` + `tests/test_adr_0244_gamma_calibration.py` (9 tests). The bracketed-local Fibonacci search certifies `γ* = 0.2126624458513829` (cert `0079b5f201fbf616…`) separating the geometric attack signal; pinned as `identity._WAVE_LEAKAGE_BOUND` and **decoupled the wave path from `alignment_threshold`**. **Then measured the crux the whole arc deferred:** real benign live turns do NOT preserve `span(e1,e2,e3)` — leakage 0.140.81 (mean 0.55), self-align to 0.52, **12/13 benign false-refused at γ***, best-achievable balanced error **0.346**. So the calibration certifies `flag_flip_authorized=False`; **`identity_wave_gate` stays OFF**. Root cause: the shipped value axes are *nominal basis vectors, not dynamically-preserved eigenmodes* — no dynamical anchoring in the current field evolution; the fix is the **ADR-0246 induced-action programme** (brief already merged). The wave gate is validated, correctly-off scaffolding. A slow drift-guard test canaries any future change that starts preserving identity. Gate: smoke 176 + fast lane + 59 targeted (incl. wave/ablation/runtime unchanged). Next: Phase 4 (§2.5 cast) — independent of 13.
- **2026-07-17****Phase 4 landed `17ec6eee` → §2.5 / 0245 §2.2 serving cast COMPLETE.** `serving_cast(psi_steady, certificate, verdict) → ServingState` + `ServingCastError` in `core/physics/cognitive_lifecycle.py`; `tests/test_adr_0244_serving_cast.py` (10 tests). The single explicit, fail-closed f64→f32 down-cast at the certified egress: casts only a validated / digest-matched / admitted state; precision-checks the f32 result and fails closed on a cliff (`f32_precision_insufficient`); keeps f64 as source of truth (digest chain untouched) and carries audit provenance. Measured `cast_error ≈ 1.2e-8`. Off-serve (A-04 guard green). Gate: smoke 176 + fast lane + 10 targeted. Next: Phase 5 (§2.7 residual + §2.9 allocator + §2.10 audit + 0245 §3 gate).
---
@ -242,10 +243,10 @@ Forward-looking mechanical-sympathy items from the critique — a separate optim
| 1 | §2.1 identity manifold primitive (operator-preservation) | ✅ DONE | `3c3d2c29` |
| 2 | §2.2 fail-closed gate + boundary_ids + C_id + telemetry + eval | ✅ DONE | `1c7ea26e`+`c7e2b3b6`+`c0ff4720` |
| 3 | §2.4 γ_id calibration (certified; **live flip BLOCKED — flag stays OFF**) | ✅ DONE | `f3702ad4` |
| 4 | §2.5 / 0245 §2.2 serving-boundary cast | ⬜ NOT STARTED | — |
| 4 | §2.5 / 0245 §2.2 serving-boundary cast | ✅ DONE | `17ec6eee` |
| 5 | §2.7 residual + §2.9 wiring + §2.10 audit + 0245 §3 gate | ⬜ NOT STARTED | — |
| 6 | Close-out (2 acceptance packets + 2 ratified flips) | ⬜ NOT STARTED | — |
**▶ NEXT: Phase 4 — §2.5 / ADR-0245 §2.2 serving-boundary f64→f32 cast** (lifecycle-internal, `core/physics/cognitive_lifecycle.py`; independent of 13, depends only on Phase 0). Then Phase 5 (residuals + allocator + 0245 §3 gate), then Phase 6 (close-out).
**▶ NEXT: Phase 5 — §2.7 residual (3 contemplation content-id sites) + §2.9 allocator wiring + §2.10 audit-confirm + ADR-0245 §3 acceptance gate** (f32 ≥10× speedup benchmark + `_content_id` collision-resistance proof). Then Phase 6 (close-out: 2 acceptance packets + user-ratified status flips + cleanup).
**Phase 3 headline for resumers:** the γ_id calibration machinery is built + certified, but the empirical finding is that **the live serving flag cannot honestly be flipped on** — real benign traffic doesn't preserve the nominal identity subspace (best balanced error 0.346), so the gate would mass-refuse. `identity_wave_gate` stays OFF; the live gate awaits the ADR-0246 induced-action work. This is a **true architectural-gap discovery**, faithfully honoring the plan's conditional ("flip *once evidenced*"), not a deviation.

View file

@ -0,0 +1,135 @@
"""ADR-0244 §2.5 / ADR-0245 §2.2 — governed f64→f32 serving-boundary cast.
Pins the single explicit down-cast at the certified lifecycle egress: it casts
only a certified, admitted, digest-matched state; keeps f64 as the source of
truth (the digest chain is untouched); is precision-checked (fails closed on an
f32 cliff) and deterministic.
"""
from __future__ import annotations
import numpy as np
import pytest
from algebra.cl41 import N_COMPONENTS
from core.physics.cognitive_lifecycle import (
RelaxationResult,
ServingCastError,
ServingState,
compile_quadratic_well,
egress_gate,
relax_to_ground,
serving_cast,
)
def _unit(v: np.ndarray) -> np.ndarray:
return v / np.linalg.norm(v)
def _onehot(i: int) -> np.ndarray:
v = np.zeros(N_COMPONENTS, dtype=np.float64)
v[i] = 1.0
return v
def _certified_outcome():
target = _unit(_onehot(1) + _onehot(6))
ham = compile_quadratic_well(target, curvature=1.0)
result = relax_to_ground(_unit(_onehot(1) + 0.3 * _onehot(6)), ham)
verdict = egress_gate(result.psi_steady, result.certificate)
assert verdict.admitted is True
return result, verdict
def test_cast_of_certified_state_is_f32_and_within_tolerance():
result, verdict = _certified_outcome()
state = serving_cast(result.psi_steady, result.certificate, verdict)
assert isinstance(state, ServingState)
assert state.psi_f32.dtype == np.dtype("<f4")
assert state.psi_f32.shape == (N_COMPONENTS,)
assert state.cast_error < 1e-6 # documented f32 parity tolerance
assert abs(state.unit_norm_f32 - 1.0) < 1e-6
# provenance links back to the f64 certificate
assert state.source_psi_digest == result.certificate.psi_digest
assert state.certificate_id == result.certificate.certificate_id
def test_f64_remains_the_source_of_truth():
result, verdict = _certified_outcome()
digest_before = result.certificate.psi_digest
serving_cast(result.psi_steady, result.certificate, verdict)
# neither the state nor its certificate/digest is mutated by the cast
assert result.psi_steady.dtype == np.float64
assert result.certificate.psi_digest == digest_before
def test_cast_is_deterministic():
result, verdict = _certified_outcome()
a = serving_cast(result.psi_steady, result.certificate, verdict)
b = serving_cast(result.psi_steady, result.certificate, verdict)
assert np.array_equal(a.psi_f32, b.psi_f32)
assert a.cast_error == b.cast_error and a.unit_norm_f32 == b.unit_norm_f32
def test_uncertified_verdict_is_never_served():
result, _ = _certified_outcome()
class _Refused:
admitted = False
reason = "relaxation_not_certified"
with pytest.raises(ServingCastError, match="uncertified_state_not_served"):
serving_cast(result.psi_steady, result.certificate, _Refused())
def test_digest_mismatch_refuses():
result, verdict = _certified_outcome()
ham = compile_quadratic_well(_unit(_onehot(2) + _onehot(6)), curvature=1.0)
other = relax_to_ground(_unit(_onehot(2) + 0.2 * _onehot(6)), ham)
# a state that is not the one the certificate addresses must not be served
with pytest.raises(ServingCastError, match="certificate_state_mismatch"):
serving_cast(other.psi_steady, result.certificate, verdict)
def test_malformed_state_refuses_before_casting():
result, verdict = _certified_outcome()
with pytest.raises(ServingCastError, match="bad_shape"):
serving_cast(np.zeros(16, dtype=np.float64), result.certificate, verdict)
bad = np.array(result.psi_steady, dtype=np.float64, copy=True)
bad[0] = np.nan
with pytest.raises(ServingCastError, match="non_finite"):
serving_cast(bad, result.certificate, verdict)
def test_precision_sufficiency_guard_is_enforced():
# For a unit versor f32 is sufficient, so the guard passes in normal use.
# Tightening tol below f32's own rounding proves the guard is live (fails
# closed on a precision cliff) rather than decorative.
result, verdict = _certified_outcome()
with pytest.raises(ServingCastError, match="f32_precision_insufficient"):
serving_cast(result.psi_steady, result.certificate, verdict, tol=0.0)
def test_serving_state_as_dict_is_audit_ready():
result, verdict = _certified_outcome()
d = serving_cast(result.psi_steady, result.certificate, verdict).as_dict()
assert d["certificate_id"] == result.certificate.certificate_id
assert d["source_psi_digest"] == result.certificate.psi_digest
assert d["dtype"] == "float32"
assert d["cast_error"] < 1e-6
def test_cast_never_imported_into_serve_hot_path():
# A-04: cognitive_lifecycle stays off the chat serve path.
import inspect
import chat.runtime
assert "serving_cast" not in inspect.getsource(chat.runtime)
def test_relaxation_result_type_is_unchanged():
result, _ = _certified_outcome()
assert isinstance(result, RelaxationResult)
assert result.psi_steady.flags.writeable is False