Commit Graph

22 Commits

Author SHA1 Message Date
004bd53a39 fix(api): let the Dockerized proxy reach a host-run Ollama
After the prompts fix, /dm/narrate reached the model call and failed with
ConnectError: Connection refused — the container's localhost is not the
host, so the default OLLAMA_BASE_URL=http://localhost:11434 hit nothing.
Ollama runs on the host (M1's venv smoke reached it because uvicorn ran on
the host too; the container never had a route).

Add extra_hosts host.docker.internal:host-gateway so the container can
reach the host, and document using http://host.docker.internal:11434 in
.env.example. Verified a container reaches host Ollama's /api/tags (200).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:07:44 -05:00
f18d306059 fix(api): package api/prompts into the Docker image
The narrator pipeline (M1) reads role prompts from /app/prompts, but the
Dockerfile copied only api/app and docs/schemas — never api/prompts — so
/dm/narrate 500'd with FileNotFoundError on narrator.md in any Docker run.
The M1 live smoke passed only because it ran uvicorn from the local venv,
where ../prompts resolves on the host; the container path was never
exercised until the client HTTP loop drove a real request through compose.

Dockerfile now COPYs api/prompts; compose mounts it :ro so prompt edits
reload live like the app code (prompts are source code, §16). Verified by
building the image and confirming /app/prompts/narrator.md is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:29:08 -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
4ab0e564ef chore(api): bundle canon log schemas into image; repo-root build context
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 12:45:49 -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
d4ada9bedb chore: dockerize api proxy and add gitignore
Add a Docker-based FastAPI proxy skeleton (charter §4) for parity across
dev / homelab VPS / fly.io, plus root .gitignore for Godot 4.7 and Python.

- api/Dockerfile: python:3.12-slim, non-root, PORT-aware CMD
- api/app/main.py: /health + five role endpoint stubs (§4)
- api/requirements.txt: fastapi/uvicorn/httpx/pydantic, pinned
- docker-compose.yml: local dev with live reload
- api/fly.toml: prod deploy stub with /health check
- api/.env.example: key lives in the proxy, never the client (§4)
- .gitignore: ignores .env, whitelists .env.example

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:23:27 -05:00
d54599e27d first commit 2026-07-09 11:17:54 -05:00