-
Notifications
You must be signed in to change notification settings - Fork 0
VALIDATION_SCHEMAS
spuentesp edited this page Dec 27, 2025
·
1 revision
Pydantic models for data validation across the MONITOR system.
This document defines Pydantic models for all data structures in MONITOR. These schemas:
- Validate API requests and responses
- Type-check data at runtime
- Document data structures with examples
- Generate OpenAPI/JSON Schema for MCP tools
Key principle: All data crossing layer boundaries must be validated.
from enum import Enum
from typing import Literal
class CanonLevel(str, Enum):
"""Canonization status for most canonical nodes."""
PROPOSED = "proposed"
CANON = "canon"
RETCONNED = "retconned"
class SourceCanonLevel(str, Enum):
"""Canonization status for Source nodes only.
Sources use 'authoritative' instead of 'retconned' because
source documents themselves aren't revised—only facts derived
from them can be retconned.
"""
PROPOSED = "proposed"
CANON = "canon"
AUTHORITATIVE = "authoritative"
class Authority(str, Enum):
"""Who asserted this data (full set for Facts, Events, Entities)."""
SOURCE = "source"
GM = "gm"
PLAYER = "player"
SYSTEM = "system"
class AxiomAuthority(str, Enum):
"""Authority for Axiom nodes only (excludes 'player').
World rules (physics, magic systems) cannot be created by player
actions—only by GM declaration or authoritative sources.
"""
SOURCE = "source"
GM = "gm"
SYSTEM = "system"
class EntityType(str, Enum):
"""Entity classification."""
CHARACTER = "character"
FACTION = "faction"
LOCATION = "location"
OBJECT = "object"
CONCEPT = "concept"
ORGANIZATION = "organization"
class EntityClass(str, Enum):
"""Axiomatic vs Concrete."""
AXIOMATICA = "EntityArchetype"
CONCRETA = "EntityInstance"
class StoryType(str, Enum):
"""Story type."""
CAMPAIGN = "campaign"
ARC = "arc"
EPISODE = "episode"
ONE_SHOT = "one_shot"
class SceneStatus(str, Enum):
"""Scene workflow status."""
ACTIVE = "active"
FINALIZING = "finalizing"
COMPLETED = "completed"
class ProposalStatus(str, Enum):
"""Proposed change status."""
PENDING = "pending"
ACCEPTED = "accepted"
REJECTED = "rejected"
class ProposalType(str, Enum):
"""Type of proposed change."""
FACT = "fact"
ENTITY = "entity"
RELATIONSHIP = "relationship"
STATE_CHANGE = "state_change"
EVENT = "event"
class Speaker(str, Enum):
"""Who is speaking in a turn."""
USER = "user"
GM = "gm"
ENTITY = "entity"from pydantic import BaseModel, Field, field_validator
from datetime import datetime
from uuid import UUID
class CanonicalMetadata(BaseModel):
"""Base metadata for all canonical nodes."""
canon_level: CanonLevel
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
created_at: datetime
@field_validator('confidence')
@classmethod
def validate_confidence(cls, v: float) -> float:
if not 0.0 <= v <= 1.0:
raise ValueError('confidence must be between 0.0 and 1.0')
return vclass EntityArchetypeCreate(BaseModel):
"""Request to create an EntityArchetype."""
universe_id: UUID
name: str = Field(min_length=1, max_length=200)
entity_type: EntityType
description: str = Field(min_length=1, max_length=2000)
properties: dict = Field(default_factory=dict)
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
evidence_refs: list[str] = Field(min_items=1)
model_config = {
"json_schema_extra": {
"examples": [
{
"universe_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Wizard",
"entity_type": "character",
"description": "Practitioner of arcane magic",
"properties": {
"archetype": "wizard",
"default_abilities": ["spellcasting", "ritual magic"]
},
"confidence": 1.0,
"authority": "source",
"evidence_refs": ["source:550e8400-e29b-41d4-a716-446655440001"]
}
]
}
}class EntityInstanceCreate(BaseModel):
"""Request to create an EntityInstance."""
universe_id: UUID
name: str = Field(min_length=1, max_length=200)
entity_type: EntityType
description: str = Field(min_length=1, max_length=2000)
properties: dict = Field(default_factory=dict)
state_tags: list[str] = Field(default_factory=list)
derives_from: UUID | None = None # Optional archetype reference
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
evidence_refs: list[str] = Field(min_items=1)
@field_validator('state_tags')
@classmethod
def validate_state_tags(cls, v: list[str]) -> list[str]:
# Ensure no duplicates
if len(v) != len(set(v)):
raise ValueError('state_tags must not contain duplicates')
return v
model_config = {
"json_schema_extra": {
"examples": [
{
"universe_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Gandalf the Grey",
"entity_type": "character",
"description": "Istari wizard sent to Middle-earth",
"properties": {
"role": "NPC",
"archetype": "wizard"
},
"state_tags": ["alive", "traveling", "wielding_staff"],
"derives_from": "wizard-archetype-uuid",
"confidence": 1.0,
"authority": "source",
"evidence_refs": ["source:550e8400-e29b-41d4-a716-446655440001"]
}
]
}
}from typing import Annotated
from pydantic import Discriminator
class EntityCreate(BaseModel):
"""Polymorphic entity creation request."""
entity_class: EntityClass
data: Annotated[
EntityArchetypeCreate | EntityInstanceCreate,
Discriminator('entity_class')
]class EntityResponse(BaseModel):
"""Response from entity creation."""
entity_id: UUID
canon_level: CanonLevel
created_at: datetime
class EntityFull(CanonicalMetadata):
"""Complete entity data."""
id: UUID
entity_class: EntityClass
universe_id: UUID
name: str
entity_type: EntityType
description: str
properties: dict
state_tags: list[str] = Field(default_factory=list) # Only for Instance
updated_at: datetime | None = None # Only for Instanceclass StateTagChanges(BaseModel):
"""State tag modifications."""
add: list[str] = Field(default_factory=list)
remove: list[str] = Field(default_factory=list)
@field_validator('add', 'remove')
@classmethod
def no_duplicates(cls, v: list[str]) -> list[str]:
if len(v) != len(set(v)):
raise ValueError('no duplicate tags allowed')
return v
class EntityStateUpdate(BaseModel):
"""Update entity state tags."""
entity_id: UUID
state_tag_changes: StateTagChanges
authority: Literal[Authority.GM, Authority.PLAYER, Authority.SYSTEM]
evidence_refs: list[str] = Field(min_items=1)
class EntityStateUpdateResponse(BaseModel):
"""Response from state update."""
entity_id: UUID
new_state_tags: list[str]
fact_ids: list[UUID] # Facts documenting the changesclass FactCreate(BaseModel):
"""Create a canonical fact."""
universe_id: UUID
statement: str = Field(min_length=1, max_length=1000)
time_ref: datetime | None = None
duration: int | None = Field(None, ge=0) # seconds
involved_entity_ids: list[UUID] = Field(min_items=1)
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
evidence_refs: list[str] = Field(min_items=1)
model_config = {
"json_schema_extra": {
"examples": [
{
"universe_id": "550e8400-e29b-41d4-a716-446655440000",
"statement": "Gandalf defeated the Balrog",
"time_ref": "3019-01-15T12:00:00Z",
"involved_entity_ids": [
"gandalf-uuid",
"balrog-uuid"
],
"confidence": 1.0,
"authority": "source",
"evidence_refs": ["source:lotr-fellowship"]
}
]
}
}
class FactResponse(BaseModel):
"""Response from fact creation."""
fact_id: UUID
canon_level: CanonLevel
created_at: datetimeclass EventCreate(BaseModel):
"""Create a canonical event."""
scene_id: UUID | None = None
universe_id: UUID
title: str = Field(min_length=1, max_length=200)
description: str = Field(min_length=1, max_length=2000)
time_ref: datetime | None = None
severity: int = Field(ge=0, le=10)
involved_entity_ids: list[UUID] = Field(min_items=1)
causes_event_ids: list[UUID] = Field(default_factory=list)
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
evidence_refs: list[str] = Field(min_items=1)
class EventResponse(BaseModel):
"""Response from event creation."""
event_id: UUID
canon_level: CanonLevel
created_at: datetimeclass StoryCreate(BaseModel):
"""Create a canonical story container."""
universe_id: UUID
title: str = Field(min_length=1, max_length=200)
story_type: StoryType
theme: str | None = None
premise: str | None = None
parent_story_id: UUID | None = None
start_time_ref: datetime | None = None
class StoryResponse(BaseModel):
"""Response from story creation."""
story_id: UUID
created_at: datetimeclass SourceType(str, Enum):
MANUAL = "manual"
RULEBOOK = "rulebook"
LORE = "lore"
SESSION = "session"
class SourceCreate(BaseModel):
"""Create a canonical source."""
universe_id: UUID
doc_id: str # MongoDB reference
title: str = Field(min_length=1, max_length=200)
edition: str | None = None
provenance: str | None = None # ISBN, URL, etc.
source_type: SourceType
canon_level: Literal[CanonLevel.PROPOSED, CanonLevel.CANON, "authoritative"]
class SourceResponse(BaseModel):
"""Response from source creation."""
source_id: UUID
created_at: datetimeclass StateTagFilter(BaseModel):
"""State tag filtering."""
all_of: list[str] = Field(default_factory=list)
any_of: list[str] = Field(default_factory=list)
none_of: list[str] = Field(default_factory=list)
class EntityQuery(BaseModel):
"""Query entities by filters."""
universe_id: UUID | None = None
entity_type: EntityType | None = None
entity_class: EntityClass | None = None
canon_level: CanonLevel | None = None
state_tags: StateTagFilter | None = None
name_pattern: str | None = None
limit: int = Field(50, ge=1, le=500)
offset: int = Field(0, ge=0)
class EntityQueryResponse(BaseModel):
"""Response from entity query."""
entities: list[EntityFull]
total: intclass TimeRange(BaseModel):
"""Time range filter."""
start: datetime
end: datetime
class FactQuery(BaseModel):
"""Query facts by filters."""
universe_id: UUID | None = None
entity_id: UUID | None = None
time_range: TimeRange | None = None
canon_level: CanonLevel | None = None
authority: Authority | None = None
limit: int = Field(50, ge=1, le=500)
offset: int = Field(0, ge=0)
class FactFull(CanonicalMetadata):
"""Complete fact data."""
id: UUID
universe_id: UUID
statement: str
time_ref: datetime | None
duration: int | None
replaces: UUID | None
class FactQueryResponse(BaseModel):
"""Response from fact query."""
facts: list[FactFull]
total: intclass SceneCreate(BaseModel):
"""Create a new scene."""
story_id: UUID
universe_id: UUID
title: str = Field(min_length=1, max_length=200)
purpose: str | None = None
order: int | None = Field(default=None, ge=0)
location_ref: UUID | None = None # EntityInstance location
participating_entities: list[UUID] = Field(default_factory=list)
class SceneResponse(BaseModel):
"""Response from scene creation."""
scene_id: UUID
status: Literal[SceneStatus.ACTIVE]
created_at: datetimeclass TurnAppend(BaseModel):
"""Append a turn to a scene."""
scene_id: UUID
speaker: Speaker
entity_id: UUID | None = None # Required if speaker is 'entity'
text: str = Field(min_length=1, max_length=10000)
resolution_ref: UUID | None = None
@field_validator('entity_id')
@classmethod
def entity_id_required_for_entity_speaker(cls, v, info):
if info.data.get('speaker') == Speaker.ENTITY and v is None:
raise ValueError('entity_id required when speaker is "entity"')
return v
class TurnResponse(BaseModel):
"""Response from turn append."""
turn_id: UUID
timestamp: datetime
class Turn(BaseModel):
"""Turn data structure."""
turn_id: UUID
speaker: Speaker
entity_id: UUID | None = None
text: str
timestamp: datetime
resolution_ref: UUID | None = Noneclass SceneGet(BaseModel):
"""Get scene request."""
scene_id: UUID
include_turns: bool = True
include_proposals: bool = False
turn_limit: int | None = None # Last N turns
class SceneFull(BaseModel):
"""Complete scene data."""
scene_id: UUID
story_id: UUID
universe_id: UUID
title: str
status: SceneStatus
order: int | None = None
location_ref: UUID | None
participating_entities: list[UUID]
turns: list[Turn] = Field(default_factory=list)
proposed_changes: list[UUID] = Field(default_factory=list)
canonical_outcomes: list[UUID] = Field(default_factory=list)
summary: str | None = None
created_at: datetime
updated_at: datetime
completed_at: datetime | None = Noneclass SceneFinalize(BaseModel):
"""Finalize a scene."""
scene_id: UUID
canonical_outcome_ids: list[UUID] # Neo4j Fact/Event IDs
summary: str = Field(min_length=1, max_length=2000)
class SceneFinalizeResponse(BaseModel):
"""Response from scene finalization."""
scene_id: UUID
status: Literal[SceneStatus.COMPLETED]
completed_at: datetimeclass EvidenceRef(BaseModel):
"""Evidence reference."""
type: Literal["turn", "snippet", "source", "rule"]
ref_id: UUID
class ProposedChangeCreate(BaseModel):
"""Create a proposed change."""
scene_id: UUID
turn_id: UUID | None = None
type: ProposalType
content: dict # Type-specific structure
evidence: list[EvidenceRef] = Field(min_items=1)
confidence: float = Field(ge=0.0, le=1.0)
authority: Authority
model_config = {
"json_schema_extra": {
"examples": [
{
"scene_id": "scene-uuid",
"turn_id": "turn-uuid",
"type": "state_change",
"content": {
"entity_id": "gandalf-uuid",
"tag": "wounded",
"action": "add"
},
"evidence": [
{"type": "turn", "ref_id": "turn-uuid"}
],
"confidence": 0.9,
"authority": "gm"
}
]
}
}
class ProposedChangeResponse(BaseModel):
"""Response from proposal creation."""
proposal_id: UUID
status: Literal[ProposalStatus.PENDING]
created_at: datetimeclass ProposalEvaluate(BaseModel):
"""Evaluate a proposal."""
proposal_id: UUID
decision: Literal[ProposalStatus.ACCEPTED, ProposalStatus.REJECTED]
rationale: str | None = None
canonical_id: UUID | None = None # If accepted
@field_validator('canonical_id')
@classmethod
def canonical_id_required_for_accepted(cls, v, info):
if info.data.get('decision') == ProposalStatus.ACCEPTED and v is None:
raise ValueError('canonical_id required when decision is "accepted"')
return v
class ProposalEvaluateResponse(BaseModel):
"""Response from proposal evaluation."""
proposal_id: UUID
status: ProposalStatus
evaluated_at: datetimeclass ProposedChangeFull(BaseModel):
"""Complete proposed change data."""
proposal_id: UUID
scene_id: UUID
turn_id: UUID | None
type: ProposalType
content: dict
evidence: list[EvidenceRef]
confidence: float
authority: Authority
status: ProposalStatus
rationale: str | None
canonical_id: UUID | None
created_at: datetime
evaluated_at: datetime | Noneclass CharacterMemoryCreate(BaseModel):
"""Create a character memory."""
entity_id: UUID
text: str = Field(min_length=1, max_length=2000)
linked_fact_id: UUID | None = None
scene_id: UUID | None = None
emotional_valence: float = Field(ge=-1.0, le=1.0)
importance: float = Field(ge=0.0, le=1.0)
certainty: float = Field(ge=0.0, le=1.0)
class CharacterMemoryResponse(BaseModel):
"""Response from memory creation."""
memory_id: UUID
created_at: datetime
class CharacterMemoryFull(BaseModel):
"""Complete character memory data."""
memory_id: UUID
entity_id: UUID
text: str
linked_fact_id: UUID | None
scene_id: UUID | None
emotional_valence: float
importance: float
certainty: float
created_at: datetime
last_accessed: datetime
access_count: intclass DocumentCreate(BaseModel):
"""Create a document record."""
source_id: UUID # Neo4j Source
universe_id: UUID
minio_ref: str
title: str = Field(min_length=1, max_length=200)
filename: str
file_type: str
class DocumentResponse(BaseModel):
"""Response from document creation."""
doc_id: UUID
extraction_status: Literal["pending"]
created_at: datetimeclass SnippetCreate(BaseModel):
"""Create a snippet."""
doc_id: UUID
source_id: UUID
text: str = Field(min_length=1, max_length=10000)
page: int | None = None
section: str | None = None
chunk_index: int = Field(ge=0)
class SnippetResponse(BaseModel):
"""Response from snippet creation."""
snippet_id: UUID
created_at: datetimeclass QdrantCollection(str, Enum):
SCENE_CHUNKS = "scene_chunks"
MEMORY_CHUNKS = "memory_chunks"
SNIPPET_CHUNKS = "snippet_chunks"
class SemanticSearchFilters(BaseModel):
"""Qdrant payload filters."""
universe_id: UUID | None = None
entity_id: UUID | None = None
source_id: UUID | None = None
class SemanticSearchRequest(BaseModel):
"""Semantic search request."""
query_text: str = Field(min_length=1, max_length=1000)
collection: QdrantCollection
filters: SemanticSearchFilters | None = None
limit: int = Field(10, ge=1, le=100)
min_score: float = Field(0.0, ge=0.0, le=1.0)
class SemanticSearchResult(BaseModel):
"""Single search result."""
id: UUID
score: float
payload: dict
text: str
class SemanticSearchResponse(BaseModel):
"""Response from semantic search."""
results: list[SemanticSearchResult]class AssembleSceneContextRequest(BaseModel):
"""Request to assemble scene context."""
scene_id: UUID
include_canonical: bool = True
include_narrative: bool = True
include_semantic: bool = True
semantic_query: str | None = None
class CanonicalContext(BaseModel):
"""Canonical data from Neo4j."""
entities: list[EntityFull]
facts: list[FactFull]
relations: list[dict] # Relationship data
class NarrativeContext(BaseModel):
"""Narrative data from MongoDB."""
prior_turns: list[Turn]
scene_summary: str | None
gm_notes: str | None
class RecalledContext(BaseModel):
"""Semantic recall from Qdrant."""
similar_scenes: list[dict]
character_memories: list[CharacterMemoryFull]
rule_excerpts: list[dict]
class ContextMetadata(BaseModel):
"""Context metadata."""
universe_id: UUID
story_id: UUID
scene_id: UUID
timestamp: datetime
class AssembleSceneContextResponse(BaseModel):
"""Response from context assembly."""
canonical: CanonicalContext
narrative: NarrativeContext
recalled: RecalledContext
metadata: ContextMetadataclass CanonizeSceneRequest(BaseModel):
"""Request to canonize a scene."""
scene_id: UUID
evaluate_proposals: bool = True
class CanonizeSceneResponse(BaseModel):
"""Response from scene canonization."""
scene_id: UUID
accepted_proposals: list[UUID]
rejected_proposals: list[UUID]
canonical_fact_ids: list[UUID]
canonical_event_ids: list[UUID]
canonical_entity_ids: list[UUID]from pydantic import field_validator
class EvidenceRefValidator:
"""Validate evidence reference format."""
@field_validator('evidence_refs')
@classmethod
def validate_evidence_refs(cls, v: list[str]) -> list[str]:
"""Ensure evidence refs are in correct format: 'type:uuid'."""
import re
pattern = re.compile(r'^(source|turn|scene|snippet|rule):[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.I)
for ref in v:
if not pattern.match(ref):
raise ValueError(f'Invalid evidence_ref format: {ref}. Expected "type:uuid"')
return vfrom uuid import UUID as StdUUID
def validate_uuid(value: str) -> UUID:
"""Validate UUID string."""
try:
return StdUUID(value)
except ValueError as e:
raise ValueError(f'Invalid UUID format: {value}') from eclass ErrorDetail(BaseModel):
"""Error detail structure."""
code: int
message: str
data: dict | None = None
class APIError(BaseModel):
"""API error response."""
error: ErrorDetail
# Standard errors
class UnauthorizedError(APIError):
"""Agent lacks authority."""
error: ErrorDetail = Field(
default_factory=lambda: ErrorDetail(
code=-32001,
message="Unauthorized: Agent lacks authority for this operation"
)
)
class NotFoundError(APIError):
"""Resource not found."""
error: ErrorDetail = Field(
default_factory=lambda: ErrorDetail(
code=-32002,
message="Not Found: Requested resource does not exist"
)
)
class ValidationError(APIError):
"""Validation failed."""
error: ErrorDetail = Field(
default_factory=lambda: ErrorDetail(
code=-32003,
message="Validation Error: Request data is invalid"
)
)This matrix defines which agents can execute which operations.
| Operation | Allowed Agents | Notes |
|---|---|---|
| CreateUniverse | CanonKeeper | World creation |
| CreateMultiverse | CanonKeeper | World creation |
| CreateStory | CanonKeeper, Orchestrator | Orchestrator for story setup only |
| CreateEntity | CanonKeeper | All entity types |
| UpdateEntity | CanonKeeper | Property/state changes |
| CreateFact | CanonKeeper | Canonization only |
| CreateEvent | CanonKeeper | Canonization only |
| CreateAxiom | CanonKeeper | World rules |
| CreateSource | CanonKeeper | Document registration |
| CreateRelationship | CanonKeeper | Entity relationships |
| LinkEvidence | CanonKeeper | SUPPORTED_BY edges |
| Operation | Allowed Agents | Notes |
|---|---|---|
| CreateScene | Orchestrator | Scene lifecycle |
| UpdateScene | Orchestrator | Status changes |
| FinalizeScene | Orchestrator | After canonization |
| AppendTurn | Narrator | Turn transcription |
| UndoTurn | Orchestrator | Meta-command |
| CreateProposedChange | Narrator, Resolver | Proposing canonical changes |
| EvaluateProposal | CanonKeeper | Accept/reject |
| CreateMemory | MemoryManager | Character memories |
| UpdateMemory | MemoryManager | Memory updates |
| CreateDocument | Indexer | Document ingestion |
| CreateSnippet | Indexer | Text chunking |
| CreateStoryOutline | Orchestrator | Story structure |
| CreateResolution | Resolver | Dice/action results |
| Operation | Allowed Agents | Notes |
|---|---|---|
| EmbedScene | Indexer | Scene vectorization |
| EmbedMemory | Indexer | Memory vectorization |
| EmbedSnippet | Indexer | Document vectorization |
| DeleteVectors | Indexer | Cleanup |
All read operations are available to all agents.
class AuthorityEnforcer:
"""Middleware for authority enforcement."""
WRITE_PERMISSIONS = {
"neo4j_create_fact": ["CanonKeeper"],
"neo4j_create_entity": ["CanonKeeper"],
"neo4j_create_story": ["CanonKeeper", "Orchestrator"],
"mongodb_append_turn": ["Narrator"],
"mongodb_create_proposal": ["Narrator", "Resolver"],
"mongodb_evaluate_proposal": ["CanonKeeper"],
"mongodb_create_memory": ["MemoryManager"],
"qdrant_embed_scene": ["Indexer"],
# ... etc
}
def check_authority(self, agent: str, operation: str) -> bool:
allowed = self.WRITE_PERMISSIONS.get(operation, [])
if not allowed: # Read operation
return True
return agent in allowedfrom monitor.schemas import EntityInstanceCreate, EntityType, Authority
# Create request
request = EntityInstanceCreate(
universe_id=UUID("550e8400-e29b-41d4-a716-446655440000"),
name="Gandalf the Grey",
entity_type=EntityType.CHARACTER,
description="Istari wizard sent to Middle-earth",
properties={
"role": "NPC",
"archetype": "wizard"
},
state_tags=["alive", "traveling"],
confidence=1.0,
authority=Authority.SOURCE,
evidence_refs=["source:550e8400-e29b-41d4-a716-446655440001"]
)
# Validate automatically via Pydantic
assert request.confidence == 1.0
assert "alive" in request.state_tags
# Serialize to JSON for MCP call
request_json = request.model_dump_json()from monitor.schemas import EntityQuery, EntityType, StateTagFilter
# Build query
query = EntityQuery(
universe_id=UUID("550e8400-e29b-41d4-a716-446655440000"),
entity_type=EntityType.CHARACTER,
state_tags=StateTagFilter(
all_of=["alive"],
none_of=["dead", "unconscious"]
),
limit=50
)
# Validation happens automatically
# Query for living charactersfrom monitor.schemas import ProposedChangeCreate, ProposalType, EvidenceRef
proposal = ProposedChangeCreate(
scene_id=UUID("scene-uuid"),
turn_id=UUID("turn-uuid"),
type=ProposalType.STATE_CHANGE,
content={
"entity_id": str(UUID("gandalf-uuid")),
"tag": "wounded",
"action": "add"
},
evidence=[
EvidenceRef(type="turn", ref_id=UUID("turn-uuid"))
],
confidence=0.9,
authority=Authority.GM
)
# Automatically validated
assert proposal.type == ProposalType.STATE_CHANGE
assert len(proposal.evidence) >= 1from monitor.schemas import EntityInstanceCreate
# Generate JSON Schema for MCP tool registration
schema = EntityInstanceCreate.model_json_schema()
# Output:
{
"type": "object",
"properties": {
"universe_id": {"type": "string", "format": "uuid"},
"name": {"type": "string", "minLength": 1, "maxLength": 200},
...
},
"required": ["universe_id", "name", ...]
}from fastapi import FastAPI
from monitor.schemas import *
app = FastAPI()
@app.post("/neo4j/entity", response_model=EntityResponse)
def create_entity(request: EntityInstanceCreate):
...
# FastAPI auto-generates OpenAPI spec from Pydantic models- Create base enums and metadata models
- Implement Neo4j request/response models
- Implement MongoDB request/response models
- Implement Qdrant request/response models
- Implement composite operation models
- Add custom validators (UUID, evidence_refs, etc.)
- Add error models
- Generate JSON Schema for all models
- Create unit tests for validation logic
- Document all model fields with descriptions
- Set up JSON schema export for MCP tools
- DATA_LAYER_API.md - API operation specifications
- MCP_TRANSPORT.md - MCP tool definitions
- ONTOLOGY.md - Data model specification
- Pydantic Documentation: https://docs.pydantic.dev/