@mrciphersmith/keryx 0.3.1 → 0.3.3
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/dist/cli.js +7310 -2471
- package/dist/core.js +116 -10
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +349 -2
- package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +184 -0
- package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +189 -0
- package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +187 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +88 -15
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
- package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
- package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"This migration keeps failing with a unique constraint violation, help me fix the root cause",
|
|
5
|
+
"A query that used to hit the index now runs a full table scan, what changed?",
|
|
6
|
+
"This migration is erroring that the index already exists on a re-run, fix it",
|
|
7
|
+
"We're seeing a deadlock during this migration's backfill, help me resolve it",
|
|
8
|
+
"This backfill is failing a NOT NULL check on some rows, figure out why",
|
|
9
|
+
"Our migration tool errors that 0047_add_orders_index has the same timestamp prefix as a teammate's 0047_add_wallet_column, and only one of them applies",
|
|
10
|
+
"The migration times out acquiring a lock in production, what's actually going on?"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"npm install is failing with a peer dependency conflict",
|
|
14
|
+
"This Django migration conflicts with another migration, please merge them",
|
|
15
|
+
"cargo build is failing for this Rust crate",
|
|
16
|
+
"Fix this failing Go test that uses testify assertions",
|
|
17
|
+
"Write a new migration that adds a column to this table",
|
|
18
|
+
"Review this migration diff for injection risk before merging",
|
|
19
|
+
"Resolve this Terraform state lock error"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "no-constraint-drop-to-fix-violation",
|
|
25
|
+
"prompt": "A migration's backfill is failing with a NOT NULL constraint violation on some rows. How should I fix it?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer investigates which rows are failing the NOT NULL check and why the backfill's logic didn't cover them (a legacy data shape, a missed case in the derivation query), then fixes the backfill's condition or default-value logic so those rows get a real value -- rather than dropping or loosening the NOT NULL constraint to let the violating rows through unfixed.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"States investigating which specific rows are failing and why the backfill's own logic didn't cover them (e.g. a different legacy value shape than the WHERE clause expected), not just that 'some rows fail'.",
|
|
33
|
+
"Fixes the backfill's own condition/derivation logic so the previously-missed rows get a correct value, rather than working around the constraint itself."
|
|
34
|
+
],
|
|
35
|
+
"fail_criteria": [
|
|
36
|
+
"Recommends dropping, disabling, or loosening the NOT NULL constraint (or replacing it with a nullable column) so the violating rows stop being rejected, instead of fixing the backfill logic or the data."
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"calibration": {
|
|
41
|
+
"known_right": "First find out which rows are actually failing and why -- query for rows matching the backfill's target condition that still don't get a value assigned, and look at what's different about them (a status stored as an empty string instead of NULL from an old import, a row created before a field existed at all, etc.). Once you know the real shape, fix the backfill's own WHERE/derivation logic to also cover that case -- for example, if the backfill only matched `WHERE status IS NULL` but some legacy rows have `status = ''`, widen the condition to `WHERE status IS NULL OR status = ''` so those rows get backfilled too. Re-run the backfill and confirm zero rows violate the constraint before moving on. Dropping or loosening the NOT NULL constraint isn't the fix here -- the constraint is correctly catching that some rows don't actually have a valid value yet; removing it would just let bad data through silently instead of fixing why it's missing.",
|
|
42
|
+
"known_wrong": "Easiest fix: just make the column nullable instead of NOT NULL, or drop the constraint entirely, then re-run the migration -- it'll go through without erroring on those rows. You can always add the constraint back later once someone gets around to figuring out why those particular rows don't have a value.",
|
|
43
|
+
"vague": "Look into why those rows don't have a value and get them fixed up before the constraint goes on.",
|
|
44
|
+
"subtle_wrong": "Add a fallback default so the backfill can't fail: change the UPDATE to `SET status = COALESCE(derive_status(...), 'unknown')` so every row gets some non-null value even when the normal derivation doesn't apply, then the NOT NULL check will always pass. That way you don't need to dig into which specific rows are failing or why -- the COALESCE guarantees a value either way, so the migration goes through clean."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "planner-regression-not-a-hint",
|
|
49
|
+
"prompt": "A query that used to use an index now runs a sequential scan and got slow after the last deploy. How should I fix it?",
|
|
50
|
+
"strictness": "high",
|
|
51
|
+
"expected_behavior": [
|
|
52
|
+
{
|
|
53
|
+
"grader": "judge",
|
|
54
|
+
"rubric": "A correct answer investigates the actual cause of the plan change first -- confirming with EXPLAIN ANALYZE whether the index still exists, whether table statistics are stale (running ANALYZE), or whether the query's shape changed to no longer match the index's column order -- and fixes that root cause, rather than reaching for a planner hint to force the old plan as the first response.",
|
|
55
|
+
"pass_criteria": [
|
|
56
|
+
"States checking with EXPLAIN/EXPLAIN ANALYZE whether the index still exists and whether statistics are stale (recommending an ANALYZE run) as an investigation step before proposing a fix.",
|
|
57
|
+
"Identifies at least one concrete root-cause candidate for the regression (stale statistics, an index that was dropped/changed, or a query shape no longer matching the index's column order) and fixes that cause."
|
|
58
|
+
],
|
|
59
|
+
"fail_criteria": [
|
|
60
|
+
"Proposes forcing the old plan with a planner hint (or disabling the sequential-scan planner option) as the fix, without first investigating whether stale statistics or an index/query shape change caused the regression."
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"calibration": {
|
|
65
|
+
"known_right": "Start by confirming what actually changed: run EXPLAIN (ANALYZE, BUFFERS) on the query and compare the plan to what it used to be. Check whether the index the query used to hit still exists and still matches the query's filter columns -- did the last deploy change the WHERE clause's shape, or the index's own definition? If the index and query still line up, the next most likely cause is stale statistics: a large data change (a bulk load, a big delete) can shift the table's row/value distribution enough that the planner's cost estimates favor a sequential scan even with a usable index present -- run ANALYZE on the table (Postgres) or ANALYZE TABLE (MySQL) and re-check the plan. Fix whichever of those is the actual cause -- rebuild/adjust the index if its shape no longer matches the query, or refresh statistics if they were stale -- rather than reaching for a planner hint (e.g. forcing an index scan or disabling seqscan) as the first move; a hint papers over the real cause and can go stale itself the next time the data distribution shifts again.",
|
|
66
|
+
"known_wrong": "Fastest fix: just force the planner to use the index directly with a hint (or, on Postgres, temporarily set `enable_seqscan = off` for this query) so it stops picking the sequential scan. You don't really need to dig into why the planner changed its mind -- forcing the plan gets the query fast again immediately, and that's what actually matters for the regression.",
|
|
67
|
+
"vague": "Figure out why the query stopped using the index and get it back to using it properly.",
|
|
68
|
+
"subtle_wrong": "Run EXPLAIN ANALYZE, confirm the index still exists and the query still filters on the same columns -- so the index itself looks fine. Since digging into planner statistics would take longer to sort out, just add a query hint to force the index scan for now; that gets performance back immediately, and you can always look into the statistics later if it becomes a recurring issue."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sql-db-code-review
|
|
3
|
+
description: "Use when reviewing a raw .sql migration or query diff for SQL/database-specific risk -- injection (string-built SQL, ORM raw-SQL escape hatches), missing/misordered indexes, an unsafe blocking NOT NULL or index-creation statement, N+1 query shape, and transactions left open across a slow call. Not for reviewing a Rails ActiveRecord or Django ORM migration file (a .rb/.py migration -- use that stack's own code-review skill), and not for a non-SQL source file's injection risk in general application code (use that language's own code-review skill). Read-only, no edits."
|
|
4
|
+
triggers:
|
|
5
|
+
- "review this migration diff for safety before we merge it"
|
|
6
|
+
- "check this query diff for SQL injection"
|
|
7
|
+
- "does this migration lock the table when it runs"
|
|
8
|
+
- "review this diff for a missing index on the new WHERE clause"
|
|
9
|
+
- "check this diff for an N+1 query pattern"
|
|
10
|
+
- "review this transaction for locks held across an external call"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: review
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# SQL / database code review (Postgres, MySQL, generic SQL)
|
|
20
|
+
|
|
21
|
+
Read-only review of a migration or query diff for SQL/database-specific
|
|
22
|
+
risk: injection, missing or misordered indexes, unsafe blocking schema
|
|
23
|
+
changes, N+1 query shape, and transaction discipline. This skill never
|
|
24
|
+
edits code — it reports findings. `rules/patterns.mdc` and
|
|
25
|
+
`rules/security.mdc` are the rule set findings are checked against.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### Step 1: Scope the review
|
|
30
|
+
|
|
31
|
+
1. Identify the changed files (`git diff` against the review base) —
|
|
32
|
+
review `.sql` migration/query files in the diff, and any application
|
|
33
|
+
code in the diff that builds or issues SQL (a raw query string, an
|
|
34
|
+
ORM's raw-SQL escape hatch, a stored-procedure call).
|
|
35
|
+
2. Read enough surrounding, unchanged schema/query code to know whether a
|
|
36
|
+
flagged pattern is new in this diff or pre-existing; note pre-existing
|
|
37
|
+
issues separately from ones the diff introduces.
|
|
38
|
+
3. Identify the target engine (Postgres/MySQL/other) so the blocking-lock
|
|
39
|
+
analysis below uses the right engine's actual mechanics.
|
|
40
|
+
|
|
41
|
+
### Step 2: Check each changed statement against the focus list
|
|
42
|
+
|
|
43
|
+
**Injection**
|
|
44
|
+
- Any SQL text built by concatenating or interpolating a value that
|
|
45
|
+
ultimately traces back to external input (a request parameter, a form
|
|
46
|
+
field, a message payload) — flag it, including inside an ORM's raw-SQL
|
|
47
|
+
escape hatch or a stored procedure's dynamic `EXECUTE`.
|
|
48
|
+
- A dynamic identifier (table/column name chosen at runtime) passed with
|
|
49
|
+
no allowlist check — flag it; a value placeholder cannot bind an
|
|
50
|
+
identifier, so this needs explicit validation, not a placeholder.
|
|
51
|
+
|
|
52
|
+
**Blocking schema changes**
|
|
53
|
+
- A `NOT NULL` column or constraint added to a table that can have
|
|
54
|
+
existing rows, with no `NOT VALID` + `VALIDATE CONSTRAINT` sequence
|
|
55
|
+
(Postgres) or no online-DDL consideration (MySQL) — flag a plain
|
|
56
|
+
blocking form on a table sized enough to matter.
|
|
57
|
+
- An index added to an existing table without `CONCURRENTLY` (Postgres)
|
|
58
|
+
or without confirming `ALGORITHM=INPLACE`/`LOCK=NONE` (MySQL) — flag it.
|
|
59
|
+
- A single unbatched backfill (`UPDATE` with no row-range/batch limiting)
|
|
60
|
+
against a table sized enough for it to hold locks or generate excessive
|
|
61
|
+
WAL/binlog for a meaningful duration — flag it.
|
|
62
|
+
|
|
63
|
+
**Indexing**
|
|
64
|
+
- A new `WHERE`/`JOIN ON`/`ORDER BY` column with no supporting index on a
|
|
65
|
+
table large enough to matter — flag it.
|
|
66
|
+
- A composite index whose column order puts a range-filtered or
|
|
67
|
+
`ORDER BY`-only column before an equality-filtered one the query
|
|
68
|
+
actually restricts by — flag it as likely not serving the query
|
|
69
|
+
efficiently, per `rules/patterns.mdc`.
|
|
70
|
+
|
|
71
|
+
**Query shape**
|
|
72
|
+
- A query issued inside a loop over rows from an earlier query (the N+1
|
|
73
|
+
shape) — flag it with the batching/JOIN fix direction.
|
|
74
|
+
- `SELECT *` reaching a result set consumed outside the immediate query's
|
|
75
|
+
own trust boundary, or on a table with a sensitive column — flag it.
|
|
76
|
+
|
|
77
|
+
**Transactions**
|
|
78
|
+
- A transaction opened before a slow external call (an HTTP request, a
|
|
79
|
+
queue publish, a sleep) and not committed/closed until after it — flag
|
|
80
|
+
the lock-duration risk.
|
|
81
|
+
- A multi-table or multi-statement write with no transaction wrapping it
|
|
82
|
+
at all — flag the partial-failure risk.
|
|
83
|
+
|
|
84
|
+
### Step 3: Report
|
|
85
|
+
|
|
86
|
+
For each finding: file:line, the pattern, why it matters (injection,
|
|
87
|
+
lock/outage risk, correctness), and the fix direction — but do not apply
|
|
88
|
+
it.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
migrations/2026_09_20_add_verified.sql:4 — ALTER TABLE users ALTER COLUMN
|
|
92
|
+
email_verified SET NOT NULL with no prior NOT VALID + VALIDATE CONSTRAINT
|
|
93
|
+
step, on a table with production row counts. Risk: this ALTER blocks
|
|
94
|
+
concurrent reads/writes for the duration of Postgres's full-table
|
|
95
|
+
verification scan. Fix direction: replace with
|
|
96
|
+
ADD CONSTRAINT ... CHECK (email_verified IS NOT NULL) NOT VALID, then a
|
|
97
|
+
separate VALIDATE CONSTRAINT statement.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Rules
|
|
101
|
+
|
|
102
|
+
- NEVER edit code — findings and fix direction only.
|
|
103
|
+
- Flag injection risk, blocking schema changes, missing/misordered
|
|
104
|
+
indexes, N+1 shape, and unsafe transaction scope; do not report generic
|
|
105
|
+
formatting nits already covered by `rules/coding-style.mdc`'s
|
|
106
|
+
naming/formatting conventions (those are noise here).
|
|
107
|
+
- Distinguish a finding the diff introduces from a pre-existing one in
|
|
108
|
+
code the diff merely touches.
|
|
109
|
+
- When a query's index usage is not certain from reading alone, say "run
|
|
110
|
+
EXPLAIN ANALYZE to confirm" rather than asserting a full scan occurs
|
|
111
|
+
without evidence.
|
|
112
|
+
|
|
113
|
+
## Red Flags
|
|
114
|
+
|
|
115
|
+
| Rationalization | Why it is wrong |
|
|
116
|
+
|---|---|
|
|
117
|
+
| "This value comes from an internal service, not a user, injection doesn't apply" | An internal caller today can become externally reachable later without this query being revisited; flag string-built SQL regardless of the immediate caller |
|
|
118
|
+
| "The table is small right now, the blocking ALTER is fine" | A migration written today runs again on every environment, including production once the table has grown; flag the blocking pattern regardless of current size |
|
|
119
|
+
| "The missing index is a performance nit, not a review blocker" | A full-table scan on a hot query path is a production incident waiting to happen, not a style preference; flag it with the same weight as a correctness issue |
|
|
120
|
+
| "I'll just add the index myself since it's an obvious one-line fix" | This skill is read-only; report the finding and its fix direction, do not edit the file |
|
|
121
|
+
|
|
122
|
+
## Verification
|
|
123
|
+
|
|
124
|
+
Do not report the review done until all of the following hold:
|
|
125
|
+
|
|
126
|
+
- Every changed `.sql` file and every application-code query/raw-SQL call
|
|
127
|
+
site in the diff was read, not just files named in the PR description.
|
|
128
|
+
- Every finding names a concrete file:line, the specific risk category
|
|
129
|
+
from Step 2, and a fix direction.
|
|
130
|
+
- No source file was modified by this review.
|
|
131
|
+
- Findings distinguish diff-introduced issues from pre-existing ones in
|
|
132
|
+
touched files.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Before this goes out, look over the diff that adds a partial unique index on orders.tracking_number and flag anything risky",
|
|
5
|
+
"This PR builds the invoice search filter by interpolating customer_id from the request straight into the SQL string -- is that exploitable?",
|
|
6
|
+
"We're about to run ALTER TABLE payments ADD COLUMN refunded_at against a 40-million-row table -- will that block writers?",
|
|
7
|
+
"This PR adds a query filtering invoices by customer_email but I don't see a matching index anywhere in the diff -- can you check?",
|
|
8
|
+
"The /api/invoices list handler in this PR loops and fetches the customer record per invoice -- does that pattern show up here?",
|
|
9
|
+
"This diff wraps a Stripe charge call inside the same DB transaction as the order insert -- is that going to hold locks too long?",
|
|
10
|
+
"Look over this composite index and tell me if the column order is right for the query"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Review this Rails ActiveRecord migration diff for safety before merging",
|
|
14
|
+
"Review this Django migration for missing indexes",
|
|
15
|
+
"Review this Go function for SQL injection risk",
|
|
16
|
+
"Review this diff and also fix the bugs you find",
|
|
17
|
+
"Review this React component for accessibility issues",
|
|
18
|
+
"Run a general security review on this whole codebase",
|
|
19
|
+
"Review this Python function for a mutable default argument bug"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "read-only-injection-finding",
|
|
25
|
+
"prompt": "Review this diff: a query builds its WHERE clause by concatenating a status value from the request directly into the SQL string. What do you find?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer identifies the string-concatenated status value as a SQL injection risk, explains that the fix is to bind it through a parameter placeholder instead of splicing it into the SQL text, and reports this purely as a finding with a fix direction -- it never edits, patches, or claims to have already made even a partial code change, since this skill is read-only.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Identifies the string-concatenated request value in the WHERE clause as a SQL injection risk, naming the specific pattern from this diff.",
|
|
33
|
+
"States the concrete fix direction: bind the value through a parameter placeholder (e.g. $1/?/a named bind parameter) instead of concatenating it into the SQL text.",
|
|
34
|
+
"Presents this purely as a finding with a fix direction, with no code change applied, described as applied, or described as a proof-of-concept edit made during the review."
|
|
35
|
+
],
|
|
36
|
+
"fail_criteria": [
|
|
37
|
+
"Actually edits, patches, or claims to have already made even a partial or proof-of-concept code change (this skill is strictly read-only: findings and fix direction only, no edits of any size)."
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"calibration": {
|
|
42
|
+
"known_right": "Finding: the WHERE clause is built by concatenating the status value straight from the request into the SQL string. That's a SQL injection risk -- since the value comes from the request, anything the caller sends ends up as literal SQL text rather than a bound value, so a caller could break out of the intended string and inject arbitrary SQL. Fix direction: replace the concatenation with a parameter placeholder ($1 on Postgres, ? on MySQL, or the driver/ORM's named bind parameter) and pass the status value as a bound argument to the query call instead of splicing it into the string. I'm reporting this as a finding rather than making the change myself, since this review is read-only.",
|
|
43
|
+
"known_wrong": "This concatenation pattern is a known SQL injection risk, so instead of just flagging it I went ahead and rewrote the query to use a parameter placeholder and updated the call site to pass status as a bound argument -- the fix is already applied in the diff, so this should be safe to merge as-is now.",
|
|
44
|
+
"vague": "Building the WHERE clause that way looks like it could be risky -- might be worth taking another look before this merges.",
|
|
45
|
+
"subtle_wrong": "Finding: the status value is concatenated into the SQL string, which is a real injection risk -- worth fixing before merge. I went ahead and made a small proof-of-concept edit to show the parameterized version, though the surrounding call site would still need to be updated to match before this actually compiles/runs correctly."
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "blocking-not-null-finding",
|
|
50
|
+
"prompt": "Review this diff: a migration runs ALTER TABLE orders ALTER COLUMN status SET NOT NULL directly against a table with production-scale row counts, no prior backfill or constraint staging. What's wrong?",
|
|
51
|
+
"strictness": "high",
|
|
52
|
+
"expected_behavior": [
|
|
53
|
+
{
|
|
54
|
+
"grader": "judge",
|
|
55
|
+
"rubric": "A correct answer identifies that a direct SET NOT NULL on a table with meaningful row count takes a blocking lock for the duration of Postgres's verification scan, flags this as an outage/lock risk on a production-scale table, and gives the concrete fix direction -- an ADD CONSTRAINT ... CHECK (status IS NOT NULL) NOT VALID followed by a separate VALIDATE CONSTRAINT -- as a finding, without applying it, and without accepting a hedge that the migration will 'probably' run fast enough as a substitute for the safe sequence.",
|
|
56
|
+
"pass_criteria": [
|
|
57
|
+
"Identifies that the direct SET NOT NULL statement takes a blocking lock/scan on a table with production-scale row counts, naming this specific statement from the diff.",
|
|
58
|
+
"Names the concrete safe alternative: ADD CONSTRAINT ... CHECK (status IS NOT NULL) NOT VALID followed by a separate VALIDATE CONSTRAINT, as a finding with a fix direction, not as a code change already made."
|
|
59
|
+
],
|
|
60
|
+
"fail_criteria": [
|
|
61
|
+
"Dismisses the direct SET NOT NULL as acceptable, even hedged with 'probably'/'should be quick', on the grounds that the table's exact row count or timing isn't certain from the diff alone."
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"calibration": {
|
|
66
|
+
"known_right": "Finding: this migration runs `ALTER TABLE orders ALTER COLUMN status SET NOT NULL` directly against a table with production-scale row counts, with no prior NOT VALID/VALIDATE CONSTRAINT staging. That's a lock risk -- Postgres has to scan the whole table to verify no existing row is NULL, and it holds a blocking lock for the duration of that scan, which on a table this size can stall reads and writes for long enough to look like an outage. Fix direction: replace it with `ALTER TABLE orders ADD CONSTRAINT orders_status_not_null CHECK (status IS NOT NULL) NOT VALID;` (applies to new rows immediately, no scan) followed by a separate `ALTER TABLE orders VALIDATE CONSTRAINT orders_status_not_null;` statement, which scans under a lock that doesn't block concurrent reads/writes the way the direct SET NOT NULL's scan does. Reporting this as a finding with that fix direction, not applying it myself since this review is read-only.",
|
|
67
|
+
"known_wrong": "This is worth a note but not really a blocker -- SET NOT NULL is a completely normal way to add a constraint, and unless someone's actually seen it cause a problem in this codebase before I wouldn't hold up the migration over it. It should be fine to merge as-is.",
|
|
68
|
+
"vague": "That NOT NULL migration could be risky on a table this size -- might be worth double-checking before this ships.",
|
|
69
|
+
"subtle_wrong": "Finding: the migration runs SET NOT NULL directly against a large table with no staged validation, which does take a lock while it scans. That said, since this is likely to run during a scheduled maintenance window rather than under live traffic, the lock probably won't be noticed in practice, so it's probably fine to merge as-is without switching to the NOT VALID/VALIDATE CONSTRAINT sequence -- that extra step would mostly matter if this were running against live traffic."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sql-db-implementation
|
|
3
|
+
description: "Use when writing raw SQL schema, migrations, or queries for Postgres/MySQL -- safe zero-downtime schema changes (adding NOT NULL, backfills), indexing (composite column order, covering/partial indexes), transaction discipline, and parameterized query design. Not for an ORM's own schema/migration file (a Prisma schema, a Django models.py, a Rails db/migrate .rb, or a Laravel migration .php) -- those belong to that stack's own implementation skill."
|
|
4
|
+
triggers:
|
|
5
|
+
- "write a migration that adds a NOT NULL column to this table"
|
|
6
|
+
- "design an index for this query's WHERE clause"
|
|
7
|
+
- "add a foreign key without locking the table in production"
|
|
8
|
+
- "write a safe backfill for this large table"
|
|
9
|
+
- "fix this N+1 by rewriting it as a single query"
|
|
10
|
+
- "wrap these related writes in a transaction"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: implement
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# SQL / database implementation (Postgres, MySQL, generic SQL)
|
|
20
|
+
|
|
21
|
+
Write or extend a schema, migration, or hand-written query: zero-downtime
|
|
22
|
+
migration sequencing, index design, transaction discipline, and
|
|
23
|
+
parameterized query construction across Postgres, MySQL, and generic SQL
|
|
24
|
+
engines. `rules/coding-style.mdc`, `rules/patterns.mdc`, and
|
|
25
|
+
`rules/security.mdc` carry the full rule set this skill's checklist draws
|
|
26
|
+
from — read them before writing SQL, not just this summary. This pack
|
|
27
|
+
narrows the general guidance in the core `database-patterns.mdc` rule with
|
|
28
|
+
concrete, engine-specific mechanics.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
### Step 1: Discover the project's own conventions and engine
|
|
33
|
+
|
|
34
|
+
1. Identify the engine (Postgres vs. MySQL vs. another SQL dialect) from
|
|
35
|
+
the project's connection config, migration tool config, or an existing
|
|
36
|
+
`.sql` file's syntax — the safe migration mechanics below differ by
|
|
37
|
+
engine.
|
|
38
|
+
2. Find the migration tool already in use (a framework's own migration
|
|
39
|
+
runner, `Flyway`/`Liquibase`, plain numbered `.sql` files) and match
|
|
40
|
+
its existing naming and up/down (or up-only) convention; do not
|
|
41
|
+
introduce a second migration mechanism into a project that already has
|
|
42
|
+
one.
|
|
43
|
+
3. Read 1-2 existing migrations and 1-2 existing query files for naming
|
|
44
|
+
conventions (`rules/coding-style.mdc`), whether the project already has
|
|
45
|
+
an established pattern for safe `NOT NULL` additions or backfills, and
|
|
46
|
+
whether raw SQL or a query builder/ORM is the project's norm.
|
|
47
|
+
4. Check the table's approximate row count (or ask, if it isn't
|
|
48
|
+
discoverable) before deciding whether a schema change needs the
|
|
49
|
+
zero-downtime multi-step sequence — a near-empty table doesn't need
|
|
50
|
+
the same care as one with millions of rows, but a migration written
|
|
51
|
+
for a small table today should still be safe if that table grows.
|
|
52
|
+
|
|
53
|
+
### Step 2: Design before writing
|
|
54
|
+
|
|
55
|
+
- For a schema change: classify it against `rules/patterns.mdc`'s
|
|
56
|
+
zero-downtime table (nullable column add, `NOT NULL` with constant
|
|
57
|
+
default, `NOT NULL` needing backfill, rename, type change, drop) and
|
|
58
|
+
pick the corresponding safe sequence — do not default to a single
|
|
59
|
+
blocking `ALTER TABLE` for anything beyond a nullable-column add on a
|
|
60
|
+
table that can plausibly grow large.
|
|
61
|
+
- For a new query: identify every `WHERE`/`JOIN ON`/`ORDER BY` column and
|
|
62
|
+
check whether it's already indexed; if not, design the index alongside
|
|
63
|
+
the query in the same change, with composite column order matching the
|
|
64
|
+
query's actual equality-then-range filter shape.
|
|
65
|
+
- For a query inside a loop (fetching a collection, then a per-item
|
|
66
|
+
detail): redesign it as one query — a `JOIN`, an `IN (...)` batch, or an
|
|
67
|
+
ORM eager-load option — before implementing, not as a follow-up
|
|
68
|
+
optimization.
|
|
69
|
+
- Decide whether related writes need a transaction: any write touching
|
|
70
|
+
more than one table, or more than one row-affecting statement against
|
|
71
|
+
the same table where a partial failure would leave the data
|
|
72
|
+
inconsistent, needs one.
|
|
73
|
+
|
|
74
|
+
### Step 3: Implement
|
|
75
|
+
|
|
76
|
+
1. Write the migration/query following `rules/coding-style.mdc`'s naming
|
|
77
|
+
and formatting, matching the project's existing convention over this
|
|
78
|
+
rule set when the two conflict on a purely stylistic point.
|
|
79
|
+
2. Apply the zero-downtime sequence chosen in Step 2; split a migration
|
|
80
|
+
that combines a blocking schema change with a potentially slow
|
|
81
|
+
backfill into separate migration files per `rules/patterns.mdc`.
|
|
82
|
+
3. Parameterize every value — placeholders or the ORM's parameter API,
|
|
83
|
+
never string concatenation/interpolation of a value into SQL text, per
|
|
84
|
+
`rules/security.mdc`. This applies equally inside `PL/pgSQL`/stored
|
|
85
|
+
procedure dynamic SQL.
|
|
86
|
+
4. Add the index(es) designed in Step 2 in the same change as the query
|
|
87
|
+
that needs them, with a comment stating which query a non-obvious
|
|
88
|
+
index (a partial predicate, a surprising composite order) serves.
|
|
89
|
+
5. Wrap multi-statement/multi-table writes in an explicit transaction;
|
|
90
|
+
keep the transaction's lifetime to database work only — commit before,
|
|
91
|
+
or reopen after, any slow external call.
|
|
92
|
+
|
|
93
|
+
### Step 4: Verify
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
EXPLAIN (ANALYZE, BUFFERS) <the new/changed query>; -- Postgres
|
|
97
|
+
EXPLAIN ANALYZE <the new/changed query>; -- MySQL
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Confirm the plan uses the intended index (an Index Scan/Index-Only Scan,
|
|
101
|
+
not an unplanned Seq Scan/full table scan on a table with meaningful row
|
|
102
|
+
count). Run the migration against a representative-sized dataset when one
|
|
103
|
+
is available, not just an empty dev database.
|
|
104
|
+
|
|
105
|
+
### Step 5: Report
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Implemented: migrations/2026_09_25_add_orders_status_index.sql,
|
|
109
|
+
src/orders/queries.sql
|
|
110
|
+
- NOT VALID + VALIDATE CONSTRAINT sequence for the new NOT NULL column
|
|
111
|
+
- Composite index (status, created_at) added for the new list-orders query
|
|
112
|
+
- EXPLAIN ANALYZE confirms Index Scan, no Seq Scan
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Rules
|
|
116
|
+
|
|
117
|
+
- Never build a query by string concatenation or interpolation of a
|
|
118
|
+
user-influenced value into SQL text — use placeholders/parameter
|
|
119
|
+
binding, including inside an ORM's raw-SQL escape hatch and inside
|
|
120
|
+
stored-procedure dynamic SQL.
|
|
121
|
+
- Never add a blocking `NOT NULL` or a blocking index-creation statement
|
|
122
|
+
to a migration touching a table that can have existing rows without
|
|
123
|
+
first checking whether the engine's non-blocking form (`NOT VALID` +
|
|
124
|
+
`VALIDATE CONSTRAINT`, `CREATE INDEX CONCURRENTLY`) applies.
|
|
125
|
+
- Never leave a `WHERE`/`JOIN`/`ORDER BY` column added by this change
|
|
126
|
+
unindexed without checking `EXPLAIN`/`EXPLAIN ANALYZE` first.
|
|
127
|
+
- Never issue a query inside an application-code loop once per row of an
|
|
128
|
+
outer result set — rewrite as a single batched query.
|
|
129
|
+
|
|
130
|
+
## Red Flags
|
|
131
|
+
|
|
132
|
+
| Rationalization | Why it is wrong |
|
|
133
|
+
|---|---|
|
|
134
|
+
| "I'll just `ALTER TABLE ... ADD COLUMN ... NOT NULL` directly, the table isn't that big" | "Not that big" today doesn't stay true; a plain `SET NOT NULL`/blocking constraint add on Postgres takes a full-table-scanning lock, and the safe `NOT VALID` + `VALIDATE CONSTRAINT` sequence costs nothing extra when the table is genuinely small |
|
|
135
|
+
| "I'll build this filter with string formatting since the value comes from our own config, not a user" | "Our own config" today can become user-influenced tomorrow (an admin panel, an import feature) without anyone revisiting this query; parameterize from the start |
|
|
136
|
+
| "The loop is only iterating a handful of rows in dev, one query per row is fine" | Dev data is rarely production-sized; an N+1 that's invisible at 10 rows becomes a real latency/load problem at 10,000 |
|
|
137
|
+
| "I'll add the index after it ships if the query turns out slow" | `CREATE INDEX CONCURRENTLY`/`ALGORITHM=INPLACE` exist specifically so adding it now costs no more than adding it later — there's no reason to defer and risk forgetting |
|
|
138
|
+
|
|
139
|
+
## Verification
|
|
140
|
+
|
|
141
|
+
Do not report the work done until all of the following hold:
|
|
142
|
+
|
|
143
|
+
- Every value in every query is parameterized; no string concatenation or
|
|
144
|
+
interpolation of a user-influenced value into SQL text anywhere in the
|
|
145
|
+
change.
|
|
146
|
+
- Every schema change touching a table that can have existing rows uses
|
|
147
|
+
the zero-downtime sequence from `rules/patterns.mdc` appropriate to its
|
|
148
|
+
category, not a single blocking statement, unless the table is
|
|
149
|
+
genuinely new in this same change.
|
|
150
|
+
- `EXPLAIN`/`EXPLAIN ANALYZE` was run on every new or materially changed
|
|
151
|
+
query and shows the intended index usage.
|
|
152
|
+
- Every multi-table or multi-statement write is wrapped in an explicit
|
|
153
|
+
transaction whose lifetime does not span a slow external call.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"I need to add a required column to a table that already has millions of rows, what's the safe way to do this?",
|
|
5
|
+
"Design an index for a query that filters orders by status and sorts by created_at",
|
|
6
|
+
"I need a migration that adds a foreign key from payments.order_id to orders.id on a 20-million-row table, and it can't take the app down while it runs",
|
|
7
|
+
"This endpoint loops over orders and fetches the user for each one separately -- rewrite it as one query",
|
|
8
|
+
"I'm writing a query that filters by a value from an API request, how should I build it safely?",
|
|
9
|
+
"Add a unique constraint to this column and make sure it doesn't take the table down while it runs",
|
|
10
|
+
"Wrap these two related inserts into orders and wallets so a partial failure can't leave one without the other"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Write a Django migration that adds a required field to this model",
|
|
14
|
+
"Review this Rails ActiveRecord migration for safety before we merge it",
|
|
15
|
+
"Review this Go function for SQL injection risk",
|
|
16
|
+
"Implement a new REST endpoint in this Express app that lists orders",
|
|
17
|
+
"Write a MongoDB aggregation pipeline that joins two collections",
|
|
18
|
+
"Write a Prisma schema migration that adds this field to the model",
|
|
19
|
+
"Fix this failing pytest test for the order validator"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "safe-not-null-backfill",
|
|
25
|
+
"prompt": "I need to add a NOT NULL column to a Postgres table that already has millions of rows. What's the safe way to do this without an outage?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer takes one of two safe paths depending on whether every row can share one value. If a single constant works for every existing and future row, Postgres 11+ (and MySQL 8.0+ for an appended column) apply ADD COLUMN ... NOT NULL DEFAULT <constant> as a metadata-only change with no full-table rewrite -- that one step is safe on its own. If the value must instead be computed or backfilled per row (not one shared constant), the answer must add the column as nullable first, deploy application code that writes it on every insert/update, backfill existing rows in small batches rather than one large UPDATE, and only then enforce NOT NULL using a NOT VALID CHECK constraint followed by a separate VALIDATE CONSTRAINT step -- not a direct ALTER TABLE ... SET NOT NULL, which takes a blocking scan unless the NOT VALID/VALIDATE sequence was used first (or the constant-default fast path applies).",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Either (a) recognizes that a single constant value can safely be added NOT NULL DEFAULT <constant> in one step on modern Postgres/MySQL because it is metadata-only (no table rewrite, no per-row backfill needed), or (b) -- when the value genuinely must be computed or backfilled per row -- states adding the column as nullable first, with application code writing it going forward, before any constraint is enforced.",
|
|
33
|
+
"If following path (b), describes backfilling existing rows in batches (not a single UPDATE touching every row at once); path (a) satisfies this automatically since it needs no backfill at all.",
|
|
34
|
+
"If following path (b), names the concrete non-blocking way to enforce NOT NULL afterward -- an ADD CONSTRAINT ... CHECK (col IS NOT NULL) NOT VALID followed by a separate VALIDATE CONSTRAINT -- rather than a plain SET NOT NULL; path (a) satisfies this automatically since the DEFAULT already guarantees no nulls exist."
|
|
35
|
+
],
|
|
36
|
+
"fail_criteria": [
|
|
37
|
+
"Recommends a direct ALTER TABLE ... ALTER COLUMN ... SET NOT NULL on the large table (whether before or after a backfill) with no constant default and no prior NOT VALID+VALIDATE sequence -- neither safe path was followed.",
|
|
38
|
+
"Recommends backfilling millions of rows with a single unbatched UPDATE statement when the value is not a shared constant.",
|
|
39
|
+
"Claims a VOLATILE default (one that produces a different value per row/per evaluation, e.g. gen_random_uuid() or random()) gets the same metadata-only fast path as a constant -- only a genuinely constant or non-volatile (IMMUTABLE/STABLE) expression that evaluates to the same value across the statement qualifies (a STABLE function like now() does still get the fast path); a VOLATILE default still forces a full table rewrite."
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"calibration": {
|
|
44
|
+
"known_right": "This depends on whether every row can share one value. If the new column's value is the same constant for every row (e.g. a status defaulting to 'pending' for all existing orders), the fast path is safe on its own: `ALTER TABLE orders ADD COLUMN status text NOT NULL DEFAULT 'pending';` -- Postgres 11+ (and MySQL 8.0+ for an appended column) apply this as a metadata-only change, no table rewrite, no lock held for the duration of a scan. If instead the value has to be computed or backfilled per row -- say, derived from existing columns or an external lookup -- do it in stages: add the column as nullable (`ALTER TABLE orders ADD COLUMN status text;`, instant), deploy application code that writes `status` on every new insert/update, then backfill the existing rows in small batches, e.g. an UPDATE with a LIMIT/range predicate run repeatedly (`UPDATE orders SET status = compute_status(id) WHERE status IS NULL AND id BETWEEN :lo AND :hi`), not one UPDATE touching every row -- a single giant UPDATE holds locks and generates a huge amount of WAL, and can itself stall other writers. Once every row has a value, enforce NOT NULL without the usual full-table-scanning lock: `ALTER TABLE orders ADD CONSTRAINT orders_status_not_null CHECK (status IS NOT NULL) NOT VALID;` followed by `ALTER TABLE orders VALIDATE CONSTRAINT orders_status_not_null;` in a separate statement, which scans under a SHARE UPDATE EXCLUSIVE lock that doesn't block concurrent reads/writes the way a plain SET NOT NULL's scan does.",
|
|
45
|
+
"known_wrong": "Easiest way regardless of what the value is: `ALTER TABLE orders ADD COLUMN status text NOT NULL DEFAULT gen_random_uuid();` in one migration -- Postgres 11+ made ALTER TABLE ADD COLUMN NOT NULL DEFAULT metadata-only, so this is instant no matter what the default expression is, millions of rows or not, and there's no need to think about whether the value is a shared constant or computed per row. And if you ever do need to enforce NOT NULL after the fact on an existing column, just run `ALTER TABLE orders ALTER COLUMN status SET NOT NULL` directly -- on modern Postgres that's fast enough not to worry about NOT VALID/VALIDATE CONSTRAINT.",
|
|
46
|
+
"vague": "Add the column, backfill the values, and then enforce the constraint -- just be careful not to lock the table for too long while you do it.",
|
|
47
|
+
"subtle_wrong": "Add the column nullable, ship code that writes it on new rows, and backfill in batches of a few thousand rows at a time so you're not holding one giant lock -- that part matters. Once the backfill finishes and every row has a value, you're safe to just run `ALTER TABLE orders ALTER COLUMN status SET NOT NULL` directly -- since there are no nulls left at that point, the scan Postgres does to verify the constraint will be effectively instant, so there's no real need to bother with the NOT VALID/VALIDATE CONSTRAINT dance on top of the batched backfill you already did."
|
|
48
|
+
},
|
|
49
|
+
"anti_patterns": ["SET NOT NULL"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "parameterized-status-filter",
|
|
53
|
+
"prompt": "Write a query that filters orders by a status value that comes in from an API request.",
|
|
54
|
+
"strictness": "high",
|
|
55
|
+
"expected_behavior": [
|
|
56
|
+
{
|
|
57
|
+
"grader": "judge",
|
|
58
|
+
"rubric": "A correct answer builds the query with a parameter placeholder (e.g. $1, ?, or a named bind parameter) for the status value and binds the value separately through the driver/query API, rather than building the query text by string concatenation or interpolation of the status value directly into the SQL string.",
|
|
59
|
+
"pass_criteria": [
|
|
60
|
+
"Shows the query text using a parameter placeholder (e.g. `$1`, `?`, or a named bind parameter) in place of the status value, not the literal value spliced into the SQL string.",
|
|
61
|
+
"Shows the status value passed as a bound parameter/argument to the query call, separate from the SQL text itself."
|
|
62
|
+
],
|
|
63
|
+
"fail_criteria": [
|
|
64
|
+
"Builds the query text by string concatenation or interpolation of the status value directly into the SQL string (e.g. an f-string, template literal, or `.format()`/`%` splicing the value into the query) instead of using a placeholder."
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"calibration": {
|
|
69
|
+
"known_right": "Use a placeholder and bind the status value separately, never splice it into the query text: `SELECT id, user_id, status, created_at FROM orders WHERE status = $1;` (Postgres) or `SELECT id, user_id, status, created_at FROM orders WHERE status = ?;` (MySQL), then pass the incoming status value as the bound parameter through the driver's query call (e.g. `client.query(sql, [status])`), not interpolated into the string. This holds even if the value is going through an ORM's raw-SQL escape hatch -- pass it through that API's own parameter binding, not a template literal or `.format()` call that puts the value straight into the SQL text.",
|
|
70
|
+
"known_wrong": "Just build the query with string concatenation since it's a simple filter: `\"SELECT * FROM orders WHERE status = '\" + status + \"'\"` -- that's the fastest way to get the value from the request into the query, and status is just a short string so there's not much room for anything to go wrong.",
|
|
71
|
+
"vague": "Make sure you build the query safely so the status value from the request can't cause any problems.",
|
|
72
|
+
"subtle_wrong": "Use an f-string to build the query since it reads cleanly: `f\"SELECT id, user_id, status, created_at FROM orders WHERE status = '{status}'\"`. As long as you validate on the frontend that status can only be one of the known enum values before the request is sent, splicing it into the query text like this is fine -- the frontend validation already narrows what can end up in the string."
|
|
73
|
+
},
|
|
74
|
+
"anti_patterns": ["string concatenation"]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|