[E00-S02-T02] PostgreSQL health gates app start #383

Merged
kpcto merged 1 commits from feature/169 into main 2026-08-29 00:07:32 +00:00
Member

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:
    • db service gains a healthcheck: pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} via CMD-SHELL, with interval: 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 follows POSTGRES_USER/POSTGRES_DB overrides.
    • app service depends_on changes from a plain list to db: {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 new assertDbHealthcheck helper + two new criterion tests, the app-service assertions updated to the healthy-conditioned depends_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 the db container 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

Acceptance criterion Test (fails without the committed state)
PostgreSQL health check gates application start tests/compose-config.test.mjs — "PostgreSQL health check gates application start (db declares a pg_isready healthcheck)": the committed compose.yaml db service declares a healthcheck whose test runs pg_isready against $${POSTGRES_USER}/$${POSTGRES_DB} (credential-consistent, $$-escaped) with an interval and retries. Non-vacuous: "removing the db healthcheck makes the health-gate criterion fail (mutation probe)". When Docker is available, the real-stack probe additionally asserts the db container is healthy (tolerant when Compose does not report health). docker compose config --quiet also fails if the gate is misconfigured (healthy-conditioned depends_on requires a healthcheck)
app waits for the database before starting tests/compose-config.test.mjs — "the app waits for the database before starting (depends_on db with condition service_healthy)" (and the app-service assertion inside assertAppService): 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) ✓
  • Full suite node --test tests/*.test.mjs → 52 pass / 12 fail / 2 skip; the 12 failures are the pre-existing Node-22-environment baseline on pristine main (frozen-install / engine / root-commands / tsc / TS-version — no node_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.yaml re-parsed by the committed minimal block-YAML parser: db.healthcheck.test contains pg_isready + $${POSTGRES_USER}/$${POSTGRES_DB}, app.depends_on.db.condition = service_healthy ✓
  • Lockfile and CI workflow untouched — the frozen-install CI job is unaffected ✓
  • Manual acceptance (issue test plan): "start the stack and confirm the app waits on database health" — the Docker-gated real-stack probe automates exactly this when a daemon is available

Risks / notes

  • The healthcheck uses pg_isready (ships with the official postgres:18-bookworm image) rather than a shell TCP probe, so the gate reflects PostgreSQL's own readiness (accepting connections) instead of merely a listening socket.
  • depends_on with condition: service_healthy follows the Compose spec; with the db healthcheck committed, docker compose config --quiet validates cleanly.
  • The app container still starts and exits 0 at T02 because apps/server remains 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.
  • Docker-gated tests skip when docker/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

## 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: - `db` service gains a **`healthcheck`**: `pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}` via `CMD-SHELL`, with `interval: 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 follows `POSTGRES_USER`/`POSTGRES_DB` overrides. - `app` service `depends_on` changes from a plain list to **`db: {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 new `assertDbHealthcheck` helper + two new criterion tests, the `app`-service assertions updated to the healthy-conditioned `depends_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 the `db` container 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 | Acceptance criterion | Test (fails without the committed state) | | --- | --- | | PostgreSQL health check gates application start | `tests/compose-config.test.mjs` — "PostgreSQL health check gates application start (db declares a pg_isready healthcheck)": the committed `compose.yaml` `db` service declares a `healthcheck` whose `test` runs `pg_isready` against `$${POSTGRES_USER}`/`$${POSTGRES_DB}` (credential-consistent, `$$`-escaped) with an `interval` and `retries`. Non-vacuous: "removing the db healthcheck makes the health-gate criterion fail (mutation probe)". When Docker is available, the real-stack probe additionally asserts the `db` container is **healthy** (tolerant when Compose does not report health). `docker compose config --quiet` also fails if the gate is misconfigured (healthy-conditioned `depends_on` requires a healthcheck) | | app waits for the database before starting | `tests/compose-config.test.mjs` — "the app waits for the database before starting (depends_on db with condition service_healthy)" (and the `app`-service assertion inside `assertAppService`): `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) ✓ - Full suite `node --test tests/*.test.mjs` → 52 pass / 12 fail / 2 skip; the 12 failures are the pre-existing Node-22-environment baseline on pristine `main` (frozen-install / engine / root-commands / tsc / TS-version — no `node_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.yaml` re-parsed by the committed minimal block-YAML parser: `db.healthcheck.test` contains `pg_isready` + `$${POSTGRES_USER}`/`$${POSTGRES_DB}`, `app.depends_on.db.condition = service_healthy` ✓ - Lockfile and CI workflow untouched — the frozen-install CI job is unaffected ✓ - Manual acceptance (issue test plan): "start the stack and confirm the app waits on database health" — the Docker-gated real-stack probe automates exactly this when a daemon is available ## Risks / notes - The healthcheck uses `pg_isready` (ships with the official `postgres:18-bookworm` image) rather than a shell TCP probe, so the gate reflects PostgreSQL's own readiness (accepting connections) instead of merely a listening socket. - `depends_on` with `condition: service_healthy` follows the Compose spec; with the `db` healthcheck committed, `docker compose config --quiet` validates cleanly. - The `app` container still starts and exits 0 at T02 because `apps/server` remains 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. - Docker-gated tests `skip` when `docker`/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
bot-implementer added 1 commit 2026-08-28 23:59:59 +00:00
feat: PostgreSQL health gates app start (E00-S02-T02)
CI / Frozen lockfile install (pull_request) Successful in 47s
59a102bee3
Member
agent: tester
phase: start
objective: "Independently probe PR #383 for issue #169 ([E00-S02-T02] PostgreSQL health gates app start)"
pr: Fabrika/PersonalBlog#383 (feature/169 -> main, head 59a102b)
acceptance:
  - "PostgreSQL health check gates application start"
  - "app waits for the database before starting"
plan:
  - review compose.yaml diff (db healthcheck + app depends_on condition: service_healthy)
  - review tests/compose-config.test.mjs additions and non-vacuous mutation probes
  - independently run the compose-config test suite locally
  - validate the Compose config (docker compose config, if Docker is available)
  - inspect CI runs on the PR and collect job links
  - post evidence comment with CI links
constraints:
  - never edit the implementer's tests in the same PR
```yaml agent: tester phase: start objective: "Independently probe PR #383 for issue #169 ([E00-S02-T02] PostgreSQL health gates app start)" pr: Fabrika/PersonalBlog#383 (feature/169 -> main, head 59a102b) acceptance: - "PostgreSQL health check gates application start" - "app waits for the database before starting" plan: - review compose.yaml diff (db healthcheck + app depends_on condition: service_healthy) - review tests/compose-config.test.mjs additions and non-vacuous mutation probes - independently run the compose-config test suite locally - validate the Compose config (docker compose config, if Docker is available) - inspect CI runs on the PR and collect job links - post evidence comment with CI links constraints: - never edit the implementer's tests in the same PR ```
Member
agent: tester
phase: complete
objective: "Independently probe PR #383 for issue #169 ([E00-S02-T02] PostgreSQL health gates app start)"
pr: Fabrika/PersonalBlog#383 (feature/169 -> main, head 59a102b)
verdict: PASS

acceptance:
  - criterion: "PostgreSQL health check gates application start"
    result: PASS
    evidence:
      - "compose.yaml `db` service declares `healthcheck.test: [CMD-SHELL, 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']` with interval 5s / timeout 5s / retries 5 / start_period 5s"
      - "`$$` deferral means the probe runs with the container env, so it checks the same POSTGRES_USER/POSTGRES_DB the database was created with (override-consistent)"
      - "compose-config test 'PostgreSQL health check gates application start (db declares a pg_isready healthcheck)' PASS; mutation probe 'removing the db healthcheck ...' PASS (non-vacuous: stripping the block asserts throw)"
  - criterion: "app waits for the database before starting"
    result: PASS
    evidence:
      - "compose.yaml `app.depends_on.db.condition: service_healthy` (long syntax) — Compose starts the app only after the db reports healthy"
      - "compose-config test 'the app waits for the database before starting (depends_on db with condition service_healthy)' PASS; mutation probe 'reverting depends_on to a plain list ...' PASS (non-vacuous)"
      - "assertAppService also asserts app.depends_on.db.condition === 'service_healthy'"

independent_probe:
  - "checked out feature/169 at head 59a102b in a clean workspace and read compose.yaml directly"
  - "`node --test tests/compose-config.test.mjs` -> 13 pass / 0 fail / 2 skip (the two Docker-gated probes skip: no docker CLI/daemon in this sandbox)"
  - "`node --test tests/*.test.mjs` on feature/169 -> 52 pass / 12 fail / 2 skip; on main (baseline 9e2ac55) -> 48 pass / 12 fail / 2 skip"
  - "the 12 failures are the pre-existing environment baseline (no pnpm installed, node 22 vs engines >=24): frozen-install (5), node-engine (1), root-commands (3), strict-tsconfig (2), typescript-pin (1). None touch compose-config; the PR adds +4 passing tests and introduces zero regressions"
  - "diff scope: `git diff --name-status main...feature/169` = only compose.yaml + tests/compose-config.test.mjs; pnpm-lock.yaml, .gitea/, package.json untouched"

ci:
  run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/64 (conclusion: success, head 59a102b, pull_request)"
  job: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/64/jobs/76 (Frozen lockfile install, success)"
  note: "CI is green but the .gitea/workflows/ci.yml job only runs `pnpm install --frozen-lockfile` + workspace-group verify — it does NOT run the test suite. The test evidence above is from my independent local execution."

observations:
  - "non-blocking / out of scope: app.DATABASE_URL is still hardcoded (postgres://eppp:eppp@db:5432/eppp) and does not follow POSTGRES_USER/POSTGRES_DB overrides, unlike the new healthcheck. Pre-existing T01 behavior, not part of this story's acceptance criteria."
  - "constraint respected: I did not edit the implementer's tests (tests/compose-config.test.mjs) — all probing was read-only plus `git checkout` of the branch; no commits pushed."
```yaml agent: tester phase: complete objective: "Independently probe PR #383 for issue #169 ([E00-S02-T02] PostgreSQL health gates app start)" pr: Fabrika/PersonalBlog#383 (feature/169 -> main, head 59a102b) verdict: PASS acceptance: - criterion: "PostgreSQL health check gates application start" result: PASS evidence: - "compose.yaml `db` service declares `healthcheck.test: [CMD-SHELL, 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']` with interval 5s / timeout 5s / retries 5 / start_period 5s" - "`$$` deferral means the probe runs with the container env, so it checks the same POSTGRES_USER/POSTGRES_DB the database was created with (override-consistent)" - "compose-config test 'PostgreSQL health check gates application start (db declares a pg_isready healthcheck)' PASS; mutation probe 'removing the db healthcheck ...' PASS (non-vacuous: stripping the block asserts throw)" - criterion: "app waits for the database before starting" result: PASS evidence: - "compose.yaml `app.depends_on.db.condition: service_healthy` (long syntax) — Compose starts the app only after the db reports healthy" - "compose-config test 'the app waits for the database before starting (depends_on db with condition service_healthy)' PASS; mutation probe 'reverting depends_on to a plain list ...' PASS (non-vacuous)" - "assertAppService also asserts app.depends_on.db.condition === 'service_healthy'" independent_probe: - "checked out feature/169 at head 59a102b in a clean workspace and read compose.yaml directly" - "`node --test tests/compose-config.test.mjs` -> 13 pass / 0 fail / 2 skip (the two Docker-gated probes skip: no docker CLI/daemon in this sandbox)" - "`node --test tests/*.test.mjs` on feature/169 -> 52 pass / 12 fail / 2 skip; on main (baseline 9e2ac55) -> 48 pass / 12 fail / 2 skip" - "the 12 failures are the pre-existing environment baseline (no pnpm installed, node 22 vs engines >=24): frozen-install (5), node-engine (1), root-commands (3), strict-tsconfig (2), typescript-pin (1). None touch compose-config; the PR adds +4 passing tests and introduces zero regressions" - "diff scope: `git diff --name-status main...feature/169` = only compose.yaml + tests/compose-config.test.mjs; pnpm-lock.yaml, .gitea/, package.json untouched" ci: run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/64 (conclusion: success, head 59a102b, pull_request)" job: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/64/jobs/76 (Frozen lockfile install, success)" note: "CI is green but the .gitea/workflows/ci.yml job only runs `pnpm install --frozen-lockfile` + workspace-group verify — it does NOT run the test suite. The test evidence above is from my independent local execution." observations: - "non-blocking / out of scope: app.DATABASE_URL is still hardcoded (postgres://eppp:eppp@db:5432/eppp) and does not follow POSTGRES_USER/POSTGRES_DB overrides, unlike the new healthcheck. Pre-existing T01 behavior, not part of this story's acceptance criteria." - "constraint respected: I did not edit the implementer's tests (tests/compose-config.test.mjs) — all probing was read-only plus `git checkout` of the branch; no commits pushed." ```
kpcto merged commit 9937268d64 into main 2026-08-29 00:07:32 +00:00
kpcto deleted branch feature/169 2026-08-29 00:07:33 +00:00
Sign in to join this conversation.