@syntopica/db-quality 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -25
- package/assets/db-quality.yml +2 -0
- package/dist/cli.js +1894 -155
- package/dist/cli.js.map +1 -1
- package/package.json +19 -16
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ everything as one gate with honest exit codes. Brings to databases what
|
|
|
8
8
|
|
|
9
9
|
- **Design spec:**
|
|
10
10
|
[docs/superpowers/specs/2026-09-25-db-quality-design.md](https://github.com/syntopica/codeality/blob/main/docs/superpowers/specs/2026-09-25-db-quality-design.md)
|
|
11
|
+
- **Strict mode and performance design spec:**
|
|
12
|
+
[docs/superpowers/specs/2026-09-25-db-quality-perf-design.md](https://github.com/syntopica/codeality/blob/main/docs/superpowers/specs/2026-09-25-db-quality-perf-design.md)
|
|
11
13
|
|
|
12
14
|
## Install
|
|
13
15
|
|
|
@@ -16,8 +18,11 @@ pnpm add -D @syntopica/db-quality squawk-cli prisma-lint eslint eslint-plugin-dr
|
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
Install only the peers your stacks need: `squawk-cli` for Supabase migrations,
|
|
19
|
-
`prisma-lint` for Prisma, the three ESLint packages for Drizzle
|
|
20
|
-
CLI, `sqlite3
|
|
21
|
+
`prisma-lint` for Prisma, the three ESLint packages for Drizzle, `typescript`
|
|
22
|
+
for the PostgREST rules (`postgrest.roots`). The Supabase CLI, `sqlite3`, `uvx`
|
|
23
|
+
and `psql` are external executables; `psql` is required by the `perf` commands
|
|
24
|
+
and the gate's `perf` stage, and a missing one fails with exit 3 rather than
|
|
25
|
+
skipping silently.
|
|
21
26
|
|
|
22
27
|
## Usage
|
|
23
28
|
|
|
@@ -25,51 +30,266 @@ CLI, `sqlite3` and `uvx` are external executables.
|
|
|
25
30
|
codeality-db init [--check|--apply|--force] # codeality-db.json, db:gate script, CI workflow
|
|
26
31
|
codeality-db check [--json] # static findings, never writes
|
|
27
32
|
codeality-db audit --linked|--db-url <url> # live Supabase advisors, inspect, Soda
|
|
28
|
-
codeality-db gate # check or baseline check,
|
|
33
|
+
codeality-db gate # check (or baseline check), the linked audit, then perf
|
|
29
34
|
codeality-db baseline create|update|check # record and enforce the debt you carry
|
|
35
|
+
codeality-db perf snapshot|diff|bench [--db-url <url>] [--json] [--record] # record, compare and benchmark the live database
|
|
30
36
|
```
|
|
31
37
|
|
|
32
38
|
`--project <dir>` before the command runs against another directory.
|
|
33
39
|
|
|
34
40
|
## Configuration
|
|
35
41
|
|
|
36
|
-
`codeality-db.json`, written by `init` from the stacks it detects
|
|
42
|
+
`codeality-db.json`, written by `init` from the stacks it detects. This example
|
|
43
|
+
is the **phase 4** configuration — see [Adoption in phases](#adoption-in-phases)
|
|
44
|
+
— with every section adopted and `perf.inGate: true`; `init` itself writes
|
|
45
|
+
`perf.inGate: false` and no `postgrest` section until `@supabase/supabase-js` is
|
|
46
|
+
a dependency:
|
|
37
47
|
|
|
38
48
|
```json
|
|
39
49
|
{
|
|
40
|
-
"schemaVersion":
|
|
50
|
+
"schemaVersion": 2,
|
|
41
51
|
"supabase": { "migrations": "supabase/migrations" },
|
|
42
52
|
"prisma": { "schema": "prisma/schema.prisma" },
|
|
43
53
|
"drizzle": { "roots": ["src"], "objectNames": ["db", "tx"] },
|
|
44
54
|
"sqlite": { "files": ["data/app.db"] },
|
|
55
|
+
"postgrest": { "roots": ["src", "app"] },
|
|
45
56
|
"audit": { "inGate": true, "bloatThreshold": 5, "soda": "db-quality/soda" },
|
|
46
|
-
"
|
|
57
|
+
"perf": {
|
|
58
|
+
"inGate": true,
|
|
59
|
+
"slowMs": 100,
|
|
60
|
+
"regressionPercent": 20,
|
|
61
|
+
"minCalls": 20,
|
|
62
|
+
"seqScanRows": 10000,
|
|
63
|
+
"benchDir": "db-quality/bench",
|
|
64
|
+
"benchRuns": 5,
|
|
65
|
+
"benchTimeoutMs": 60000,
|
|
66
|
+
"roles": ["authenticator", "service_role", "postgres"],
|
|
67
|
+
"ignore": []
|
|
68
|
+
},
|
|
69
|
+
"disable": [
|
|
70
|
+
{
|
|
71
|
+
"code": "BDB100/prefer-bigint-over-int",
|
|
72
|
+
"reason": "int keys are a deliberate choice, see ADR-0007"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
47
75
|
}
|
|
48
76
|
```
|
|
49
77
|
|
|
50
78
|
Every section is optional. `audit.soda` names a directory holding a Soda Core
|
|
51
79
|
`checks.yml`; it runs only with `--db-url`, because a linked project carries no
|
|
52
|
-
database password.
|
|
80
|
+
database password. `postgrest.roots` names the directories scanned for `.ts` and
|
|
81
|
+
`.tsx` files. The `perf` values above are the built-in defaults except `inGate`,
|
|
82
|
+
which `init` always writes as `false` — the gate measures nothing until a
|
|
83
|
+
project reaches phase 4.
|
|
53
84
|
|
|
54
85
|
## Findings
|
|
55
86
|
|
|
56
|
-
| Code | Source | Severity | What it means
|
|
57
|
-
| ------------------ | --------------------------- | ----------- |
|
|
58
|
-
| `BDB001` | permissive-policy | warn | a policy uses `using (true)` or `with check (true)`
|
|
59
|
-
| `BDB002` | rls-enabled-no-policy |
|
|
60
|
-
| `BDB003` | table-without-rls | warn | a `public` table never enables row level security
|
|
61
|
-
| `BDB004` | auth-uid-not-wrapped | warn | `auth.uid()` in a policy without `(select ...)`: evaluated per row
|
|
62
|
-
| `BDB005` | definer-without-search-path | warn | `SECURITY DEFINER` function without `set search_path`
|
|
63
|
-
| `BDB100/<rule>` | squawk | as squawk | migration lock and schema hazards, Supabase profile
|
|
64
|
-
| `BDB200/<rule>` | prisma-lint | warn | relation field without an index
|
|
65
|
-
| `BDB300/<rule>` | eslint-plugin-drizzle | error | `delete` or `update` without `.where()`
|
|
66
|
-
| `BDB401`-`BDB403` | sqlite3 | error/warn | integrity, dangling foreign keys, table without primary key
|
|
67
|
-
| `BDB500/<name>` | Supabase advisors | as Supabase | splinter security and performance lints on the live project
|
|
68
|
-
| `BDB601`, `BDB602` | Supabase inspect |
|
|
69
|
-
| `BDB700/<check>` | Soda Core | error/warn | a failed or warned data check from `<audit.soda>/checks.yml`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
| Code | Source | Severity | What it means |
|
|
88
|
+
| ------------------ | --------------------------- | ----------- | --------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `BDB001` | permissive-policy | warn | a policy uses `using (true)` or `with check (true)` |
|
|
90
|
+
| `BDB002` | rls-enabled-no-policy | warn | RLS on, no policy in any migration: service role only |
|
|
91
|
+
| `BDB003` | table-without-rls | warn | a `public` table never enables row level security |
|
|
92
|
+
| `BDB004` | auth-uid-not-wrapped | warn | `auth.uid()` in a policy without `(select ...)`: evaluated per row |
|
|
93
|
+
| `BDB005` | definer-without-search-path | warn | `SECURITY DEFINER` function without `set search_path` |
|
|
94
|
+
| `BDB100/<rule>` | squawk | as squawk | migration lock and schema hazards, Supabase profile |
|
|
95
|
+
| `BDB200/<rule>` | prisma-lint | warn | relation field without an index |
|
|
96
|
+
| `BDB300/<rule>` | eslint-plugin-drizzle | error | `delete` or `update` without `.where()` |
|
|
97
|
+
| `BDB401`-`BDB403` | sqlite3 | error/warn | integrity, dangling foreign keys, table without primary key |
|
|
98
|
+
| `BDB500/<name>` | Supabase advisors | as Supabase | splinter security and performance lints on the live project |
|
|
99
|
+
| `BDB601`, `BDB602` | Supabase inspect | warn | never-scanned index, table bloat over `audit.bloatThreshold` |
|
|
100
|
+
| `BDB700/<check>` | Soda Core | error/warn | a failed or warned data check from `<audit.soda>/checks.yml` |
|
|
101
|
+
| `BDB801`-`BDB805` | PostgREST rules | warn | query-chain rules on `.ts`/`.tsx` under `postgrest.roots`; see [PostgREST rules](#postgrest-rules) |
|
|
102
|
+
| `BDB901`-`BDB904` | perf diff | error/warn | live regressions, slow queries, sequential scans, temp spill; see [Performance](#performance) |
|
|
103
|
+
| `BDB911`-`BDB913` | perf bench | error/warn | bench query regressions, plan degradation, stale planner estimates; see [Performance](#performance) |
|
|
104
|
+
|
|
105
|
+
Disable a code for a project with an object naming the reason:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
"disable": [
|
|
109
|
+
{
|
|
110
|
+
"code": "BDB100/prefer-bigint-over-int",
|
|
111
|
+
"reason": "int keys are a deliberate choice, see ADR-0007"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
That is the `schemaVersion: 2` shape. A `schemaVersion: 1` file still accepts
|
|
117
|
+
the old bare string form (`"disable": ["BDB100/prefer-bigint-over-int"]`); see
|
|
118
|
+
[Strict mode](#strict-mode).
|
|
119
|
+
|
|
120
|
+
## Strict mode
|
|
121
|
+
|
|
122
|
+
`schemaVersion: 2` removes the `info` severity: every finding is now `error` or
|
|
123
|
+
`warn`, and every finding fails the gate unless the baseline already records it
|
|
124
|
+
or a `disable` entry names it with a written reason. Two codes changed severity
|
|
125
|
+
in 0.2.0, independent of `schemaVersion`: `BDB002` (RLS enabled, no policy) and
|
|
126
|
+
`BDB601` (an index that has never been scanned) moved from `info` to `warn`.
|
|
127
|
+
Their fingerprints did not change, so a `BDB002` already carried in a baseline
|
|
128
|
+
stays known.
|
|
129
|
+
|
|
130
|
+
A `schemaVersion: 1` file keeps working exactly as before: its `disable` entries
|
|
131
|
+
stay bare strings, and every command prints one line to stderr saying that
|
|
132
|
+
`codeality-db init --apply` will upgrade the file to `schemaVersion: 2` and give
|
|
133
|
+
each disabled rule a written reason. Under `schemaVersion: 2` a bare string
|
|
134
|
+
`disable` entry is a configuration error whose message shows the object form
|
|
135
|
+
instead. The strictness is opted into by bumping the schema version yourself;
|
|
136
|
+
installing 0.2.0 alone changes nothing for a `schemaVersion: 1` project.
|
|
137
|
+
|
|
138
|
+
## PostgREST rules
|
|
139
|
+
|
|
140
|
+
With a `postgrest.roots` section, `check` and `gate` walk every `.ts` and `.tsx`
|
|
141
|
+
file under those directories with the TypeScript compiler API and collect the
|
|
142
|
+
call chains rooted at `.from('<table>')` or `.rpc('<fn>')`:
|
|
143
|
+
|
|
144
|
+
| Code | Rule | What it proves |
|
|
145
|
+
| -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
146
|
+
| `BDB801` | select-star | `.select('*')`, `.select()`, or a column list that contains `*` |
|
|
147
|
+
| `BDB802` | unbounded-list | a read with no `limit`, `range`, `single`, `maybeSingle`, `csv`, `head: true`, and no equality on a primary or unique key |
|
|
148
|
+
| `BDB803` | filter-without-index | a filter on a literal column that no index, primary key or unique constraint in the migrations covers as its leading column |
|
|
149
|
+
| `BDB804` | query-in-loop | a `.from(` or `.rpc(` chain awaited inside `for`, `for of`, `while`, or a `map`/`forEach`/`reduce`/`filter`/`flatMap` callback |
|
|
150
|
+
| `BDB805` | exact-count-unbounded | `{ count: 'exact' }` with no `limit`, `range` or `head: true` |
|
|
151
|
+
|
|
152
|
+
Table knowledge for `BDB802` and `BDB803` comes from the same migration set the
|
|
153
|
+
`BDB00x` rules already read: the leading column of every
|
|
154
|
+
`create [unique] index`, `primary key` and `unique` constraint. A chain on a
|
|
155
|
+
table the migrations never created — a view, a table in another schema — is
|
|
156
|
+
exempt from `BDB803` rather than reported as a false positive, and a chain whose
|
|
157
|
+
table or column is not a string literal is exempt from every rule: each rule
|
|
158
|
+
reports only what it can prove. `.select('*')` inside `.rpc()` is not `BDB801`:
|
|
159
|
+
a function returns what it returns. A chain rooted at `<expr>.storage.from(...)`
|
|
160
|
+
(e.g. `supabase.storage.from('bucket')`) is a Supabase Storage call, not
|
|
161
|
+
PostgREST, and is skipped by every rule; a bucket reached through an aliased
|
|
162
|
+
variable is not recognized.
|
|
163
|
+
|
|
164
|
+
What these rules cannot see: a table or column name built from a variable
|
|
165
|
+
instead of a string literal, a filter reached through `.match()` rather than a
|
|
166
|
+
named method like `.eq()`, and anything about a view or a table the migrations
|
|
167
|
+
do not define — the index knowledge `BDB802` and `BDB803` use comes only from
|
|
168
|
+
`create table`, `create index` and constraint statements.
|
|
169
|
+
|
|
170
|
+
## Performance
|
|
171
|
+
|
|
172
|
+
Three commands measure a live Supabase project, and the gate's `perf` stage runs
|
|
173
|
+
the last two of them when `perf.inGate` is `true`:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
codeality-db perf snapshot # record pg_stat_statements and table stats
|
|
177
|
+
codeality-db perf diff # compare a fresh reading against the snapshot
|
|
178
|
+
codeality-db perf bench [--record] # EXPLAIN ANALYZE the project's own bench queries
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`perf snapshot` writes `.codeality-db-perf.json`: per-statement counters from
|
|
182
|
+
`pg_stat_statements`, for the roles named in `perf.roles` (default
|
|
183
|
+
`authenticator`, `service_role`, `postgres`), and per-table
|
|
184
|
+
`pg_stat_user_tables` counters, alongside the target's host name and the tool's
|
|
185
|
+
own version — never a password or a connection string. `pg_stat_statements`
|
|
186
|
+
counters are cumulative, and the `postgres` role cannot reset them on Supabase,
|
|
187
|
+
so `perf diff` reads that file, takes a fresh reading, and for every matched
|
|
188
|
+
statement computes the window since the snapshot: the delta in calls, total time
|
|
189
|
+
and temp blocks written, turned back into a window mean. A statement whose
|
|
190
|
+
counters fell below the snapshot, or whose stats-reset timestamp moved, had its
|
|
191
|
+
server-side counters reset and is windowed from its current absolute values
|
|
192
|
+
instead, the same as a statement that is new since the snapshot.
|
|
193
|
+
|
|
194
|
+
Before the findings, `perf diff` prints an **improvement report**: every
|
|
195
|
+
statement whose window mean fell by `perf.regressionPercent` or more against its
|
|
196
|
+
snapshot mean, with both means, the call count, and the total time saved — the
|
|
197
|
+
answer to "did the change help", and never itself a finding. `--json` returns
|
|
198
|
+
`{ improvements, findings }`. Statements that are platform noise, not the
|
|
199
|
+
application, are excluded before any of this: `pg_sleep`, `pg_timezone_names`,
|
|
200
|
+
`pg_stat_statements` itself, PostgREST's schema-cache CTEs, the WAL replication
|
|
201
|
+
poll, `COPY` statements, and any statement containing one of the plain
|
|
202
|
+
substrings (not patterns) listed in `perf.ignore`, which extends that built-in
|
|
203
|
+
list rather than replacing it.
|
|
204
|
+
|
|
205
|
+
`perf bench` runs each `.sql` file in `perf.benchDir` (default
|
|
206
|
+
`db-quality/bench`) as `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)`: one warm-up
|
|
207
|
+
run, then `perf.benchRuns` runs — or the count from a leading `-- runs: N`
|
|
208
|
+
comment in the file — taking the median execution time. `perf bench --record`
|
|
209
|
+
writes `.codeality-db-bench.json`; `perf bench` without the flag compares the
|
|
210
|
+
current run against that record. A `.sql` file with no recorded entry yet is
|
|
211
|
+
still judged for `BDB913` (the estimate check needs only the current run);
|
|
212
|
+
`BDB911` and `BDB912` need a recorded entry to compare against, so they never
|
|
213
|
+
fire for a file that has none. A statement that fails to run is an
|
|
214
|
+
infrastructure error (exit 3) naming the file.
|
|
215
|
+
|
|
216
|
+
A bench file is one statement; a file holding more than one is refused as a
|
|
217
|
+
configuration error. The server, not that check, is the boundary: the statement
|
|
218
|
+
reaches Postgres as a dollar-quoted literal opened as a PL/pgSQL cursor inside a
|
|
219
|
+
`DO` block, in a session that is read-only before it starts, and the plan comes
|
|
220
|
+
back through a session setting. A cursor over more than one statement is
|
|
221
|
+
refused, so nothing after the `EXPLAIN` ever runs; a write fails in the
|
|
222
|
+
read-only transaction; and the cursor runs in an inner block that is always
|
|
223
|
+
rolled back, which undoes what a read-only transaction does not stop:
|
|
224
|
+
`EXPLAIN ANALYZE` of `CREATE TABLE AS`, `SELECT INTO` or
|
|
225
|
+
`CREATE MATERIALIZED VIEW` writes even there.
|
|
226
|
+
|
|
227
|
+
Some side effects are outside any transaction and no read-only session stops
|
|
228
|
+
them: calls that leave the database through `dblink` or `pg_net`; for a role
|
|
229
|
+
with `REPLICATION`, a replication slot, which persists and retains WAL until it
|
|
230
|
+
is dropped; and, for a superuser, `COPY ... TO PROGRAM`, `lo_export`,
|
|
231
|
+
`pg_terminate_backend` and `pg_stat_statements_reset`. The strongest boundary is
|
|
232
|
+
a dedicated role that is not a superuser, has no `REPLICATION`, and can only
|
|
233
|
+
read; point `--db-url` at it for `perf`.
|
|
234
|
+
|
|
235
|
+
| Code | Layer | Rule | Severity |
|
|
236
|
+
| -------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
237
|
+
| `BDB901` | live | query-regressed: mean time grew by `perf.regressionPercent` or more and by at least 5 ms, at least `perf.minCalls` calls, at least 5 ms mean | error |
|
|
238
|
+
| `BDB902` | live | slow-query: mean time at or above `perf.slowMs`, at least `perf.minCalls` calls | warn |
|
|
239
|
+
| `BDB903` | live | seq-scan-table: a table with at least `perf.seqScanRows` live rows and more sequential than index scans in the window | warn |
|
|
240
|
+
| `BDB904` | live | temp-spill: a statement wrote temp blocks in the window, at least `perf.minCalls` calls: a sort or hash spilled to disk | warn |
|
|
241
|
+
| `BDB911` | bench | bench-regressed: median grew by `perf.regressionPercent` or more and by at least 5 ms | error |
|
|
242
|
+
| `BDB912` | bench | bench-plan-degraded: a new sequential scan on a table with at least `perf.seqScanRows` rows, or an index scan that became one | error |
|
|
243
|
+
| `BDB913` | bench | bench-estimate-off: the planner's row estimate is off by a factor of 100 or more on a plan node with at least 1000 estimated or actual rows | warn |
|
|
244
|
+
|
|
245
|
+
Live and bench findings are never carried in `.codeality-db-baseline.json`: they
|
|
246
|
+
are measurements, and a measurement nobody likes is fixed at the source, not
|
|
247
|
+
recorded away. `disable` with a written reason is the escape hatch for a live or
|
|
248
|
+
bench rule that genuinely does not apply to a project.
|
|
249
|
+
|
|
250
|
+
### Connection
|
|
251
|
+
|
|
252
|
+
The live commands connect the same way the Supabase CLI itself does:
|
|
253
|
+
`--db-url <url>` wins; otherwise the linked project's pooler url in
|
|
254
|
+
`supabase/.temp/pooler-url` (written by `supabase link`) plus the password in
|
|
255
|
+
`SUPABASE_DB_PASSWORD` — the same variable the Supabase CLI reads. `gate` never
|
|
256
|
+
takes `--db-url`; its `perf` stage resolves only from the linked project, and
|
|
257
|
+
reports why it skipped when no password is available. Neither
|
|
258
|
+
`perf snapshot`/`diff`/`bench` nor the gate's `perf` stage runs without `psql`
|
|
259
|
+
on `PATH`: it is a required tool, and a missing one is an infrastructure failure
|
|
260
|
+
(exit 3), the same as a missing Supabase CLI.
|
|
261
|
+
|
|
262
|
+
Every session opens with `set default_transaction_read_only = on` and
|
|
263
|
+
`set statement_timeout = <ms>` as separate arguments before the query, sent
|
|
264
|
+
again before every call: a Supabase session pooler was measured, on 2026-09-25,
|
|
265
|
+
to ignore `PGOPTIONS` for this, so `PGOPTIONS` is never used. A transaction
|
|
266
|
+
pooler (port 6543, or `pgbouncer=true` in the url) is refused as a configuration
|
|
267
|
+
error: it can run each statement on a different backend, so the read-only
|
|
268
|
+
setting would not hold, and it would stay behind on a connection the application
|
|
269
|
+
shares. Use the session pooler (port 5432) or a direct connection.
|
|
270
|
+
|
|
271
|
+
The password reaches `psql` through `PGPASSWORD` only: a password in `--db-url`
|
|
272
|
+
is taken out of the url before `psql` sees its arguments, and `PGPASSWORD` is
|
|
273
|
+
left as it is when no password is given. A password `psql` echoes back in a
|
|
274
|
+
connection error is redacted to `***` before it reaches a finding or the
|
|
275
|
+
terminal.
|
|
276
|
+
|
|
277
|
+
## Adoption in phases
|
|
278
|
+
|
|
279
|
+
No step below turns a green gate red by itself; a family of findings exists only
|
|
280
|
+
once its section is in the configuration file:
|
|
281
|
+
|
|
282
|
+
| phase | what the adopter does | what changes in the gate |
|
|
283
|
+
| ----- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
284
|
+
| 0 | `pnpm add -D @syntopica/db-quality@0.2` | Nothing. `schemaVersion: 1` is read as before, `postgrest` and `perf` are absent, so no new finding exists. |
|
|
285
|
+
| 1 | `init --apply` (rewrites `disable`, adds `postgrest.roots` and `perf` with `inGate: false`), then `baseline update` | `check` gains `BDB8xx`; the baseline absorbs the existing ones, so the gate stays green and only new debt fails. |
|
|
286
|
+
| 2 | `perf snapshot` after a deploy, `perf diff` after the next one | Nothing yet: `perf.inGate` is false. The improvement report and the `BDB9xx` findings are read by a person. |
|
|
287
|
+
| 3 | Write bench queries, `perf bench --record` | Nothing yet. The bench file is the reference. |
|
|
288
|
+
| 4 | Set `perf.inGate: true` | The `perf` stage runs `diff` and `bench` in the gate; regressions fail it. Locally and in any CI that holds `SUPABASE_DB_PASSWORD`; elsewhere the stage is `skipped-not-applicable`. |
|
|
289
|
+
|
|
290
|
+
`codeality-db init` prints this table with the phase it detects and the next
|
|
291
|
+
step every time it runs, so the next move is never a guess; `--apply` prints the
|
|
292
|
+
same report after writing the plan.
|
|
73
293
|
|
|
74
294
|
## Baseline
|
|
75
295
|
|
|
@@ -79,7 +299,9 @@ fails only on findings the baseline does not carry. Fingerprints hash the code,
|
|
|
79
299
|
the path and the normalised statement, never the line number, so inserting a
|
|
80
300
|
migration above a known finding does not renew it.
|
|
81
301
|
`baseline check --check-stale` also fails on entries nothing reports any more,
|
|
82
|
-
so dead debt is not carried forever.
|
|
302
|
+
so dead debt is not carried forever. The baseline covers `check` findings only:
|
|
303
|
+
`perf`'s live and bench findings are never recorded there — see
|
|
304
|
+
[Performance](#performance).
|
|
83
305
|
|
|
84
306
|
## Squawk under Supabase
|
|
85
307
|
|
package/assets/db-quality.yml
CHANGED