"""FastAPI proxy entrypoint — the guarding proxy of charter §4. Skeleton: a health check plus the five role endpoints. Each role endpoint now validates the posted canon log against the contract (charter §11) before doing anything else — an invalid log is rejected with 422 and never reaches a prompt. Prompt routing, model selection, and logging land later. """ from fastapi import Depends, FastAPI, HTTPException from pydantic import BaseModel from .canon_log import validate_canon_log app = FastAPI(title="coc-rpg proxy", version="0.0.1") class TurnRequest(BaseModel): canon_log: dict def valid_turn(req: TurnRequest) -> TurnRequest: """Shared dependency: reject any request whose canon log breaks the contract.""" errors = validate_canon_log(req.canon_log) if errors: raise HTTPException(status_code=422, detail={"canon_log_errors": errors}) return req @app.get("/health") def health() -> dict: """Liveness probe for compose / fly.io.""" return {"status": "ok"} # ── Role endpoints (charter §4) ────────────────────────────────────────────── # The client knows these paths and nothing about which model or prompt serves # them. Bodies are validated against the canon log contract; the AI half is a # stub until prompt routing lands. @app.post("/dm/narrate") def narrate(req: TurnRequest = Depends(valid_turn)) -> dict: return {"detail": "not implemented"} @app.post("/dm/adjudicate") def adjudicate(req: TurnRequest = Depends(valid_turn)) -> dict: return {"detail": "not implemented"} @app.post("/dm/improvise") def improvise(req: TurnRequest = Depends(valid_turn)) -> dict: return {"detail": "not implemented"} @app.post("/npc/speak") def npc_speak(req: TurnRequest = Depends(valid_turn)) -> dict: return {"detail": "not implemented"} @app.post("/party/banter") def banter(req: TurnRequest = Depends(valid_turn)) -> dict: return {"detail": "not implemented"}