Test database — one Postgres for the whole run

One quotable sentence:

cargo test uses one Postgres — an external TEST_DATABASE_URL if you set one, otherwise a single reuse-labeled container shared by every test binary — never one container per binary.

The problem this fixes

cargo test --workspace runs ~50 test binaries. The old store::test_support started one Postgres container per binary and held the handle in a process-lifetime static, so a full run tried to start dozens of postgres:17-alpine containers at once and then leaked them (testcontainers-rs has no Ryuk reaper, so the Drop never fired). That exhausted the Docker daemon — WaitContainer(StartupTimeout), bridge docker0 … exchange full — and filled the disk that agent-workflows.md covers under maintenance cleanup.

The schema-per-test isolation was never the problem: within a binary, all tests already shared one container and each test got CREATE SCHEMA test_<id> + a search_path override. The waste was purely that every binary started its own server.

The decision

We keep two test tiers and only change tier 1's Postgres provisioning:

We deliberately do not pull Keycloak/OPA/OTel/Restate into cargo test. The trait seams are the contract boundary; real sidecars in unit tests would add wall-clock and flakiness, not coverage, and would raise the floor for a first-time contributor from "Docker" to "a full KIND cluster."

For tier 1's one Postgres we picked the hybrid (C-lean): honor TEST_DATABASE_URL if set, otherwise spawn one reuse-labeled container. This keeps cargo test zero-config locally, gives CI a clean external server, and lets a contributor who already ran navigator start-dev-server point tests at the KIND Postgres — one mechanism, three backends.

Reusing the KIND Postgres, or a dedicated container

Either, your choice — that is the point of the env seam. Nothing is auto-wired to the KIND Postgres (so even a bare cargo test never depends on navigator start-dev-server), but TEST_DATABASE_URL can point at it when you want one Postgres for both dev-run and tests. Schema-per-test isolation means tests create their own test_<id> schemas and never pollute the dev data, even when they share that server.

The env contract

In both cases each test still gets CREATE SCHEMA test_<id> + a search_path override (unchanged), so tests run in parallel safely.

What changed

How a contributor runs tests

How CI runs them

Each test job starts one docker run Postgres, waits for pg_isready, exports TEST_DATABASE_URL, and then runs cargo test, cargo llvm-cov, and the cucumber suite against it. No testcontainers, no per-binary proliferation, no leak. The full real stack is still exercised by the KIND e2e job, which now runs the full browser + accessibility suite on every PR.

Cold-start note

On a cold zero-config cargo test --workspace (no TEST_DATABASE_URL, no pre-existing shared container), the first few binaries can race to create the labeled container before one wins and the rest reuse it — so a cold run may briefly create a small handful of containers rather than exactly one. They are reuse-marked (not leaked), every later run settles to the single shared one, and CI avoids the race entirely by setting TEST_DATABASE_URL. To guarantee exactly one locally, set TEST_DATABASE_URL (point it at any Postgres, including navigator start-dev-server's).