diff --git a/CLAUDE.md b/CLAUDE.md index 5c504e39..83b17e98 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ router-free navigation provider. ## The gate — never merge or ship red The local gate is **ruff (`check` + `format --check`) + basedpyright (backend) + the lock check + typecheck (3 tsc projects) + -eslint + prettier --check + knip + vitest + vite build + pytest + pip-audit + npm audit**, then a coverage report (no threshold). It runs +eslint + prettier --check + knip + vitest + vite build + pytest + pip-audit + npm audit**. It runs before every `release` merges and on every `publish`, which **refuses to build on failure — there is deliberately no `--skip` flag**; a release also requires the Playwright suite, and the image must pass a Trivy scan before it is pushed. Run it and get it green **before** any merge, release, or handing work to review. diff --git a/backend/docker-compose.test.yml b/backend/docker-compose.test.yml index 4ce8ae39..67dedcf6 100644 --- a/backend/docker-compose.test.yml +++ b/backend/docker-compose.test.yml @@ -35,9 +35,5 @@ services: volumes: - ./:/app working_dir: /app - # The suite, then the coverage total (E32 D4: reported, no threshold). pytest's exit code is the - # lane's — the report is printed either way and never decides it. - command: - - sh - - -c - - 'python -m pytest --cov=app --cov-report= ; rc=$$?; echo ">> backend coverage (app/) $$(python -m coverage report --format=total)% of lines"; exit $$rc' + # Coverage is on demand, not in the gate (E35): see docs/development.md. + command: ["python", "-m", "pytest"] diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 6480647e..862ae1cc 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -7,8 +7,8 @@ # basedpyright: the backend type-check lane (E27 S3). Pinned like everything else here — a type # checker whose version floats silently changes what "clean" means, and the ratchet baseline it # writes is keyed to the diagnostics THIS version produces. -# pytest-cov: the coverage REPORT the gate prints (E32 D4) — a number to read, deliberately with no -# threshold; "every regression test seen red" is the bar, not a percentage. +# pytest-cov: the on-demand coverage report (docs/development.md) — a number to read, deliberately +# with no threshold; "every regression test seen red" is the bar, not a percentage. # pip-audit: the gate's audit of requirements.lock (scripts/check.sh), blocking at any severity. basedpyright==1.40.1 pytest==8.4.2 diff --git a/docs/development.md b/docs/development.md index 10fb564a..bd9171e3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -75,8 +75,8 @@ python -m ruff format --check --config backend/ruff.toml backend scripts docker compose -f backend/docker-compose.test.yml run --rm --no-deps --build test \ python -m basedpyright --project pyrightconfig.json -# tests — pure-logic + the DB-backed integration lane, against an ephemeral Postgres, ending with the -# coverage total. The backend tree is bind-mounted, so it always tests your working files. +# tests — pure-logic + the DB-backed integration lane, against an ephemeral Postgres. The backend +# tree is bind-mounted, so it always tests your working files. docker compose -f backend/docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from test docker compose -f backend/docker-compose.test.yml down -v @@ -103,7 +103,10 @@ npx vite build # the production bundle (devOnlyGuard fails it on dev-only npm audit --omit=dev --audit-level=high # what ships to the browser, at high or above ``` -Coverage is reported, never enforced: `npm run test -- --coverage`, and the pytest lane's last line. +Coverage is on demand, never enforced and not in the gate (E35): `npm run test -- --coverage` in +`frontend/`, and for the backend +`docker compose -f backend/docker-compose.test.yml run --rm test python -m pytest --cov=app` (then +`… down -v`). The Playwright suite (`npm run e2e`) runs against the `:8080` stack built from your tree; the release requires it. Before an image is pushed, it is scanned with Trivy (HIGH/CRITICAL with a fix fails; exceptions in `.trivyignore.yaml`, each with a reason and an expiry). diff --git a/docs/gates.md b/docs/gates.md index 651e4d34..67de78a8 100644 --- a/docs/gates.md +++ b/docs/gates.md @@ -107,7 +107,7 @@ project checks needs a row. | --- | --- | --- | --- | | check | e2e (Playwright) not in `check` | It needs the :8080 stack built from the tree (minutes). It runs in `check-full`, and `release` rebuilds the stack and requires it (E32 D2) — nothing ships without it. | 5 specs × 3 engines | | pushes | no server-side gate (no CI) | Owner decision 2026-10-04. The gate (`scripts/check.sh`) runs on the dev machine before work is handed over, and `release` runs it, then e2e, before anything is merged and Trivy before the image is pushed — the CI only repeated that, after the merge. Its runner shared the git host's container, and one run there starved Gitea of memory until it stopped answering. What it added — a fresh `npm ci` on Linux — the image build does at publish. | — | -| coverage | no threshold | Owner decision E32 D4: reported on both sides, never enforced. "Every regression test seen red" is the bar; a percentage invites tests written to move it. | frontend 80% / backend 78% of lines (E32 S1) | +| coverage | not in the gate; no threshold | Owner decision E32 D4: never enforced; "every regression test seen red" is the bar, and a percentage invites tests written to move it. Out of the lanes since E35: read once, by hand, it found 5 bugs; as a lane it was a number nobody acted on, and the slowest vitest run (the one that flaked). On demand: docs/development.md. | frontend 80% / backend 78% of lines (E32 S1) | | CVE gate | HIGH/CRITICAL with a fix available only | `--ignore-unfixed` + `--severity HIGH,CRITICAL`: a finding with no fix cannot be acted on by an upgrade; it shows in the scan output. A fixable one fails the publish, and the only way past it is `.trivyignore.yaml`. | 0 open (E32 S1) | | npm audit | `--omit=dev --audit-level=high` | What ships to the browser, at high or above. The dev toolchain's advisories were burned down by hand in E32 S1 (18 → 0). | 0 | | React Compiler | the functions it skips, pinned | `components/compilerCoverage.contract.test.ts` — the list may only shrink. End state: only VirtualGrid (TanStack Virtual, an IncompatibleLibrary) remains. | 93 (E32 S1) → 29 (E32 S3) → 1, VirtualGrid (E32 S4) | diff --git a/frontend/src/lib/gates.snapshot.json b/frontend/src/lib/gates.snapshot.json index 46fe0b2a..c96525f4 100644 --- a/frontend/src/lib/gates.snapshot.json +++ b/frontend/src/lib/gates.snapshot.json @@ -2110,10 +2110,7 @@ " volumes:", " - ./:/app", " working_dir: /app", - " command:", - " - sh", - " - -c", - " - 'python -m pytest --cov=app --cov-report= ; rc=$$?; echo \">> backend coverage (app/) $$(python -m coverage report --format=total)% of lines\"; exit $$rc'" + " command: [\"python\", \"-m\", \"pytest\"]" ], "frontend/.prettierignore": [ "dist", @@ -2238,7 +2235,7 @@ " pip-audit -r requirements.lock --no-deps --disable-pip", "}", "if [ \"$mode\" = all ]; then", - " lane \"vitest (+ coverage report)\" frontend npm run --silent test -- --coverage", + " lane \"vitest\" frontend npm run --silent test", " lane \"vite build\" frontend npx vite build --logLevel warn", " lane \"pytest (pure-logic + DB lane)\" pytest_lane", " lane \"pip-audit (backend lock)\" pip_audit", diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index f7fc3212..ed2e85e3 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -19,8 +19,8 @@ export default defineConfig({ // collected, and the gate still prints green. include: ["src/**/*.test.{ts,tsx}"], environment: "node", - // E32 D4: a coverage REPORT, read and not enforced — the gate's `vitest --coverage` prints the - // summary; no threshold, because "every regression test seen red" is the bar, not a percentage. + // A coverage REPORT on demand (`npm run test -- --coverage`), never enforced and not in the gate + // (E32 D4, E35): "every regression test seen red" is the bar, not a percentage. coverage: { provider: "v8", include: ["src/**/*.{ts,tsx}"], diff --git a/scripts/check.sh b/scripts/check.sh index 6efbaaf2..66f3b95a 100644 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -120,8 +120,7 @@ pip_audit() { } if [ "$mode" = all ]; then - # --coverage prints the summary: read, never a threshold (E32 D4) - lane "vitest (+ coverage report)" frontend npm run --silent test -- --coverage + lane "vitest" frontend npm run --silent test # the production bundle, and devOnlyGuard, which fails it when dev-only code reaches it lane "vite build" frontend npx vite build --logLevel warn lane "pytest (pure-logic + DB lane)" pytest_lane