test(identity): IdentityCheck gate wiring, IdentityScore invariants, TurnEvent field coverage

This commit is contained in:
Shay 2026-05-14 14:30:56 -07:00 committed by GitHub
parent 1343171fab
commit 9f2b2acbf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

178
tests/test_identity_gate.py Normal file
View file

@ -0,0 +1,178 @@
"""tests/test_identity_gate.py — Tests for IdentityCheck gate wiring.
Covers:
- IdentityCheck.check() returns an IdentityScore for a valid trajectory
- IdentityScore.score is a float in [0, 1]
- IdentityScore.flagged is a bool
- IdentityScore.value and .alignment property aliases work
- IdentityScore.axes_evaluated returns a sorted list
- ChatRuntime.turn_log[-1].identity_score is populated after chat()
- ChatRuntime.turn_log[-1].flagged matches response.flagged
- TurnEvent.elaboration field exists and is Optional[str]
- A response that should not be flagged has flagged=False
"""
from __future__ import annotations
import pytest
from core.physics.identity import (
IdentityCheck,
IdentityManifold,
IdentityScore,
TurnEvent,
ValueAxis,
)
from core.physics.reasoning import ReasoningTrajectory, TrajectoryOperator
import numpy as np
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_manifold() -> IdentityManifold:
return IdentityManifold(
value_axes=(
ValueAxis(
name="truthfulness",
direction=np.array([1.0, 0.0, 0.0], dtype=np.float32),
weight=1.0,
),
ValueAxis(
name="coherence",
direction=np.array([0.0, 1.0, 0.0], dtype=np.float32),
weight=1.0,
),
),
)
def _make_trajectory(n_steps: int = 4) -> ReasoningTrajectory:
"""Minimal trajectory with n_steps identity operators."""
ops = [
TrajectoryOperator(
versor=np.ones(32, dtype=np.float32) / np.sqrt(32),
step=i,
)
for i in range(n_steps)
]
return ReasoningTrajectory(operators=tuple(ops), turn=0)
# ---------------------------------------------------------------------------
# IdentityScore invariants
# ---------------------------------------------------------------------------
class TestIdentityScore:
def test_score_is_float_in_unit_interval(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
trajectory = _make_trajectory()
score = check.check(trajectory)
assert isinstance(score, IdentityScore)
assert 0.0 <= score.score <= 1.0
def test_flagged_is_bool(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
score = check.check(_make_trajectory())
assert isinstance(score.flagged, bool)
def test_value_alias_matches_score(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
score = check.check(_make_trajectory())
assert score.value == score.score
def test_alignment_is_float_in_unit_interval(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
score = check.check(_make_trajectory())
assert 0.0 <= score.alignment <= 1.0
def test_axes_evaluated_is_sorted_list(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
score = check.check(_make_trajectory())
axes = score.axes_evaluated
assert isinstance(axes, list)
assert axes == sorted(axes)
def test_deviation_axes_is_frozenset_of_str(self):
manifold = _make_manifold()
check = IdentityCheck(manifold=manifold)
score = check.check(_make_trajectory())
assert isinstance(score.deviation_axes, frozenset)
for ax in score.deviation_axes:
assert isinstance(ax, str)
# ---------------------------------------------------------------------------
# TurnEvent field invariants
# ---------------------------------------------------------------------------
class TestTurnEventFields:
def test_elaboration_field_exists_and_is_optional_str(self):
"""TurnEvent.elaboration must be Optional[str] — verify field presence."""
import dataclasses
fields = {f.name: f for f in dataclasses.fields(TurnEvent)}
assert "elaboration" in fields, "TurnEvent missing elaboration field"
# Default must be None.
# We can't instantiate without all required fields, so just check the
# default value via the field descriptor.
field_obj = fields["elaboration"]
assert field_obj.default is None
def test_identity_score_field_is_optional(self):
import dataclasses
fields = {f.name: f for f in dataclasses.fields(TurnEvent)}
assert "identity_score" in fields
def test_flagged_field_exists(self):
import dataclasses
fields = {f.name: f for f in dataclasses.fields(TurnEvent)}
assert "flagged" in fields
# ---------------------------------------------------------------------------
# ChatRuntime integration: identity_score wired into turn_log
# ---------------------------------------------------------------------------
class TestChatRuntimeIdentityWiring:
"""These tests instantiate a live ChatRuntime and check identity gate wiring.
Skipped if ChatRuntime cannot be instantiated (missing language pack data).
"""
@pytest.fixture(autouse=True)
def runtime(self):
try:
from chat.runtime import ChatRuntime
self._runtime = ChatRuntime()
except Exception as exc:
pytest.skip(f"ChatRuntime not available: {exc}")
def test_turn_log_populated_after_chat(self):
self._runtime.chat("truth", max_tokens=4)
assert len(self._runtime.turn_log) >= 1
def test_identity_score_is_identityscore_or_none(self):
self._runtime.chat("light", max_tokens=4)
ev = self._runtime.turn_log[-1]
assert ev.identity_score is None or isinstance(ev.identity_score, IdentityScore)
def test_flagged_matches_response(self):
response = self._runtime.chat("covenant", max_tokens=4)
ev = self._runtime.turn_log[-1]
assert ev.flagged == response.flagged
def test_elaboration_is_none_or_str(self):
self._runtime.chat("word", max_tokens=8)
ev = self._runtime.turn_log[-1]
assert ev.elaboration is None or isinstance(ev.elaboration, str)
def test_unflagged_response_has_non_empty_surface(self):
response = self._runtime.chat("beginning", max_tokens=8)
if not response.flagged:
assert response.surface, "Surface must not be empty for unflagged response"