Files
siftlode/docs/development.md
T
peter 783f6408f9 docs: the gate register, held to the effective config by a test
docs/gates.md lists every place a gate deliberately checks less than it
could, each with its reason and the count measured when the row was
written:
- an ESLint rule off, at warn, or not applied, by scope;
- a weak compiler flag, a ruff ignore, the basedpyright mode and baseline;
- a knip exclusion, a lane that does not run everywhere;
- an accepted CVE.
gates.contract.test.ts reads the EFFECTIVE configuration and fails when a
narrowing has no row:
- ESLint's computed config per scope;
- the three tsconfigs, ruff.toml, pyrightconfig, pytest.ini and knip.jsonc;
- .trivyignore.yaml, where an entry also needs a statement and an expiry;
- CI's pip-audit ignores.
It also pins `--max-warnings 0`, which is what makes a warn-level rule fail.
Seen red on a missing row, a new `off` rule and an allowlist entry without
an expiry.

CLAUDE.md gains the rule E32 exists for: a gate with any real hit is never
switched off or narrowed, and an "enforced by" comment is checked by running
the check. The gate descriptions there, in docs/development.md and in
backend/app/README.md name the lanes as they now are, and the "directly"
recipe points at the full list.
2026-10-03 17:26:42 +02:00

91 lines
4.4 KiB
Markdown

# Development
How to run Siftlode locally and pass the gate. Conventions for the backend code itself live in
[`backend/app/README.md`](../backend/app/README.md).
## Node
One major, everywhere. `.nvmrc` pins it; the CI container, the `Dockerfile`'s frontend build stage,
`frontend/package.json`'s `engines` and `@types/node` must all match it — and so must the machine you
run the gate on. `frontend/src/lib/nodeEngine.contract.test.ts` fails the gate the moment any of them
drift apart, including when yours is the odd one out. `nvm use` in the repo root picks it up.
This is a contract, not a preference: tsc and vitest read the engine's own `Intl` and web APIs, and
those change between majors — the same currency format renders `$160M` on one and `$160.0M` on the
next. A gate that is green on a major you do not ship is green about nothing.
## Run it locally
The whole stack (Postgres + API + worker) comes up from the localdev compose file:
```bash
docker compose -f docker-compose.localdev.yml up --build
```
Browse at **http://localhost:8080** (Google login works — the OAuth redirect is
`http://localhost:8080/auth/callback`). The API runs `alembic upgrade head` on startup, so the schema
is always current.
For a fast frontend edit loop, run the Vite dev server with HMR against that API:
```bash
cd frontend && npm install && npm run dev # http://localhost:5173, proxying the API on :8080
```
The dev server also serves the **design lab** at `http://localhost:5173/design-lab`: the open design
decisions and the chosen patterns, rendered with the app's real CSS. It never reaches a production build
(see [`DESIGN.md`](DESIGN.md)).
## The gate
A change is done when all of this is green. CI runs the same checks server-side
(`.gitea/workflows/check.yml`), so "main is always green". What each gate deliberately does NOT
check — and why — is the [gate register](gates.md); a test fails when a narrowing is missing from it.
**Backend**
```bash
# lint + format check (ruff is pinned in backend/requirements-dev.txt)
python -m ruff check --config backend/ruff.toml backend scripts
python -m ruff format --check --config backend/ruff.toml backend scripts
# type check — in the test image (requirements.lock does not install on Windows); new errors only,
# the pre-existing ones are in backend/.basedpyright/baseline.json
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.
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
```
A missing database or ffmpeg is a hard error, not a skip — so the gate can't print green while the
DB-backed tests were quietly not run (`SIFTLODE_TEST_REQUIRE_DB=0` is the explicit "pure-logic only"
run for a machine without Postgres). Tests that need a database take the `db` fixture
(`backend/tests/conftest.py`); the rest are pure-logic and run without one.
**Frontend** (from `frontend/`)
```bash
node ../scripts/check-node-modules.mjs # node_modules == package-lock.json (else: npm ci)
npm run typecheck # tsc x3 (app / node / e2e)
npm run lint # eslint (typed rules included; warnings fail)
npm run format:check # prettier
npm run knip # unused files/exports/deps — and unused by what SHIPS (--production)
npm run test # vitest (incl. the compiler, size/complexity and gate-register contracts)
npx vite build # the production bundle (devOnlyGuard fails it on dev-only code)
```
Coverage is reported, never enforced: `npm run test -- --coverage`, and the pytest lane's last line.
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).
## Branching
Trunk-based: branch off `main`, keep the branch gate-green, and land the whole change back to `main`
in one reviewed step. Migrations are Alembic revisions under `backend/alembic/versions`; the API
applies them on startup, and the DB-lane rebuilds the schema via `alembic upgrade head`, so a broken
migration chain fails the tests.