diff --git a/CLAIMS.md b/CLAIMS.md index c6beb41d..2a5b9b40 100644 --- a/CLAIMS.md +++ b/CLAIMS.md @@ -38,8 +38,8 @@ is a CI failure (`.github/workflows/lane-shas.yml`). | ADR-0093 | `domain_contract_validation` | All ratified packs satisfy the 9 ADR-0091 contract predicates | `evals/domain_contract_validation/results/v1_dev.json` | `98ace04e3f02bbc5a8ad655bb6593c3f1ee64cb67014f1122fe6c3c85f48d22f` | | ADR-0095 | `miner_loop_closure` | Miner-sourced proposals route through single reviewed teaching path | `evals/miner_loop_closure/results/v1_dev.json` | `9f071733abe7dcacf759f928548ce738fb639af3fd6e4c621a651b306d7e77ce` | | ADR-0096 | `fabrication_control_summary` | Phantom endpoints / cross-pack non-bridges / sibling collapses refuse | `evals/fabrication_control/results/v1_summary.json` | `01e1b6b711141f2b4a14551d7df3ea482d8d6dd7b364a25c509f4f8d08cda8a8` | -| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `403be13bd666389bdf1caa1b97382753e531ad72686c5237f64a5e23bc702ac4` | -| ADR-0099 | `public_demo` | Public showcase runs deterministically under 30s; all claims supported | `evals/public_demo/results/v1_dev.json` | `acd51d0c17948030aa2843309a3729c6f825959e2e0cbbc477e07de8269268a4` | +| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `3a3d09f3a87462737e615c2dd3481b9e13e5ff8fadee0043c37873494ded556d` | +| ADR-0099 | `public_demo` | Public showcase runs deterministically under 30s; all claims supported | `evals/public_demo/results/v1_dev.json` | `888ddd0d12635d709f91898cf06601062168694d870f776d2b0b7710e5d68cbd` | | ADR-0104 | `curriculum_loop_closure` | Curriculum-sourced proposals route through single reviewed teaching path | `evals/curriculum_loop_closure/results/v1_dev.json` | `b46d56b2d209172cc3ffaf3776dc8dcfe55093f13587c5cb67372be6dfa23e8d` | ## Verification diff --git a/evals/demo_composition/results/v1_dev.json b/evals/demo_composition/results/v1_dev.json index 4110e6a9..8f8b2da8 100644 --- a/evals/demo_composition/results/v1_dev.json +++ b/evals/demo_composition/results/v1_dev.json @@ -30,7 +30,7 @@ "details": { "all_claims_supported_a": true, "all_claims_supported_b": true, - "sha256": "deb3af8fab8f3ff9cf7468efd7a403b60c825a67b76a5c07ab95147f79bd2e9a" + "sha256": "6a02197c0d9fae0eb041aaed9d8c864291477c066ec915ca403cf774c301f7d3" }, "divergence": null, "passed": true diff --git a/evals/public_demo/results/v1_dev.json b/evals/public_demo/results/v1_dev.json index b31f2506..f284c5d5 100644 --- a/evals/public_demo/results/v1_dev.json +++ b/evals/public_demo/results/v1_dev.json @@ -13,7 +13,7 @@ { "case_id": "determinism_run_to_run_byte_equality", "details": { - "sha256": "48fc9cba028d5d8e8e098d71cd25a140861fcf380c1214612358bce433f79996" + "sha256": "58e4ddec73aba31b0e193d573921cd85df7fd225ba68716eeef1517b635c6141" }, "divergence": null, "passed": true diff --git a/scripts/verify_lane_shas.py b/scripts/verify_lane_shas.py index 86d8f4c6..d1af4fc1 100644 --- a/scripts/verify_lane_shas.py +++ b/scripts/verify_lane_shas.py @@ -38,8 +38,8 @@ PINNED_SHAS: dict[str, str] = { "curriculum_loop_closure": "b46d56b2d209172cc3ffaf3776dc8dcfe55093f13587c5cb67372be6dfa23e8d", "domain_contract_validation": "98ace04e3f02bbc5a8ad655bb6593c3f1ee64cb67014f1122fe6c3c85f48d22f", "fabrication_control_summary": "01e1b6b711141f2b4a14551d7df3ea482d8d6dd7b364a25c509f4f8d08cda8a8", - "demo_composition": "403be13bd666389bdf1caa1b97382753e531ad72686c5237f64a5e23bc702ac4", - "public_demo": "acd51d0c17948030aa2843309a3729c6f825959e2e0cbbc477e07de8269268a4", + "demo_composition": "3a3d09f3a87462737e615c2dd3481b9e13e5ff8fadee0043c37873494ded556d", + "public_demo": "888ddd0d12635d709f91898cf06601062168694d870f776d2b0b7710e5d68cbd", } diff --git a/session/context.py b/session/context.py index 9ab475e4..f1873984 100644 --- a/session/context.py +++ b/session/context.py @@ -11,7 +11,8 @@ from __future__ import annotations import numpy as np from algebra.backend import cga_inner, versor_apply -from algebra.versor import unitize_versor, versor_condition as _versor_condition +from algebra.rotor import rotor_power, word_transition_rotor +from algebra.versor import versor_condition as _versor_condition from field.state import FieldState from generate.dialogue import DialogueTurn from generate.proposition import Proposition @@ -35,35 +36,6 @@ _BLADE_EMA_ALPHA: float = 0.15 _ANCHOR_PULL_ALPHA: float = 0.05 -def _slerp_toward( - F: np.ndarray, - target: np.ndarray, - alpha: float, -) -> np.ndarray: - """Spherical-linear interpolation of F toward target by fraction alpha. - - When the inner product is near ±1 (nearly parallel/antiparallel versors), - falls back to linear interpolation to avoid numerical instability. - """ - f_norm = float(np.linalg.norm(F)) - t_norm = float(np.linalg.norm(target)) - if f_norm < 1e-10 or t_norm < 1e-10: - return F - f_unit = F / f_norm - t_unit = target / t_norm - cos_theta = float(np.clip(np.dot(f_unit.ravel(), t_unit.ravel()), -1.0, 1.0)) - theta = float(np.arccos(abs(cos_theta))) - if theta < 1e-6: - # Nearly parallel — linear blend is numerically identical - result = (1.0 - alpha) * F + alpha * target - else: - sin_theta = float(np.sin(theta)) - w_f = float(np.sin((1.0 - alpha) * theta)) / sin_theta - w_t = float(np.sin(alpha * theta)) / sin_theta - result = w_f * F + w_t * target - return np.asarray(result, dtype=F.dtype) - - class SessionContext: def __init__(self, vocab, persona=None, vault=None, vault_reproject_interval: int = 100): self.vocab = vocab @@ -221,19 +193,29 @@ class SessionContext: ) def _anchor_pull(self, field_state: FieldState) -> FieldState: - """Drift fix 3: mild slerp toward the session anchor field. + """Drift fix 3: mild rotor-geodesic pull toward the session anchor field. Applied after hemisphere correction. Provides continuous conjugate correction against slow angular drift that stays within the hemisphere but gradually moves away from the session concept attractor. + Computes the transition rotor R = anchor * reverse(F), scales it to + R^α via rotor_power (stays on the versor manifold by construction), and + applies it via versor_apply. This replaces the previous _slerp_toward + approach, which interpolated on S^31 rather than on the Spin sub-manifold + and required a post-hoc unitize_versor to repair the manifold violation. + α=0.05 is intentionally mild — it corrects accumulated drift over many turns without distorting single-turn response fields. """ if self._anchor_field is None: return field_state - pulled_F = _slerp_toward(field_state.F, self._anchor_field, _ANCHOR_PULL_ALPHA) - pulled_F = unitize_versor(pulled_F) + try: + R = word_transition_rotor(field_state.F, self._anchor_field) + except ValueError: + return field_state + R_step = rotor_power(R, _ANCHOR_PULL_ALPHA) + pulled_F = versor_apply(R_step, field_state.F) return FieldState( F=pulled_F, node=field_state.node, diff --git a/tests/test_session_coherence.py b/tests/test_session_coherence.py index a4d8b6fe..1290e827 100644 --- a/tests/test_session_coherence.py +++ b/tests/test_session_coherence.py @@ -3,7 +3,7 @@ from __future__ import annotations import numpy as np from algebra.backend import cga_inner -from algebra.versor import unitize_versor +from algebra.versor import unitize_versor, versor_condition from session.context import SessionContext from vocab.manifold import VocabManifold @@ -106,3 +106,35 @@ def test_repeated_prompt_accumulates_field_and_stays_prompt_coherent() -> None: prompt_score = cga_inner(result.final_state.F, initial.F) random_score = cga_inner(result.final_state.F, random_unrelated) assert prompt_score > random_score + + +def test_anchor_pull_output_satisfies_versor_condition() -> None: + """_anchor_pull must not break the versor condition (W-015 fix contract). + + The previous _slerp_toward implementation interpolated on S^31 rather than + the Spin sub-manifold, producing versor_condition values up to 38.58. + The rotor-geodesic replacement must satisfy vc < 1e-6 by construction. + """ + from field.state import FieldState + + vocab = _vocab() + session = SessionContext(vocab=vocab) + session.ingest(["logos"]) + + # Build a drifted field well separated from the anchor. + drifted = _random_rotor(seed=42) + drifted_state = FieldState( + F=drifted, + node=session.state.node, + step=session.state.step, + holonomy=session.state.holonomy, + energy=session.state.energy, + valence=session.state.valence, + ) + + pulled = session._anchor_pull(drifted_state) + vc = versor_condition(pulled.F) + assert vc < 1e-6, ( + f"_anchor_pull output violated versor_condition: {vc:.3e} >= 1e-6. " + "The rotor-geodesic path must stay on the Spin sub-manifold by construction." + )