Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ jobs:
# via the project venv that uv-sync set up above.
run: bash scripts/ci/verify_enum_source_of_truth.sh

- name: Verify install.sh builds every buildable Compose service (bug_install_skip_ui_rebuild)
# Fails when scripts/install.sh's `docker compose build` line uses an
# explicit service list that doesn't cover every service with a
# `build:` block in docker-compose.yml. Catches the silent-drift class
# of bug where adding a service to Compose leaves operators running a
# stale image on the second `make up` after a code change (the
# smoke-test job runs on a fresh runner so it never sees that path).
run: bash scripts/ci/verify_install_builds_all_services.sh

frontend:
name: frontend (lint + typecheck + tests + build)
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ make migrate-create name=<slug> # alembic revision --autogenerate -m "<slug>"

**Ports (MVP1):**
- API: `127.0.0.1:8000`
- UI dev server: `127.0.0.1:3000` (run via `cd ui && pnpm dev`; not yet a Compose service)
- UI: `127.0.0.1:3000` (Compose service `ui`, rebuilt by `make up`; for hot-reload during frontend work, stop the service with `docker compose stop ui` and run `cd ui && pnpm dev` instead)
- Postgres: internal only (`postgres:5432` on the Compose network; not bound to host)
- Redis: internal only (`redis:6379`)
- Elasticsearch: `127.0.0.1:9200`
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ ui-dev: ## Start the Next.js dev server (http://localhost:3000) — uses .nvmrc
up: ## Generate secrets if missing, then docker compose up -d (auto-bootstrap)
bash scripts/install.sh

down: ## docker compose stop (preserves data volumes)
docker compose stop
down: ## docker compose down (removes containers + network; preserves data volumes)
docker compose down

restart: ## docker compose restart api + worker (fast bounce when something wedges)
docker compose restart api worker
Expand Down
4 changes: 2 additions & 2 deletions docs/01_architecture/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ echo "<openai-key>" > ./secrets/openai_key
make up

# Daily use
make up # docker compose up -d (builds ui image on first run)
make up # docker compose build (all services) + up -d
make logs # docker compose logs -f api worker
make down # docker compose stop
make down # docker compose down (containers removed; data volumes preserved)
make migrate # alembic upgrade head + optuna_schema — idempotent (also runs automatically via the migrate init container at boot)
make seed-clusters # populate local-es + local-opensearch as cluster rows
make seed-es # seed local-es 'products' index from samples/products.json
Expand Down
4 changes: 2 additions & 2 deletions docs/03_runbooks/local-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ If you want the round-trip test to actually run from your shell, you'd need to e

| Target | What it does |
|---|---|
| `make up` | Generate secrets if missing → `docker compose up -d` |
| `make down` | `docker compose stop` (preserves data volumes) |
| `make up` | Generate secrets if missing → `docker compose build` (every buildable service) → `docker compose up -d` |
| `make down` | `docker compose down` (removes containers + network; preserves data volumes) |
| `make logs` | `docker compose logs -f api worker` |
| `make migrate` | `alembic upgrade head` + initialize Optuna RDB schema (idempotent — also runs automatically via the `migrate` init container at boot) |
| `make migrate-create name=<slug>` | New Alembic revision (autogenerate) |
Expand Down
83 changes: 83 additions & 0 deletions scripts/ci/verify_install_builds_all_services.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# bug_install_skip_ui_rebuild regression gate.
#
# Asserts that scripts/install.sh's `docker compose build` invocation covers
# every Compose service that declares a `build:` block. Catches the silent-drift
# class of bug where a service is added to docker-compose.yml but the build
# step in install.sh isn't updated, leaving `make up` with a stale image on
# subsequent runs (the smoke job runs on a fresh runner so it never sees this
# failure mode — only the second-run-after-edit operator does).
#
# Accepts two forms:
# 1. `docker compose build` — no args, builds everything; OK
# 2. `docker compose build a b c` — explicit list; must include every
# service with a `build:` block

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
COMPOSE_FILE="${REPO_ROOT}/docker-compose.yml"
INSTALL_FILE="${REPO_ROOT}/scripts/install.sh"

if [[ ! -f "${COMPOSE_FILE}" ]]; then
echo "verify_install_builds_all_services: ${COMPOSE_FILE} not found" >&2
exit 2
fi
if [[ ! -f "${INSTALL_FILE}" ]]; then
echo "verify_install_builds_all_services: ${INSTALL_FILE} not found" >&2
exit 2
fi

# Extract services with a `build:` block. Service headers are top-level keys
# at 2-space indent; build configs sit at 4-space indent inside them.
buildable=$(awk '
/^[a-z][a-z0-9_-]*:$/ { in_services = ($0 == "services:") ? 1 : 0; next }
in_services && /^ [a-z][a-z0-9_-]*:$/ {
svc = $1; sub(/:$/, "", svc); next
}
in_services && /^ build:/ { print svc }
' "${COMPOSE_FILE}" | sort -u)

if [[ -z "${buildable}" ]]; then
echo "verify_install_builds_all_services: no buildable services found in ${COMPOSE_FILE}" >&2
exit 2
fi

# Extract the `docker compose build [args...]` line from install.sh.
# Match the bare command line (no pipes, no &&) — we want the operative build
# step, not commentary or shell-substitution variants.
build_line=$(grep -E '^docker compose build( .*)?$' "${INSTALL_FILE}" || true)

if [[ -z "${build_line}" ]]; then
echo "verify_install_builds_all_services: no 'docker compose build' line found in ${INSTALL_FILE}" >&2
echo " Expected a top-level invocation that builds images before 'up -d'." >&2
exit 1
fi

# Strip the prefix to get the args (if any).
args=$(echo "${build_line}" | sed -E 's/^docker compose build *//')

if [[ -z "${args}" ]]; then
echo "verify_install_builds_all_services: OK (no-args = builds all)"
exit 0
fi

# Explicit list — every buildable service must appear in args.
missing=()
for svc in ${buildable}; do
if ! grep -qE "(^| )${svc}( |$)" <<<"${args}"; then
missing+=("${svc}")
fi
done

if (( ${#missing[@]} > 0 )); then
echo "verify_install_builds_all_services: FAIL" >&2
echo " install.sh build invocation: 'docker compose build ${args}'" >&2
echo " Missing services: ${missing[*]}" >&2
echo " Fix: either add the missing services, or drop all args so the line" >&2
echo " becomes 'docker compose build' (which builds every service that" >&2
echo " declares a build: block — preferred, drift-proof)." >&2
exit 1
fi

echo "verify_install_builds_all_services: OK (explicit list covers all buildable services)"
5 changes: 4 additions & 1 deletion scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ docker compose config --quiet
# explicit build step, contributors who pull new code and re-run `make up`
# keep running the stale image (PR #4 first-run testing surfaced exactly
# this — a stale image missing newly-added Python deps).
docker compose build api worker
# No-args = build every service that declares a `build:` block. The earlier
# hardcoded `api worker` list silently skipped the `ui` service after it
# joined Compose, leaving frontend changes invisible until manual rebuild.
docker compose build

# 7. Bring the stack up. `docker compose up -d` is itself idempotent.
exec docker compose up -d
Loading