The production pass (7ed2ac4e, E32) asked "is this export used by what
ships?". No real hit: its only findings were 37 exports a test imports,
answered with 36 `@internal` JSDoc tags (E34 S2) — test-only exports, not
defects. Since then it has only ever asked for another tag. The tags go
with it (36, no longer read by anything), and so do the production-only
`!…!` exclusions in knip.jsonc and their register rows. Plain knip (unused
files, exports and dependencies) keeps running in the gate.
126 lines
14 KiB
Markdown
126 lines
14 KiB
Markdown
# 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.
|
||
|
||
**The snapshot.** ESLint's effective config for every code file `eslint .` reaches, and the lines
|
||
that run the lanes (`scripts/check.sh`, `backend/docker-compose.test.yml`,
|
||
`backend/Dockerfile.test`, the npm scripts, `vitest.config.ts`, `.prettierignore`), are committed in
|
||
[`frontend/src/lib/gates.snapshot.json`](../frontend/src/lib/gates.snapshot.json). The test fails on
|
||
ANY difference — a rule, an option, a glob that moves one file to another config, a new ignore, a gate
|
||
flag. So a change to a gate is always a diff of that file in review, never a shape a detector failed
|
||
to model (E32 review: three rounds of a cleverer detector each found a bypass of the last one's
|
||
fixes). Regenerate it with `GATE_SNAPSHOT=update npx vitest run src/lib/gates.contract.test.ts`
|
||
(from `frontend/`), and read its diff as a gate change: a new file only adds its path; anything else
|
||
needs a reason. A new code file is part of that diff by design.
|
||
|
||
**How the register is matched:** 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 (a cell
|
||
may list several, separated by ` · `). For ESLint the scopes are the snapshot's config names, and a
|
||
narrowing is read off each config against `src`: a rule at off or warn, or one src applies that a
|
||
scope does not (one id per plugin family), a `no-restricted-*` list that leaves out an entry src's
|
||
declares, an option that lets something through (`allow…`, `ignore…`, `except…`), an unused-disable
|
||
report that is not an error, and a file no config lints. Ruff's TOML is parsed and held to a
|
||
whitelist of key paths, pytest.ini to a whitelist of whole lines.
|
||
|
||
## ESLint (frontend)
|
||
|
||
`npm run lint` is `eslint . --max-warnings 0`, so a rule at **warn** fails the lane exactly like an
|
||
error (the test holds that exact script). 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 | `@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`). | 0 suppressions (E32 S4: a suppression of this rule drops the whole function from the React Compiler, so the two that were left went) |
|
||
| 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 | `@typescript-eslint/no-floating-promises` — narrowed | `allowForKnownSafeCalls`: React Query's `invalidateQueries`, `refetchQueries`, `cancelQueries`, `resetQueries`, `prefetchQuery`, `prefetchInfiniteQuery` never reject unless asked to (`throwOnError`), so a bare call is safe by name. | 6 names |
|
||
| e2e | `@typescript-eslint/no-floating-promises` — narrowed | The same `allowForKnownSafeCalls` list (the typed rules are one object for both scopes). | 6 names |
|
||
| lib/http.ts | `no-restricted-globals` — narrowed | A network owner (`NETWORK_OWNERS` in eslint.config.js): the one module that may call `fetch` (`send`, which `req` and the public pages go through). Every other network global stays restricted there. | 1 global |
|
||
| lib/api/core.ts | `no-restricted-globals` — narrowed | A network owner: `uploadReq` is the one `XMLHttpRequest` (upload progress). | 1 global |
|
||
| src tests · ui tests · lib 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 · ui tests · lib 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 + AnchoredPopover.tsx | `local/focus-safe-disabled` — off | FocusSafeButton: it IS the focus-safe primitive the rule points everyone else to. | — |
|
||
| FocusSafeButton.tsx + AnchoredPopover.tsx | `local/focus-safe-disabled` — off | AnchoredPopover: 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. | — |
|
||
| repo scripts | `scripts/*.mjs` — not in the eslint or prettier lane; `js.configs.recommended` + Prettier via `src/lib/repoScripts.contract.test.ts` | They sit at the repo root, and an ESLint config cannot reach past its own directory. The contract test lints and format-checks them from the repo root, to the same bar as the `js configs` row; no typed rules, as they are in no tsc project. | 0 lint hits; 2 files reformatted (E32 review) |
|
||
| 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 |
|
||
| --- | --- | --- | --- |
|
||
| config files | `eslint.config.js — not type-checked` | Linted (the `js configs` row) and executed by every lint run, but in no tsc project. Under the node project's `strict` + `checkJs` it reports 43 errors: 42 implicit-`any` parameters in the local rules' visitors (JSX nodes, which ESLint's own `Rule` typings do not model) and one inferred `string` severity. None is a defect; typing them means moving the local rules onto typescript-eslint's `TSESTree` types — a refactor of its own, not done in E32. | 43 (E32 review) |
|
||
| 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, if a `strict` sub-flag
|
||
(`strictNullChecks`, `noImplicitAny`, …) is set false beside it, if a project gains an `extends`,
|
||
`exclude` or `files`, or if its `include` changes — and a config file at the frontend root that no
|
||
project checks needs a row.
|
||
|
||
## Ruff (backend + scripts)
|
||
|
||
| Scope | Narrowed | Why | Measured |
|
||
| --- | --- | --- | --- |
|
||
| 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/app | `C901` — not in ruff; reported by `scripts/audit_metrics.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. Complexity is a number an epic reads before and after (E35), not a gate: the ratchet that held it caught no defect in E32–E34, went red 8 times on its own ceilings and drew 5 review findings about itself. | 32 functions over 10 (E34) |
|
||
|
||
## 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. | — |
|
||
|
||
## 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 |
|
||
| 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) |
|
||
| 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) |
|
||
|
||
## Accepted findings
|
||
|
||
Entries in `.trivyignore.yaml` (CVE gate) and `--ignore-vuln` flags on the gate's `pip-audit` lane (`scripts/check.sh`). 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 | — | — |
|