[E00-S02-T02] PostgreSQL health gates app start #383
No Reviewers
Labels
Clear labels
agent/analyst-drafted
agent/analyst-drafted
needs/human-decision
needs/human-decision
needs/security-review
needs/security-review
tier/t0
tier/t1
tier/t2
tier/t3
kind
bug
kind
bug
kind
epic
kind
epic
kind
initiative
EPPP programme initiative
kind
story
kind
story
kind
task
EPPP engineering card/task decomposed from a story
kind
toil
kind
toil
loop
1
loop
1
loop
2
loop
2
loop
3
loop
3
risk
agent-full
risk
agent-full
risk
human-gated
risk
human-gated
risk
human-only
risk
human-only
size
l
size
l
size
m
size
m
size
s
size
s
status
blocked
status
blocked
status
done
Workflow: Done
status
in-progress
status
in-progress
status
proposed
status
proposed
status
ready
status
ready
status
review
status
review
stream
checkout
stream
checkout
stream
onboarding
stream
onboarding
stream
platform
stream
platform
trivial — implementer only, auto-merge
standard — implementer + reviewer + tester
complex — security if triggered, human merge
critical — full chain + security, human merge
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Fabrika/PersonalBlog#383
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What changed
Implements the [E00-S02-T02] health gate (#169) on top of the [E00-S02-T01] Compose baseline (#59): the application now waits for PostgreSQL before starting.
compose.yaml— two changes, nothing else:dbservice gains ahealthcheck:pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}viaCMD-SHELL, withinterval: 5s,timeout: 5s,retries: 5,start_period: 5s. The$$escapes defer interpolation to the container, so the probe checks the same credentials the database was created with and followsPOSTGRES_USER/POSTGRES_DBoverrides.appservicedepends_onchanges from a plain list todb: {condition: service_healthy}— Compose starts the app only once the database reports healthy, so the app waits for the DB before starting.tests/compose-config.test.mjs— locks in both acceptance criteria against the committed state (see table): a newassertDbHealthcheckhelper + two new criterion tests, theapp-service assertions updated to the healthy-conditioneddepends_on, two new non-vacuous mutation probes (healthcheck removal, depends_on revert to plain list), the parser probe extended to the new structure, and the Docker-gated real-stack probe now also asserts thedbcontainer is healthy when Docker reports it.Explicitly out of scope per the brief, not touched: compose starts DB + app (E00-S02-T01 — unchanged behavior), app health endpoint (E00-S02-T03), DB volume persistence (E00-S02-T04). No manifests, no lockfile, no CI workflow changes.
Criterion → test table
tests/compose-config.test.mjs— "PostgreSQL health check gates application start (db declares a pg_isready healthcheck)": the committedcompose.yamldbservice declares ahealthcheckwhosetestrunspg_isreadyagainst$${POSTGRES_USER}/$${POSTGRES_DB}(credential-consistent,$$-escaped) with anintervalandretries. Non-vacuous: "removing the db healthcheck makes the health-gate criterion fail (mutation probe)". When Docker is available, the real-stack probe additionally asserts thedbcontainer is healthy (tolerant when Compose does not report health).docker compose config --quietalso fails if the gate is misconfigured (healthy-conditioneddepends_onrequires a healthcheck)tests/compose-config.test.mjs— "the app waits for the database before starting (depends_on db with condition service_healthy)" (and theapp-service assertion insideassertAppService):app.depends_on.db.condition === 'service_healthy', so Compose starts the application only after PostgreSQL reports healthy. Non-vacuous: "reverting depends_on to a plain list breaks the healthy-gate criterion (mutation probe)"Test plan executed
node --test tests/compose-config.test.mjs→ 13/13 pass, 2 skipped (Docker-gated probes skip cleanly — this sandbox has no Docker CLI/daemon) ✓node --test tests/*.test.mjs→ 52 pass / 12 fail / 2 skip; the 12 failures are the pre-existing Node-22-environment baseline on pristinemain(frozen-install / engine / root-commands / tsc / TS-version — nonode_modules,engines.node: 24.x), identical to the baseline the T01 tester recorded, none caused by this change (compose-config: +4 pass, 0 fail) ✓compose.yamlre-parsed by the committed minimal block-YAML parser:db.healthcheck.testcontainspg_isready+$${POSTGRES_USER}/$${POSTGRES_DB},app.depends_on.db.condition = service_healthy✓Risks / notes
pg_isready(ships with the officialpostgres:18-bookwormimage) rather than a shell TCP probe, so the gate reflects PostgreSQL's own readiness (accepting connections) instead of merely a listening socket.depends_onwithcondition: service_healthyfollows the Compose spec; with thedbhealthcheck committed,docker compose config --quietvalidates cleanly.appcontainer still starts and exits 0 at T02 becauseapps/serverremains the bootstrap placeholder (the Fastify shell is a later story); the gate ensures it is only attempted after the DB is healthy. The health endpoint (T03) turns it into a staying-up process.skipwhendocker/the Compose plugin/daemon are unavailable, so the suite stays green on Docker-less machines while giving real end-to-end validation where Docker exists.Refs #169