Commit Graph

24 Commits

Author SHA1 Message Date
86d1352b19 test(api): gated live smoke — Fenn answers /npc/speak in voice
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:07:24 -05:00
a2c654c047 content: author Fenn + the NPC role prompt body 2026-07-10 12:26:04 -05:00
0c3d64d068 feat(api): wire POST /npc/speak (422 unknown npc, 502 model error) 2026-07-10 12:22:07 -05:00
004cc21724 feat(api): npc.run service — thin drop-in on the model pipeline 2026-07-10 12:18:01 -05:00
76c04b45d2 feat(api): render_npc_digest — persona/knowledge/moves/utterance 2026-07-10 12:14:34 -05:00
6cd5003930 feat(api): route the npc role to its own model 2026-07-10 12:11:54 -05:00
69729f7673 feat(api): content.load_npc + content-root resolver 2026-07-10 12:08:50 -05:00
f7c51b79da fix(api): best-effort call logging + non-JSON 200 → ModelError (final-review)
- call_log._default_write: swallow any exception from the default file/
  stdout sink and report to stderr, so a log-write failure (disk full,
  bad permissions, misconfigured CALL_LOG_PATH) never turns a successful
  narration into a 500 (charter §13). Injected write= sinks (used by
  tests) are left to surface their own errors.
- ollama_client.chat: catch ValueError alongside httpx.HTTPError in the
  retry loop so a 200 response with a non-JSON body (JSONDecodeError is
  a ValueError subclass) counts as a failed attempt and falls through to
  ModelError after the one retry, instead of escaping chat() uncaught
  (charter §12 — one retry, then ModelError, nothing else escapes).
- main.py: update stale docstrings — /dm/narrate is now fully wired
  (routing + model call + logging); the other four roles remain stubs.

Regression tests added for both fixes (TDD: watched RED, then GREEN).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:45:39 -05:00
470ccb2935 test(api): gated live Ollama smoke (--run-live) + env docs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:36:56 -05:00
45d5f0360b test(api): keep test_endpoints hermetic after /dm/narrate wiring
/dm/narrate now makes a real Ollama call (Task 7). The role-endpoint
loop tests in test_endpoints.py had no mock, so every default pytest
run was silently hitting a live Ollama instance on localhost:11434 —
breaking the hermetic-suite constraint. Add an autouse fixture that
stubs narrate.ollama_client.chat and narrate.call_log.record so all
five endpoints stay hermetic; assertions (200 on valid, 422 on
invalid) are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:31:42 -05:00
5a35802075 feat(api): wire /dm/narrate — 200 prose | 502 model_error | 422 invalid log
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:31:38 -05:00
2027e7a9b9 feat(api): narrate service — render→route→call→log, returns raw prose
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:27:36 -05:00
2558e2a2a0 feat(api): author narrator system prompt (voice, [FACT] rule, no-numbers, no player agency) 2026-07-10 08:23:31 -05:00
15995c57ee feat(api): §10 JSON-lines call logging (full prompt + seed, injectable sink)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:20:00 -05:00
be133c4f3a feat(api): Ollama /api/chat client with one retry + typed ModelError
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:15:45 -05:00
4d8a19353e feat(api): prompt loader (header split) + curated canon-log digest
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:11:57 -05:00
42b3720721 feat(api): config + role→model routing (qwen3.5 narrator default, think off)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:08:56 -05:00
7f1d9fa39a chore(api): canon log follow-ups — hardening, unified 422, contract doc
- canon_log.py: stringify jsonschema error-path segments before sorting so a
  future schema mixing array indices and object keys can't TypeError (#1)
- main.py: RequestValidationError handler reshapes pydantic body failures into
  the same {"detail": {"canon_log_errors": [...]}} envelope as schema failures,
  so the client parses ONE 422 shape (#2a) + tests
- pytest.ini: filter the starlette/httpx TestClient deprecation → pristine output (#4)
- docs/canon-log.md: living contract for the client (Plan B) — schemas, the §7
  boundary, the unified error envelope, per-role injection (#3)

Full suite 26 passing, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:03:59 -05:00
e39d1a446e fix(api): constrain disposition_overrides keys; close docker paper cuts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:54:01 -05:00
6d5510773a feat(api): validate canon log at every role endpoint (422 on invalid) 2026-07-09 12:42:03 -05:00
4aa65c9d4c feat(content): POC deserter origin + world fixtures with id-resolution check 2026-07-09 12:37:29 -05:00
e80a4071f6 fix(api): pattern-constrain humiliations id; broaden canon log boundary tests 2026-07-09 12:34:31 -05:00
843ab11a5c feat(api): canon log JSON Schema + validator, enforce §7 luck boundary
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:31:10 -05:00
710745e548 feat(api): origin seed JSON Schema + validator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:26:39 -05:00