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.
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
# Gate register
|
||||
|
||||
Every place a gate deliberately checks **less** than it could: a rule switched off or narrowed for a
|
||||
scope, a flag left off, a lane that does not run everywhere, an accepted finding. One row each, with
|
||||
the reason and the number measured when the row was written.
|
||||
|
||||
**Why this file exists (E32).** The worst defect E32 found was not in any code a reviewer read: a lint
|
||||
rule had been switched off with 64 real hits counted in its own comment, and the "false positives"
|
||||
beside them were the React Compiler skipping 95 functions without a word. The rule since: **a gate
|
||||
with any real hit is never switched off or narrowed** — fix the sites, or suppress each one inline and
|
||||
name its invariant. Whatever is left narrowed is listed here, and
|
||||
[`frontend/src/lib/gates.contract.test.ts`](../frontend/src/lib/gates.contract.test.ts) reads the
|
||||
**effective** configuration and fails when a narrowing is not on this page. A row here is a decision,
|
||||
not an oversight; a narrowing missing from here is a test failure.
|
||||
|
||||
How the test matches: each narrowing must appear in a table row below as its `code-quoted` id, in a
|
||||
row whose **Scope** cell names the scope exactly as the test reports it.
|
||||
|
||||
## ESLint (frontend)
|
||||
|
||||
`npm run lint` is `eslint . --max-warnings 0`, so a rule at **warn** fails the lane exactly like an
|
||||
error (the test checks the flag is still there). The warn rows are listed so that claim stays checked.
|
||||
A core rule switched off because its typescript-eslint twin replaces it (`no-unused-vars` →
|
||||
`@typescript-eslint/no-unused-vars`) is not listed separately; neither are the core rules
|
||||
typescript-eslint's compatibility layer switches off for TS files because the type checker reports
|
||||
them itself (`no-undef`, `no-redeclare`, `no-dupe-keys`, …), nor the formatting rules
|
||||
`eslint-config-prettier` switches off (Prettier owns formatting and has its own lane).
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| src | `react-hooks/refs` — off | The one rule off while it has real hits. Its analysis IS the React Compiler's: every hit is a component the compiler skips (the popover engine's `getReferenceProps()` / `refs.setReference` passed during render, render-phase ref writes). Too many to fix in one sprint; until they are, `components/compilerCoverage.contract.test.ts` pins every skipped function (none may be added) and E32 S3+ burns them down to turn this on. | 245 hits; 93 skipped functions (E32 S1) |
|
||||
| src | `@typescript-eslint/no-unused-vars` — off | tsc owns it (`noUnusedLocals` + `noUnusedParameters` in every project), so a finding is reported once, by one tool. | — |
|
||||
| src | `react-hooks/exhaustive-deps` — warn | Preset level; `--max-warnings 0` makes it fail. Every suppression names its invariant (`eslint-comments/require-description`). | 2 suppressions (both drop their whole function from the compiler — E32 S3) |
|
||||
| src | `react-hooks/incompatible-library` — warn | Preset level; fails under `--max-warnings 0`. VirtualGrid (TanStack Virtual) is the one site, suppressed inline. | 1 |
|
||||
| src | `react-hooks/unsupported-syntax` — warn | Preset level; fails under `--max-warnings 0`. | 0 |
|
||||
| src | `react-refresh/only-export-components` — warn | Fails under `--max-warnings 0`; `allowConstantExport` lets a module export constants beside its component. | 0 |
|
||||
| src | `@typescript-eslint/no-unused-expressions` — narrowed | `allowShortCircuit` + `allowTernary`: `cond && f()` / `a ? f() : g()` side-effect statements are an idiom here. | — |
|
||||
| src tests | `no-restricted-globals`, `no-restricted-properties` — off | A test stands a double in for the network (`vi.stubGlobal("fetch", …)`) and may reach it directly. | — |
|
||||
| src tests | `local/literal-a11y-text`, `local/button-primitive`, `local/input-primitive`, `local/surface-role`, `local/glyph-link` — off | A test renders throwaway markup (a raw button as a focus target, an untranslated label) that is not product UI. | — |
|
||||
| FocusSafeButton.tsx | `local/focus-safe-disabled` — off | It IS the focus-safe primitive the rule points everyone else to. | — |
|
||||
| AnchoredPopover.tsx | `local/focus-safe-disabled` — off | The popover's menu row: its native `disabled` is what floating-ui's roving focus reads to skip it. | — |
|
||||
| design-lab | `local/literal-a11y-text` — off | The dev-only design lab (never in the bundle — devOnlyGuard fails the build if it is) holds the owner's working copy, not product copy. | — |
|
||||
| e2e | `react-hooks/*`, `react-refresh/*`, `local/*`, `no-console`, `no-restricted-globals`, `no-restricted-properties`, `no-restricted-syntax` — not applied | Playwright specs drive a browser from Node: no React, no product UI, and they print to the console on purpose. They get the base + typed rules + the playwright rules. | — |
|
||||
| ts configs | `react-hooks/*`, `react-refresh/*`, `local/*`, `no-console`, `no-restricted-globals`, `no-restricted-properties`, `no-restricted-syntax` — not applied | The build configs (vite, vitest, playwright, reactPlugin, devOnlyGuard) run in Node at build time. | — |
|
||||
| js configs | `@typescript-eslint/*`, `react-hooks/*`, `react-refresh/*`, `local/*`, `no-console`, `no-restricted-globals`, `no-restricted-properties`, `no-restricted-syntax`, `no-var`, `prefer-const`, `prefer-rest-params`, `prefer-spread`, `no-array-constructor`, `no-unused-expressions` — not applied | `eslint.config.js` and `postcss.config.js`: plain-JS build config, linted with `js.configs.recommended` only. | — |
|
||||
| everywhere | ignored paths: `dist`, `node_modules`, `playwright-report`, `test-results`, `e2e/.auth` | Build output, installed packages, test artefacts, a live Playwright session. Generated, not written. | — |
|
||||
|
||||
**Not enabled** (decided, not forgotten): `strictTypeChecked` — owner decision E32 D3, ~2000 hits with
|
||||
none real in the sample; the curated typed pair `no-floating-promises` + `no-deprecated` runs instead.
|
||||
|
||||
## TypeScript
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| all three projects | `skipLibCheck` | The `.d.ts` of installed packages are not type-checked (ours are). Standard for an app: a package's own types are its maintainers' gate, and checking them costs minutes and reports errors we cannot fix. | — |
|
||||
|
||||
Every project has `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals`, `noUnusedParameters` and
|
||||
`noFallthroughCasesInSwitch` on; the test fails if one goes off.
|
||||
|
||||
## Ruff (backend + scripts)
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| backend + scripts | `E402` — ignored | A few modules (`main.py`, `scripts/e2e_seed.py`) run setup — logging, `sys.path` — before their imports on purpose. | — |
|
||||
| backend + scripts | `B008` — narrowed: `fastapi.Depends`, `fastapi.Query` declared immutable | FastAPI's dependency injection IS a call in a default argument, on every route. Every other call in a default still fails. | 474 sites, all of them FastAPI |
|
||||
| backend + scripts | `C901` — not in ruff; measured by `tests/test_source_size.py` | Ruff has no per-function baseline, so turning it on would need a `noqa` per offender, and a `noqa` lets a function grow without limit. The test holds each offender at its current value. | 32 functions over 10 |
|
||||
|
||||
## basedpyright (backend)
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| backend/app + alembic | `typeCheckingMode` — `standard` | Not `strict`/`all`: those report on every untyped third-party boundary (SQLAlchemy rows, yt-dlp dicts) — thousands of findings, near-zero real ones. `standard` was measured at 7 findings, all fixed (E32 S1). | — |
|
||||
| backend/app + alembic | `baseline.json` — pre-existing errors absorbed | The lane fails on NEW errors only; the baseline only shrinks (the lane fails when a run tightens it and it is not committed). Matching is per (file, rule, column span) + count, so burn the hot files first. | 195 entries (E32 S1) |
|
||||
| tests/ | not type-checked | A test's job is to fail loudly when wrong — the opposite of the silent runtime failure this lane catches — and annotating fixtures for a checker is churn. | — |
|
||||
|
||||
## pytest (backend)
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| a hand run | `SIFTLODE_TEST_REQUIRE_DB=0` — the DB-backed and ffmpeg tests skip | The explicit "pure-logic only" run for a machine without Postgres. Off by default since E32: without it a missing DB is a hard error; the gate and CI never set it. | — |
|
||||
|
||||
## knip (frontend)
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| --production | `!**/*.testkit.ts!`, `!e2e/**!` | Test kits and the e2e suite are test code: the production pass asks what the SHIPPED code uses. | — |
|
||||
| --production | `!reactPlugin.ts!`, `!devOnlyGuard.ts!` | Build tooling the vite config imports: it shapes the bundle but is not in it. | — |
|
||||
| --production | `@internal` exports | An export only its test imports says so with a JSDoc `@internal` tag naming the test; knip skips those in production mode. | 35 (E32 S1) |
|
||||
|
||||
## Lanes and pipeline
|
||||
|
||||
| Scope | Narrowed | Why | Measured |
|
||||
| --- | --- | --- | --- |
|
||||
| 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 |
|
||||
| CI | no e2e, no Trivy | The lab runner has no browser stack, and Trivy's DB download is not reachable from it. Both run in `release` / `publish` on the dev machine, before anything is pushed. | — |
|
||||
| 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) |
|
||||
| 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 (CI) | `--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 | 93 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) |
|
||||
| size / complexity | today's offenders as ceilings | `src/lib/sourceSize.contract.test.ts`, `backend/tests/test_source_size.py`: listed files and functions may not grow, nothing new may cross the limit, an entry back under it must be deleted. | files >800 lines: 13 FE / 11 BE; functions over the limit: 64 FE / 32 BE |
|
||||
|
||||
## Accepted findings
|
||||
|
||||
Entries in `.trivyignore.yaml` (CVE gate) and `--ignore-vuln` flags on CI's `pip-audit` step. Each
|
||||
must have a row here (Scope `accepted`) with its reason and its expiry; the test fails on an entry
|
||||
without one, and on a `.trivyignore.yaml` entry with no `statement` or no `expired_at`.
|
||||
|
||||
| Scope | Finding | Why | Expires |
|
||||
| --- | --- | --- | --- |
|
||||
| — | none | — | — |
|
||||
Reference in New Issue
Block a user