# docker-compose.ci.yml
#
# Local CI gate with 4-way E2E sharding. Spins up 4 pgvector services + a bun
# runner that bind-mounts the repo. Used by `bun run ci:local` and
# `bun run ci:local:diff` (see scripts/ci-local.sh).
#
# All services are pulled as `image:` (no build) so `docker compose pull`
# refreshes everything. The bun version floats with `oven/bun:1` to track CI's
# `bun-version: latest`. Named volumes isolate the Linux container's deps from
# the host's darwin-arm64 deps and keep bun + postgres data warm across runs.
#
# Why 4 postgres services: bun's E2E suite shares one DB across 36 files and
# uses TRUNCATE CASCADE in setupDB(). Running files in parallel against ONE DB
# races (file A's TRUNCATE clobbers file B's fixture import). 4 separate DBs
# remove the race; we shard the file list 1/4..4/4 and run shards in parallel.
# Within a shard, files still run sequentially. Total wall-time on a 16-core
# host: ~6 min sequential -> ~1.5-2 min sharded.
#
# Postgres host ports default to 5434-5437 (avoid 5432 manual `gbrain-test-pg`
# and 5433 sibling-project conflicts). Override BASE port with GBRAIN_CI_PG_PORT;
# shards take BASE..BASE+3.

services:
  postgres-1:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: gbrain_test
    ports:
      - "${GBRAIN_CI_PG_PORT:-5434}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d gbrain_test"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - gbrain-ci-pg-data-1:/var/lib/postgresql/data

  postgres-2:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: gbrain_test
    ports:
      - "${GBRAIN_CI_PG_PORT_2:-5435}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d gbrain_test"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - gbrain-ci-pg-data-2:/var/lib/postgresql/data

  postgres-3:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: gbrain_test
    ports:
      - "${GBRAIN_CI_PG_PORT_3:-5436}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d gbrain_test"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - gbrain-ci-pg-data-3:/var/lib/postgresql/data

  postgres-4:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: gbrain_test
    ports:
      - "${GBRAIN_CI_PG_PORT_4:-5437}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d gbrain_test"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - gbrain-ci-pg-data-4:/var/lib/postgresql/data

  # v0.43 (#2084 / eng-review TD1): PgBouncer in TRANSACTION pooling mode
  # fronting postgres-1 — the production topology (Supabase direct :5432 +
  # pooled :6543) behind three consecutive pooler-teardown waves
  # (#1972 → #2015 → #2084) that CI could never reproduce.
  # test/e2e/pgbouncer-teardown.test.ts uses a DEDICATED database
  # (gbrain_pgbouncer) on postgres-1 so it never races shard 1's
  # TRUNCATE-based fixtures; pgbouncer's wildcard [databases] section
  # forwards any dbname to DB_HOST.
  pgbouncer:
    image: edoburu/pgbouncer:latest
    environment:
      DB_HOST: postgres-1
      DB_PORT: "5432"
      DB_USER: postgres
      DB_PASSWORD: postgres
      POOL_MODE: transaction
      # plain (CI-only): pg16 stores SCRAM verifiers, and pgbouncer can only
      # answer the server's SCRAM challenge when its userlist holds the
      # PLAINTEXT password — an md5-hashed userlist fails with
      # "server login failed: wrong password type".
      AUTH_TYPE: plain
      MAX_CLIENT_CONN: "200"
      DEFAULT_POOL_SIZE: "10"
      # gbrain's client sets statement_timeout + idle_in_transaction_session_timeout
      # as startup parameters (db.ts buildConnectionParams); the Supabase pooler
      # whitelists them, so this pooler must too or every connection is refused
      # before the teardown path is even reached.
      IGNORE_STARTUP_PARAMETERS: extra_float_digits,statement_timeout,idle_in_transaction_session_timeout,search_path
    ports:
      - "${GBRAIN_CI_PGBOUNCER_PORT:-6543}:5432"
    depends_on:
      postgres-1:
        condition: service_healthy

  runner:
    image: oven/bun:1
    working_dir: /app
    depends_on:
      postgres-1:
        condition: service_healthy
      postgres-2:
        condition: service_healthy
      postgres-3:
        condition: service_healthy
      postgres-4:
        condition: service_healthy
      pgbouncer:
        condition: service_started
    # No global DATABASE_URL — scripts/ci-local.sh sets per-shard URL via -e.
    # Unit phase explicitly unsets DATABASE_URL so test/e2e/* gracefully skip.
    volumes:
      - .:/app
      # Linux container's node_modules MUST be isolated from host darwin-arm64.
      # Without this, container `bun install` stomps host node_modules and
      # subsequent `bun test` on host fails with binary-incompat errors.
      - gbrain-ci-node-modules:/app/node_modules
      # Warm install cache across runs.
      - gbrain-ci-bun-cache:/root/.bun/install/cache

volumes:
  gbrain-ci-pg-data-1:
  gbrain-ci-pg-data-2:
  gbrain-ci-pg-data-3:
  gbrain-ci-pg-data-4:
  gbrain-ci-node-modules:
  gbrain-ci-bun-cache:
