The canon-log schema and NewGame code already migrated to race_id/ calling_id + allowed_callings, but two copies of the roster outside that schema were missed, and no parity test guards them: - docs/canon-log.md (the cross-boundary contract doc) still documented the OLD player shape and allowed_classes. Anyone building a player block from the doc would get a 422. - .claude/skills/world-building's schema reference still emitted allowed_classes with two dead calling ids (assassin, priest). Author a new origin with that skill and it fails origin.schema.json (additionalProperties: false, allowed_callings required) AND, if that ever loosened, silently allows zero callings at runtime. Also: - add schema tests rejecting a dead class_id field and an unknown calling_id (priest) - guard NewGame._validate's container types (spend/skills) so a malformed JSON round-trip (null spend, string skills) produces a front-loaded error list instead of a GDScript runtime crash - extend the no-mechanics-in-content test to db.races, not just db.callings - rewrite the roadmap's M4 bullet: the two contract migrations landed; only the creation screen and title->creation->shell flow remain client: 250/250. api: 74 passed, 2 skipped. content_build --check: clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
/api — FastAPI proxy
The guarding proxy from charter §4. Not a generic API — it exists to keep the API key, the prompts, and the model routing server-side, and to log every call from day one.
Stack: Python, FastAPI. Models: Ollama (dev/staging) → Replicate (prod), routed per-role server-side.
Role endpoints (charter §4)
POST /dm/narrate Narrator — scene/outcome prose (good model)
POST /dm/adjudicate Adjudicator — free text → legal action (small/fast, strict JSON)
POST /dm/improvise Improviser — minor sandboxed event (charter §7 limits)
POST /npc/speak NPC — voice one character (bounded moves, §6)
POST /party/banter Banter — companion callbacks (cacheable, §9)
The client does not know which model serves a role or what the prompt is.
Responsibilities
- Auth · metering (retrofit later — middleware + Stripe webhook, §4)
- Prompt ownership — prompts live in
prompts/, never in the client - Model routing — role → model is config, a deploy not a client patch
- Logging — log seed and full prompt with every call (§10) for replay/eval
Contracts
Every AI response is untrusted (§2). Parse, validate, be ready to discard. One retry on parse failure, then authored fallback (§12).
See docs/ for endpoint schemas, model routing, and deploy notes.