backend/.github/workflows/product-ci.yml and frontend/.github/workflows/product-ci.yml — plus a set of local-only checks that are faster to run while iterating. This page is the canonical list of both. Run the checks that match what you changed; run the full backend and frontend CI sequence before handing work to another person or agent.
None of this is enforced by a
make target. AGENTS.md is explicit that no Make command is required or expected — use the direct uv, npm, Docker Compose, and Alembic commands below.Backend: Python
ruff targets Python 3.12 with a 100-character line length (pyproject.toml, [tool.ruff]), applied repo-wide. pytest picks up everything under backend/tests/ — over fifty modules covering the tenancy/RLS control plane, auth flows, onboarding, sources, remediation, the durable job worker, and both historical MVP1 pipelines (test_mvp1_*.py, test_banking_relational_runner.py, test_homecare_relational_*.py). test_local_setup_tools.py covers scripts/check_local_setup.py itself.
If a sandbox redirects user caches, prefix uv commands with UV_CACHE_DIR=/tmp/uv-cache.
What backend CI actually runs
product-ci.yml’s backend job, in order:
1
Install and lint
2
Unit and contract tests
CAUSELOOP_OBJECT_PROVIDER=memory, CAUSELOOP_RUNTIME_BACKEND=local, CAUSELOOP_REPOSITORY_BACKEND=local, CAUSELOOP_GRAPH_BACKEND=local, OPENAI_API_KEY="", among others) — no external services, no live LLM calls.3
Build the backend image and prove it has no Neo4j driver
4
Validate both Compose files without starting containers
5
Start a production-parity local data plane
frontend service — the backend job never builds or runs the Next.js console.6
Wait for private API readiness
/health/ready (implemented in services/api/product_app.py) only returns 200 once the database, both worker heartbeats, object storage, credential encryption, email delivery, and (when required) Redis are all ready — see Troubleshooting for what each of those checks means.7
Live product integration tests
55433, 16379, 19000, 18025), not mocks.8
Rehearse backup and restore
9
Tear down
-v to discard the ephemeral volumes; do not add -v to your own local down unless you deliberately intend to delete local product data (see Local product stack).terraform job in the same workflow runs terraform fmt -check/init -backend=false/validate against deploy/aws and compiles deploy/azure/main.bicepparam with the Azure CLI — infrastructure-as-code changes under deploy/ must pass that job too.
Backend workbench: apps/web (legacy research UI)
The Vite workbench used by the basic/mvp1 research modes (Research modes) has its own, much smaller check surface:
apps/web’s test script is tsc -b --noEmit — a type-check, not a runtime test suite. build is tsc -b && vite build. These are exactly the two web checks AGENTS.md calls out. The Makefile’s test-web target only runs npm run test (the type-check); the build is a separate command not covered by any Make target (make test runs test-python and test-web).
Frontend: Next.js console
frontend/.github/workflows/product-ci.yml runs, followed by docker build --tag causeloop-frontend:ci .. A few of these are worth explaining:
npm testrunsvitest run(seefrontend/package.json).npm run test:watchruns the interactivevitestwatcher for local iteration.npm run typecheckistsc --noEmit;npm run lintisnext lint. Both are separate fromnpm testand are not bundled into it.npm run audit:mvp/npm run audit:mvp:strictrunsscripts/audit-mvp-surface.mjs, a TypeScript-AST scanner oversrc/. It walks everyfetch/apiGet/apiSend/adminSend/tenantSendcall, resolves the literal endpoint (including simple template-literal and constant-alias forms), and classifies it against a fixed family list:keepfamilies like workspace-directory, authentication, staff-onboarding, staff-observability, and tenant-insights;targetfamilies the frontend must call (tenant-sources,tenant-members,tenant-remediation); andreplacefamilies flagged as legacy/compatibility —compatibility-session(/api/session),legacy-clients(/clients),legacy-connectors(/connectors),legacy-mvp1(/mvp1),legacy-loops(/loops), andlegacy-datasets(/datasets/status). Any call the scanner can’t classify at all fails the build even under plainnpm run audit:mvp— that check isn’t strict-only.--strictadditionally fails the build if anyreplace-family call site is present, if a required target family has zero call sites, or if production code imports the synthetic fixture, the retired report adapter, or the provenance module. CI always runs the:strictvariant.npm run e2eruns Playwright (playwright.config.ts), againsthttp://localhost:3000with a single Chromium project at a1440×900viewport,fullyParallel: false, andworkers: 1. ItswebServerblock starts (or reuses)npm run devfor you, but the FastAPI product app must already be listening onhttp://127.0.0.1:18000— start it with the product Compose stack first (see Local product stack). Playwright is not part of either CI workflow — it is a local/manual verification step. The unauthenticated redirect spec runs with no configuration; the authenticated specs infrontend/e2e/(auth.spec.ts,console.spec.ts) require a seeded tenant and are explicitly skipped unless all three of these are set:
Product-stack smoke test
scripts/smoke_product.py is a provider-neutral release check: it hits GET /health/live and GET /health/ready through the frontend’s /api/backend proxy (or a direct --api-url you pass), asserting {"status": "ok"} and {"status": "ready", ...} respectively, then confirms GET /login returns an HTML document. It defaults to http://127.0.0.1:13000 — the Compose frontend port — and is meant to be run against a stack that is already up, unlike the CI job above which builds and starts one from scratch.
docker compose ... config (with either compose file) validates configuration only — interpolates env vars, resolves anchors, and checks YAML/schema validity — without starting any container. Run it after touching either compose file.
Before handing off work
Run the checks appropriate to the files you changed, at minimum:
product_app.py route, a src/lib/api/*.ts client function, or anything in the /admin/tenants/*, /insights, /sources, /members, or /remediation/* families), run both, because audit:mvp:strict and the backend’s product contract test (tests/test_product_app_contract.py) each independently enforce the same allowlist from their own side.