Database Conventions

SkillDatabases & data

Apply when writing or modifying database schema, queries, migrations, transactions, or tenant-scoped persistence.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Database Conventions skill

What this skill tells your AI

The instructions your AI receives, as published by stella/stella in .agents/skills/conventions-db/SKILL.md and read by ahel’s review.

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.
  • For closed persisted domain values, define one named as const value list and pass it to Drizzle with text({ enum: VALUES }). Add a database CHECK when 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 timestamptz helper from apps/api/src/db/columns.ts. Never introduce a naive PostgreSQL timestamp or ::timestamp cast without explicitly anchoring its time zone.
  • Add indexes for columns used in WHERE, ORDER BY, or JOIN; lead composite indexes with the tenant-scoping columns. Treat changes to large tables as lock-sensitive.

Migrations

  • bun --filter @stll/api db:migrate is the shipped migration path used by CI and deployment. db:push is 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, bulk-backfill, 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 sets lock_timeout and statement_timeout first.
  • For large live tables, follow the repository's guarded concurrent-index protocol: either split and reopen the migrator transaction exactly as enforced by migration-concurrent-index.test.ts, or put repairable work in online-migrations.ts. Keep long backfills outside schema migrations and checkpoint them durably.
  • Validate migration history two ways: apply every committed migration to a fresh database, then confirm bun --filter @stll/api db:push -- --explain reports no schema drift. Do not repair drift by resetting a shared database.

Tenant Scope and Queries

  • Workspace data uses the authorized scopedDb supplied 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 limit and cursor and returns the standard Page<T> envelope from apps/api/src/lib/pagination.ts. Offset pagination, totalCount, and unbounded findMany require 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, IN query, or bulk mutation can express the same work.

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 mutation WHERE clause 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.

Signals

GitHub stars
240
Forks
51
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
conventions-db
Source
github.com/stella/stella