docker-compose.ci.yml
Rama main · réplica de lectura del repositorio original.
# CI overlay for the end-to-end smoke test. Never part of a real deployment.
#
# `minimal` deliberately starts no embedder, so booting the whole loop in CI
# needs one supplied. This adds a stub that answers the /embeddings contract
# with a constant vector — enough to drive the permission loop, where the ACL
# pre-filter and not relevance decides what a search returns.
#
# It also pulls MinIO forward into `minimal`, which no real deployment should
# do — object storage is `full`, and for a licensing reason (AGPLv3; see
# docs/licensing.md). CI needs it because **binary ingest requires a bucket**:
# a PDF's bytes have no other home, so the smoke cannot prove that path
# without one. The text half of the loop runs through the bucket too as a
# result, which is coverage the smoke did not have before.
#
# Used only as `docker compose -f docker-compose.yml -f docker-compose.ci.yml`,
# so it is invisible to `docker compose --profile … config` and to the
# lint:compose check, which read the base file alone.
services:
# `minimal` as well as `full`. Both are named because Compose's merge rule
# for a sequence — append to the base, or replace it — decides what this
# becomes, and `minimal` has to survive either answer.
minio:
profiles: [minimal, full]
# The bucket-creating one-shot now lives in the base file, in `full`, because
# a real deployment needs it for the same reason CI does. Only its profile
# moves here — same as minio above, and for the same merge reason.
minio-init:
profiles: [minimal, full]
stub-embedder:
image: node:24-alpine
restart: unless-stopped
command: ["node", "/opt/stub/stub-embedder.mjs"]
environment:
PORT: "8000"
# Kept in step with the .env the smoke workflow writes, so the vector
# width matches the collection init builds.
NACRE_DEFAULT_EMBEDDING_DIM: "${NACRE_DEFAULT_EMBEDDING_DIM:-8}"
volumes:
- ./scripts/ci:/opt/stub:ro
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://localhost:8000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 3s
timeout: 2s
retries: 10
start_period: 3s