Summary
CI runs the app from a host virtualenv (pip install -e ".[dev]") and compose only starts postgres/redis/kafka — the app image is never built or booted in CI. The non-editable production image therefore has no automated coverage, and two real bugs reached main because of it:
Both are invisible to the source-tree test suite (the files exist in the checkout) and to a build_app() import smoke (the crash is in the lifespan). Only a real container boot surfaces them.
Proposed fix
Add a CI job (PR or nightly) that:
docker builds the image, then runs it and asserts /healthz (or /readyz) — i.e. the lifespan actually starts (load_settings() + PromptBundle.load succeed).
- Optionally runs the README "See it run" demo end-to-end against the built image as a contract test (matches the CLAUDE.md quality bar: "
docker compose up boots the entire stack … returns a diagnosis … CI-verified contract").
Cost: a Docker build per run (the embedding-model preload dominates; cache it). Nightly is a reasonable cadence if per-PR is too slow.
Related, smaller
make compose-up is docker compose up -d --wait with no --build, so re-running after code changes silently reuses a stale app image. Consider --build (or a make compose-rebuild) and/or a README note.
Summary
CI runs the app from a host virtualenv (
pip install -e ".[dev]") and compose only startspostgres/redis/kafka— theappimage is never built or booted in CI. The non-editable production image therefore has no automated coverage, and two real bugs reachedmainbecause of it:site-packages, breaking the__file__-relative per-env YAML lookup (config silently no-op'd in the image). Fixed in fix(docker): real multi-stage image with HEALTHCHECK + relocatable config (#73) #80 viaSENTINEL_CONFIG_DIR.*.pyby default, sosentinel/diagnosis/prompts/v1.mdwas dropped from the wheel and the image crashed on startup atPromptBundle.load. Fixed in fix(packaging): ship the diagnosis prompt files in the wheel (regression from #80) #82 viapackage-data.Both are invisible to the source-tree test suite (the files exist in the checkout) and to a
build_app()import smoke (the crash is in the lifespan). Only a real container boot surfaces them.Proposed fix
Add a CI job (PR or nightly) that:
docker builds the image, then runs it and asserts/healthz(or/readyz) — i.e. the lifespan actually starts (load_settings()+PromptBundle.loadsucceed).docker compose upboots the entire stack … returns a diagnosis … CI-verified contract").Cost: a Docker build per run (the embedding-model preload dominates; cache it). Nightly is a reasonable cadence if per-PR is too slow.
Related, smaller
make compose-upisdocker compose up -d --waitwith no--build, so re-running after code changes silently reuses a staleappimage. Consider--build(or amake compose-rebuild) and/or a README note.