"""Pydantic request/response models for the GODSEED HTTP API. Shapes follow ARCHITECTURE.md §HTTP API exactly: GET /api/state -> StateResponse POST /api/wish {text} -> WishAccepted (errors: 400/429/503 with a poetic `detail` string) GET /api/wishes -> list[WishSummary] GET /api/wishes/{id} -> raw WishTrace dict (passed through, engine-owned shape) Features and WishTraces are deliberately NOT strict models here: the engine owns those shapes and the server passes them through untouched (the "her" pattern — transport never reinterprets world facts). """ from __future__ import annotations from typing import Any, Optional from pydantic import BaseModel, ConfigDict, Field, field_validator # The moderation fast-path enforces the real wish limit (<=140 chars) with a poetic # reason; this transport-level cap only stops abusive megabyte bodies before they # reach moderation. TRANSPORT_MAX_WISH_CHARS = 2000 class WishRequest(BaseModel): """Body of POST /api/wish.""" model_config = ConfigDict(str_strip_whitespace=True) text: str = Field(..., min_length=1, max_length=TRANSPORT_MAX_WISH_CHARS) @field_validator("text") @classmethod def _not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("a wish needs words") return value.strip() class WishAccepted(BaseModel): """Response of POST /api/wish.""" wish_id: str position: int = Field(..., ge=1, description="1-based place in the queue") class CurrentWish(BaseModel): """The wish the god is granting right now.""" wish_id: str text_preview: str class QueueInfo(BaseModel): length: int = 0 current: Optional[CurrentWish] = None class WorldState(BaseModel): """Snapshot of the deterministic world: ordered feature list + counters.""" version: int = Field(..., ge=0, description="total number of features") epoch: int = Field(..., ge=0, description="number of granted wishes") features: list[dict[str, Any]] class WishSummary(BaseModel): """One row of the Genesis Log index (GET /api/wishes).""" wish_id: str text: str epitaph: str = "" epoch: int ts: float class StateResponse(BaseModel): """GET /api/state.""" world: WorldState queue: QueueInfo wishes_recent: list[WishSummary]