Files
electra-archon/models/state.py
Allegro bcf8c31270 feat(state): Implement State Schema for SEED Architecture (Issue #3)
- Add JSON schema at schemas/state.json with full validation rules
- Implement State, StateType, StateMetadata dataclasses in models/state.py
- Support immutable state objects with versioning and TTL
- Include serialization/deserialization (JSON and dict)
- Add next_version() for state history chaining
- Comprehensive test suite with 19 tests (100% pass)
- Full documentation at docs/state-schema.md

Features:
- UUID validation for all ID fields
- Optimistic locking via version numbers
- Flexible payload structure
- Metadata support (source, provenance, tags)
- Expiration checking via TTL
- Complete type hints

Closes Issue #3
2026-04-02 19:59:35 +00:00

234 lines
7.3 KiB
Python

"""
State Model for SEED Architecture
This module defines the State dataclass and related utilities for the
State component of the SEED (State-Event-Entity-Domain) architecture.
"""
from __future__ import annotations
import json
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from enum import Enum, auto
from typing import Any, Optional, Dict, List
from pathlib import Path
class StateType(Enum):
"""Enumeration of valid state types."""
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
ARCHIVED = "archived"
DELETED = "deleted"
@dataclass(frozen=True)
class StateMetadata:
"""Metadata associated with a state record."""
source: str = "unknown"
provenance: Optional[str] = None
created_by: Optional[str] = None
tags: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
"""Convert metadata to dictionary."""
return {
"source": self.source,
"provenance": self.provenance,
"created_by": self.created_by,
"tags": self.tags
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> StateMetadata:
"""Create metadata from dictionary."""
return cls(
source=data.get("source", "unknown"),
provenance=data.get("provenance"),
created_by=data.get("created_by"),
tags=data.get("tags", [])
)
@dataclass(frozen=True)
class State:
"""
Immutable State record for SEED architecture.
Attributes:
id: Unique identifier for this state record (UUID v4)
entity_id: Reference to the entity this state belongs to
state_type: The type/category of this state
payload: Flexible JSON payload containing state-specific data
timestamp: ISO 8601 timestamp when this state was recorded
version: Version number for optimistic locking
metadata: Additional metadata about this state record
previous_state_id: Reference to the previous state (None if first)
ttl: Time-to-live in seconds (None for no expiration)
"""
id: str
entity_id: str
state_type: StateType
payload: Dict[str, Any]
timestamp: datetime
version: int
metadata: StateMetadata = field(default_factory=StateMetadata)
previous_state_id: Optional[str] = None
ttl: Optional[int] = None
def __post_init__(self):
"""Validate state after initialization."""
# Validate UUIDs
try:
uuid.UUID(self.id)
uuid.UUID(self.entity_id)
if self.previous_state_id:
uuid.UUID(self.previous_state_id)
except ValueError as e:
raise ValueError(f"Invalid UUID format: {e}")
# Validate version
if self.version < 1:
raise ValueError("Version must be >= 1")
# Validate TTL
if self.ttl is not None and self.ttl < 0:
raise ValueError("TTL must be non-negative")
def to_dict(self) -> Dict[str, Any]:
"""Convert state to dictionary representation."""
return {
"id": self.id,
"entity_id": self.entity_id,
"state_type": self.state_type.value,
"payload": self.payload,
"timestamp": self.timestamp.isoformat(),
"version": self.version,
"metadata": self.metadata.to_dict(),
"previous_state_id": self.previous_state_id,
"ttl": self.ttl
}
def to_json(self, indent: Optional[int] = None) -> str:
"""Serialize state to JSON string."""
return json.dumps(self.to_dict(), indent=indent, default=str)
@classmethod
def create(
cls,
entity_id: str,
state_type: StateType,
payload: Dict[str, Any],
version: int = 1,
metadata: Optional[StateMetadata] = None,
previous_state_id: Optional[str] = None,
ttl: Optional[int] = None
) -> State:
"""
Factory method to create a new State with auto-generated ID and timestamp.
Args:
entity_id: The entity this state belongs to
state_type: Type of state
payload: State data payload
version: Version number (default: 1)
metadata: Optional metadata
previous_state_id: Link to previous state
ttl: Optional time-to-live in seconds
Returns:
New State instance
"""
return cls(
id=str(uuid.uuid4()),
entity_id=entity_id,
state_type=state_type,
payload=payload,
timestamp=datetime.now(timezone.utc),
version=version,
metadata=metadata or StateMetadata(),
previous_state_id=previous_state_id,
ttl=ttl
)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> State:
"""Create State from dictionary representation."""
return cls(
id=data["id"],
entity_id=data["entity_id"],
state_type=StateType(data["state_type"]),
payload=data["payload"],
timestamp=datetime.fromisoformat(data["timestamp"].replace('Z', '+00:00')),
version=data["version"],
metadata=StateMetadata.from_dict(data.get("metadata", {})),
previous_state_id=data.get("previous_state_id"),
ttl=data.get("ttl")
)
@classmethod
def from_json(cls, json_str: str) -> State:
"""Deserialize State from JSON string."""
return cls.from_dict(json.loads(json_str))
def is_expired(self) -> bool:
"""Check if this state has expired based on TTL."""
if self.ttl is None:
return False
age = (datetime.now(timezone.utc) - self.timestamp).total_seconds()
return age > self.ttl
def next_version(self, new_payload: Dict[str, Any]) -> State:
"""
Create a new State representing the next version of this state.
Args:
new_payload: Updated payload for the new state
Returns:
New State instance with incremented version
"""
return State.create(
entity_id=self.entity_id,
state_type=self.state_type,
payload=new_payload,
version=self.version + 1,
metadata=self.metadata,
previous_state_id=self.id,
ttl=self.ttl
)
def load_schema() -> Dict[str, Any]:
"""Load the JSON schema for validation."""
schema_path = Path(__file__).parent.parent / "schemas" / "state.json"
with open(schema_path, 'r') as f:
return json.load(f)
# Example usage
if __name__ == "__main__":
# Create a sample state
state = State.create(
entity_id=str(uuid.uuid4()),
state_type=StateType.ACTIVE,
payload={"status": "running", "progress": 75},
metadata=StateMetadata(
source="electra-archon",
created_by="electra",
tags=["seed", "priority"]
)
)
print("Created State:")
print(state.to_json(indent=2))
print(f"\nIs expired: {state.is_expired()}")
# Create next version
next_state = state.next_version({"status": "running", "progress": 90})
print(f"\nNext version: {next_state.version}")
print(f"Previous state ID: {next_state.previous_state_id}")