fix(ci): stop Postgres state-bleed across CI runs (#277) #299

Merged
james merged 2 commits from 277-postgres-ci-state-bleed into main 2026-06-26 23:04:10 +00:00
Owner

Closes #277.

Problem

The self-hosted Forgejo act_runner reuses the Postgres service container's
data volume across CI runs, so a table created by one PR's run (e.g. #269's
organization_key_people) is still physically present at the start of the
next run. When a PR is stale across a table-adding migration, the test
teardown's hardcoded KYSELY_TABLES drop list doesn't know about the orphan
table, its FK blocks the people drop, and all 15 Postgres suites fail at
apps/api/tests/db/_engines.ts. This is the #259-family failure that bit PR
#270.

Both fixes from the ticket are applied (defense-in-depth):

Fix 1 — tmpfs the Postgres data dir (CI)

.forgejo/workflows/pr.yml: the test job's postgres service now mounts
--tmpfs /var/lib/postgresql/data, so the data dir lives in RAM and is fresh
on every container start. initdb re-runs clean each time, making the
runner's volume reuse harmless. Fixes the visible symptom.

Fix 2 — derive the teardown drop-list at runtime (test infra)

apps/api/tests/db/_engines.ts: the hardcoded KYSELY_TABLES array is gone.
The Postgres engine setup now queries information_schema.tables (public
schema, base tables) and drops each with CASCADE. FK ordering no longer
matters, and no KYSELY_TABLES edit is ever needed when a migration adds a
table (#259). A database left orphaned by a previous run still tears down
cleanly even if the infra side regresses. Also drops the stale
KYSELY_TABLES reference in 018_people.ts's down-migration comment.

This directly satisfies acceptance criterion 2, so the alternative CI
drift-assertion is not needed.

Verification (local, Docker Postgres)

  • Full suite against Postgres: 953 passed.
  • Injected the #277 orphan scenario (people + a dependent FK table) into the
    DB, re-ran: 953 passed — teardown handles the orphan (pre-fix this
    failed at the people drop).
  • sqlite-only run (no TEST_POSTGRES_URL): 786 passed / 167 skipped,
    unchanged.
  • tsc --noEmit and actionlint clean.

End-to-end acceptance (CI-only): a PR branched off a stale base across a
table-adding migration should now go green without a rebase.

🤖 Generated with Claude Code

Closes #277. ## Problem The self-hosted Forgejo `act_runner` reuses the Postgres service container's data volume across CI runs, so a table created by one PR's run (e.g. #269's `organization_key_people`) is still physically present at the *start* of the next run. When a PR is stale across a table-adding migration, the test teardown's hardcoded `KYSELY_TABLES` drop list doesn't know about the orphan table, its FK blocks the `people` drop, and all 15 Postgres suites fail at `apps/api/tests/db/_engines.ts`. This is the #259-family failure that bit PR #270. Both fixes from the ticket are applied (defense-in-depth): ## Fix 1 — tmpfs the Postgres data dir (CI) `.forgejo/workflows/pr.yml`: the `test` job's `postgres` service now mounts `--tmpfs /var/lib/postgresql/data`, so the data dir lives in RAM and is fresh on every container start. `initdb` re-runs clean each time, making the runner's volume reuse harmless. Fixes the visible symptom. ## Fix 2 — derive the teardown drop-list at runtime (test infra) `apps/api/tests/db/_engines.ts`: the hardcoded `KYSELY_TABLES` array is gone. The Postgres engine setup now queries `information_schema.tables` (public schema, base tables) and drops each with `CASCADE`. FK ordering no longer matters, and no `KYSELY_TABLES` edit is ever needed when a migration adds a table (#259). A database left orphaned by a previous run still tears down cleanly even if the infra side regresses. Also drops the stale `KYSELY_TABLES` reference in `018_people.ts`'s down-migration comment. This directly satisfies acceptance criterion 2, so the alternative CI drift-assertion is not needed. ## Verification (local, Docker Postgres) - Full suite against Postgres: **953 passed**. - Injected the #277 orphan scenario (`people` + a dependent FK table) into the DB, re-ran: **953 passed** — teardown handles the orphan (pre-fix this failed at the `people` drop). - sqlite-only run (no `TEST_POSTGRES_URL`): **786 passed / 167 skipped**, unchanged. - `tsc --noEmit` and `actionlint` clean. End-to-end acceptance (CI-only): a PR branched off a stale base across a table-adding migration should now go green without a rebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The self-hosted Forgejo act_runner reuses the Postgres service
container's data volume across runs, so a table created by one PR's run
is still present at the start of the next. A PR stale across a
table-adding migration then fails teardown on the orphan's FK. Mount the
data dir as tmpfs so every run starts from an empty database.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test(db): derive Postgres teardown drop-list at runtime (#277)
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 8s
PR / Static analysis (pull_request) Successful in 3m5s
PR / OpenAPI (pull_request) Successful in 4m21s
PR / Client (web export smoke) (pull_request) Successful in 5m20s
PR / Test (postgres) (pull_request) Successful in 5m57s
PR / Build (pull_request) Successful in 6m5s
PR / Lint (pull_request) Successful in 6m17s
PR / OSV-Scanner (pull_request) Successful in 2m9s
PR / Typecheck (pull_request) Successful in 6m33s
PR / Test (sqlite) (pull_request) Successful in 6m37s
PR / Package age policy (soft) (pull_request) Successful in 47s
Secrets / gitleaks (pull_request) Successful in 51s
PR / pnpm audit (pull_request) Successful in 4m27s
PR / Trivy (image) (pull_request) Successful in 4m9s
PR / Coverage (soft) (pull_request) Successful in 3m48s
0c24923b46
Replace the hardcoded KYSELY_TABLES drop list with a runtime query over
information_schema.tables plus DROP ... CASCADE. FK ordering no longer
matters and no edit is needed when a migration adds a table (#259), so a
database left orphaned by a previous CI run tears down cleanly even
without the tmpfs fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 81.6% ≥ 50%
Branches 72.8% ⚠️ ≥ 75%
Functions 91.1% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 81.6% ✅ | ≥ 50% | | Branches | 72.8% ⚠️ | ≥ 75% | | Functions | 91.1% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 0dffb2661e into main 2026-06-26 23:04:10 +00:00
james deleted branch 277-postgres-ci-state-bleed 2026-06-26 23:04:11 +00:00
Sign in to join this conversation.
No description provided.