conventions-db
GitHub定义数据库模式、查询、迁移及事务的开发规范。重点规范Schema索引与类型约束,明确迁移需遵循仅扩展原则,将耗时操作移至在线阶段,并引入安全脚本校验以防止破坏性变更。
触发场景
安装
npx skills add stella/stella --skill conventions-db -g -y
SKILL.md
Frontmatter
{
"name": "conventions-db",
"description": "Apply when writing or modifying database schema, queries, migrations, transactions, or tenant-scoped persistence."
}
Database Conventions
Apply when writing or modifying database schema, queries, migrations, transactions, or tenant-scoped persistence.
Schema
- Schema lives under
apps/api/src/db/schema/; read the owning slice and related foreign keys before editing. - To learn whether something is stored and where, search the generated index
first:
rg <word> apps/api/src/db/schema-indexgives one line per column with its builder, flags,file:lineand the first sentence of its comment. A column's comment is what the index shows, so lead it with what the column means. - For closed persisted domain values, define one named
as constvalue list and pass it to Drizzle withtext({ enum: VALUES }). Add a databaseCHECKwhen invalid values could compromise lifecycle, authorization, audit, or workflow invariants; Drizzle's enum option is compile-time-only. - Reserve
.$type<T>()for branded or structured types. Use a native PostgreSQL enum only when the value set is genuinely permanent. - Use cascade deletes for workspace-owned dependants and restrict deletes for shared file references. Trace the full FK graph before multi-resource deletion.
- Timestamp columns use the
timestamptzhelper fromapps/api/src/db/columns.ts. Never introduce a naive PostgreSQL timestamp or::timestampcast without explicitly anchoring its time zone. - Add indexes for columns used in
WHERE,ORDER BY, orJOIN; lead composite indexes with the tenant-scoping columns. Treat changes to large tables as lock-sensitive.
Migrations
Expand-only pre-deploy phase
SQL migrations are a blocking pre-deploy phase, so keep them expand-only: add
tables or columns, add constraints as NOT VALID, and use only short-lock DDL.
Never put data backfills, CREATE [UNIQUE] INDEX CONCURRENTLY, or
ALTER TABLE ... VALIDATE CONSTRAINT in a migration. Governed, bounded
backfills belong to apps/api/src/db/backfill-runtime.ts and registered online
repairs and concurrent index builds (ONLINE_MIGRATION_INDEXES) in
apps/api/src/db/online-migrations.ts.
bun --filter @stll/api db:migrateis the shipped migration path used by CI and deployment.db:pushis a local declarative schema-diff tool; it does not replace committed migrations and must not be described as the deployment path.- Schema changes remain additive across a rollout: add, deploy compatible reads/writes, backfill in bounded batches, switch, then remove the old shape in a later release.
- Migrations run before new API tasks finish rolling out. Old tasks must remain compatible with the migrated schema, and a failed rollout must have a safe forward-fix path.
- Keep irreversible schema operations out of the same release as risky
application changes. Destructive and access-control SQL requires a
statement-scoped acknowledgement enforced by
scripts/check-migration-safety.ts, placed in the comment block directly above the statement:-- stella-migration-safety: reviewed <rule-id> - <why this is safe>. An acknowledgement that clears nothing is an error. Every migration setslock_timeoutandstatement_timeoutfirst. - Keep repairable work in
online-migrations.ts. Long backfills stay outside schema migrations and checkpoint durably. - A migration directory's name is its identity: the migrator applies every directory whose name the database has not recorded, in name order. New timestamps sort after the newest migration on the base branch. Never rename or delete a merged migration; add a new one instead.
- Validate migration history two ways: apply every committed migration to a
fresh database, then confirm
bun --filter @stll/api db:push -- --explainreports no schema drift. Do not repair drift by resetting a shared database.
Tenant Scope and Queries
- Workspace data uses the authorized
scopedDbsupplied by safe handlers so PostgreSQL RLS and query-level scope reinforce each other. Raw/root database access needs a demonstrated system-level reason and a deny-by-default RLS posture. - Ownership IDs come from server-validated context, never request bodies. Keep tenant predicates in the database query even when a preceding authorization check exists.
- Prefer Drizzle's relational query API for ordinary relation reads. Use SQL-like syntax for cross-table filtering, aggregation, locking, unions, or mutations where it expresses the invariant more directly.
- Every list query uses a bounded
limitand cursor and returns the standardPage<T>envelope fromapps/api/src/lib/pagination.ts. Offset pagination,totalCount, and unboundedfindManyrequire explicit justification. - Do not filter unindexed JSONB in request paths. Fetch through indexed tenant columns, then narrow structured content with a type guard rather than a cast.
- Batch relation reads and writes. Never issue a query per item when a join,
relation preload,
INquery, or bulk mutation can express the same work.
Public Corpus Boundary
- Case-law and legislation corpora are read through their own public
connections (
apps/api/src/lib/case-law-public-read-db.ts,legislation-public-read-db.ts). Importing one puts the file inside the public SQL boundary (public-case-law-db-boundary): it may name only public-law relations. Put corpus reads inapps/api/src/lib/case-law/(or the legislation owner) and call them from tenant handlers; never import the connection into a module that names tenant tables. - A read that fails returns a typed failure (a
Resulterror or aReadOutcome), never an empty value (no-failure-as-empty). The caller decides how to degrade and says so in its own response type.
Concurrency and Transactions
- Keep transactions short; perform S3, network, conversion, and other external I/O outside them.
- Close every read-decide-write race. Lock the decisive row with
SELECT ... FOR UPDATE, or encode the expected state/version in the mutationWHEREclause and check the affected-row count. - Make retries converge. Stable identities, unique constraints, conditional transitions, and idempotency keys are stronger than read-before-insert checks.
- Preserve lock order across call sites. When multiple resources must be locked, define and reuse a deterministic ordering to avoid deadlocks.
Verification
Test behavior that schema inference cannot prove: cross-tenant denial, concurrent transitions, replay/idempotence, migration parity, destructive delete ordering, and cursor stability under inserts. Prefer invariant or integration tests over mocked query-shape tests.
版本历史
-
34498cb
当前 2026-11-02 20:19
新增迁移安全规范:强制预部署迁移仅允许扩展操作,禁止阻塞性DDL;引入check-migration-safety脚本拦截危险语句,并将并发索引构建和数据回填移至在线阶段。
-
0f3714b
2026-10-19 03:35
新增生成式Schema索引机制以简化列信息查找;在Schema章节补充索引搜索前置要求;在Migrations章节细化并发索引协议与安全审查规则。
-
99f1521
2026-08-27 17:45
收紧迁移安全检查规则:引入基于语句和规则的确认机制,新增访问控制与批量回填检测,统一基线文件管理。
-
dd81665
2026-08-16 07:08
更新 Schema 文件路径,细化枚举和索引规范,强化时间戳时区要求,明确 CI/CD 迁移流程。
- 85792bd 2026-07-24 16:11


