# domain/step_types.py - V7.4 (TYPED DOMAIN CONTRACTS) """ V7.4: Typed SignedStep — Domain-Aware Validation Contract. Replaces the raw dict `{"id", "hash", "expression"}` with a typed dataclass that carries a StepType so the ConsistencyGate validates by semantic domain, not by string shape. CTO directive: - expression → display string only (for renderer injection) - payload → semantic truth (list[str] for geometry, str for algebraic) - step_type → routing key for ConsistencyGate dispatch Backwards compatibility: SignedStep emulates a dict fully so ALL existing dict-access code (step["id"], step.get("hash"), "id" in step, step.items()) in pedagogical_renderer.py / semantic_bank.py continues to work unchanged. """ from enum import Enum from dataclasses import dataclass, asdict from typing import Any, Optional class StepType(Enum): ALGEBRAIC = "algebraic" # SymPy-parseable expression — validated with sympify GEOMETRY = "geometry" # Points, distances, labels — validated structurally NUMERIC = "numeric" # Pure numeric results — future use @dataclass class SignedStep: """ V7.4: Typed, SHA256-signed computation step. Fields: id — step identifier used as {{id}} placeholder in renderer expression — human-readable display string (injected into pedagogy text) payload — semantic truth: list[str] for geometry, str for algebraic step_type — StepType enum — routes ConsistencyGate validation hash — SHA256 digest seeded with canonical form + problem_id + step_id """ id: str expression: str payload: Any step_type: StepType hash: str # ── Full dict emulation (CTO requirement) ───────────────────────────────── # Allows ALL existing code using step["key"], step.get(...), "key" in step, # step.items() to work without any changes. _FIELDS = ("id", "expression", "payload", "step_type", "hash") def __getitem__(self, key: str): if key not in self._FIELDS: raise KeyError(key) return getattr(self, key) def get(self, key: str, default=None): try: return self[key] except KeyError: return default def keys(self): return list(self._FIELDS) def items(self): return [(k, self[k]) for k in self._FIELDS] def __contains__(self, key: object) -> bool: return key in self._FIELDS