From f8113a38ba7a0ddfd9ed2d221dbd9c8cb1483fba Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 13 May 2026 14:26:24 -0700 Subject: [PATCH] Restore FieldState slots in determinism proof --- field/state.py | 5 +++-- tests/test_determinism_proofs.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/field/state.py b/field/state.py index a0a7c1f2..31afc061 100644 --- a/field/state.py +++ b/field/state.py @@ -4,7 +4,7 @@ FieldState — the complete cognitive field at one moment. Invariant: versor_condition(F) < 1e-6 always. This is checked at injection and maintained structurally by versor_apply(). -FieldState is immutable by design (frozen=True). +FieldState is immutable by design (frozen=True, slots=True). The np.ndarray F is copied and validated at construction — the copy() call is the explicit contract boundary. Callers must not retain a mutable reference to the array passed in and expect coherence. @@ -17,7 +17,7 @@ import numpy as np _EXPECTED_COMPONENTS = 32 -@dataclass(frozen=True) +@dataclass(frozen=True, slots=True) class FieldState: F: np.ndarray # shape (32,) float32 — Cl(4,1) multivector on the versor manifold node: int = 0 # current node index in the vocabulary manifold @@ -28,6 +28,7 @@ class FieldState: # Enforce copy + dtype + shape at the construction boundary. # frozen=True prevents reassignment, but ndarray contents are still # mutable via the array object; copy() here is the defence. + # slots=True closes __dict__ so no incidental attributes can be added. F = np.array(self.F, dtype=np.float32).copy() if F.shape != (_EXPECTED_COMPONENTS,): raise ValueError( diff --git a/tests/test_determinism_proofs.py b/tests/test_determinism_proofs.py index 963af486..5bb6ee10 100644 --- a/tests/test_determinism_proofs.py +++ b/tests/test_determinism_proofs.py @@ -63,6 +63,7 @@ import hashlib import json import struct from copy import deepcopy +from dataclasses import fields from typing import Any import numpy as np @@ -498,7 +499,7 @@ class TestDET10FieldStateIsSingleMultivector: """FieldState must not hold a copy of the input tokens.""" state = inject(["in", "the", "beginning"], _PinVocab()) # No attribute should store the original token list - for attr in vars(state).values(): + for attr in (getattr(state, field.name) for field in fields(state)): if isinstance(attr, (list, tuple)): # Allow small metadata tuples but not token-length sequences assert len(attr) <= 4, (