API developer specializing in clean, well-documented REST APIs
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
SQLite database expertise for migrations, multi-tenant patterns, transactional safety, FTS5 virtual tables, and schema evolution. Use this skill for ANY work involving schema changes, data migrations, INSERT/UPSERT semantics, FTS5 rebuilds, transaction rollback design, multi-tenant data isolation, or SQLite performance tuning. Triggers include: writing migration SQL, modifying `_import_table`-style importers, designing UNIQUE/PK constraints, adding indexes for JOINs, debugging "rows missing" / "wrong count" / "stuck for hours" symptoms in SQLite-backed systems.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
SQLite-domain specialist for migrations and data integrity.
Migrations are integration code. Most "data" bugs aren't algorithmic — they're contract bugs between schema, importer, and verifier. This skill exists because P4 (the VNX one-shot consolidation migrator) consumed 5 fix-forward rounds before converging, and the bugs were almost entirely SQLite/multi-tenant gotchas a generalist would miss.
The specific recurring failure-modes are catalogued in references/bug-taxonomy.md with concrete VNX examples. Read it before writing any migration code.
schemas/migrations/_import_table, _compare_counts, or any code that ATTACHes / SELECTs / INSERTs across tenant boundariesJOIN or correlated-subquery code that scans tables ≥10k rowsreferences/sqlite-fts5-gotchas.md)INSERT OR IGNORE or INSERT OR REPLACEBEFORE proposing any design, fix, or implementation:
Consult relevant ADRs in docs/governance/decisions/. Special attention to:
Consult relevant memory in ~/.claude/projects/<your-project>/memory/MEMORY.md — particularly entries about past architectural incidents.
Check P4-style incident docs in claudedocs/ for analogous failures (e.g., 2026-05-09-p4-migration-architecture-lessons.md for multi-tenant migration patterns).
State your foundational read aloud at the start of your response. Example: "ADR-007 applies: new tabel X needs composite PK over project_id. Per P4 §4.2, single-column UNIQUE is a smell. Memory [[adr-007-multitenant-composite-keys]] confirms."
Skipping STEP 0 is a process violation, not a shortcut. The FUT-1 chain (2026-05-28) burned 6 codex rounds because ADR-007 was not consulted at design time.
Before writing migration code, walk through references/migration-defense-checklist.md (loaded explicitly when needed; checklist is reproduced inline below for fast access).
When asked to fix a "data missing" or "data wrong" bug, the first investigative step is EXPLAIN QUERY PLAN on the failing query and .indexes on the related tables. Two-thirds of the bugs in the P4 history were diagnosable this way in under five minutes once anyone thought to look.
SQLite supports two journal modes operators commonly hit: DELETE (rollback journal as a sibling file <db>-journal) and WAL (<db>-wal + <db>-shm). Never copy a DB file mid-write without one of:
sqlite3.Connection.backup(dest) — handles both modes correctly, locks briefly, copies all pages including in-flight onesBEGIN IMMEDIATE; .dump | sqlite3 newfile; COMMIT; — slower but produces SQL textPRAGMA wal_checkpoint(TRUNCATE) flushes the WAL into the base file, then a normal file copy is consistentshutil.copy2(src, dst) of a WAL DB is not safe: committed state may live in -wal and the copy will be partial. R2 of P4 hit this; fixed by switching to Connection.backup().
If a writer process is killed mid-transaction, the journal/WAL file remains. The next process to open the DB reads it and replays/rolls back. This is automatic but has two implications:
sqlite3 <db> "SELECT 1;") before inspecting it. Otherwise the file size you see includes the in-flight transaction's pages and reads can be confusing.<db>.presnap.*) created by _snapshot_central are belt-and-suspenders on top of journal recovery, useful when the rollback semantics need to span multiple migrations rather than a single transaction. Don't delete them while a migrator is alive.FTS5 is a separate physical store with its own segments. Schema-altering it usually means drop + recreate + re-INSERT-from-temp-table. There is no ALTER TABLE … ADD COLUMN for FTS5 vtabs; column changes require rebuild. See references/sqlite-fts5-gotchas.md for the rebuild pattern and the perf trap that almost shipped in P4 round 4.
Read-only attach uses a URI:
con.execute("ATTACH DATABASE ? AS src", (f"file:{src_path}?mode=ro",))
The mode=ro flag is critical — without it, the attach opens a write connection that briefly modifies the source's -shm file and breaks the read-only contract (operator backups can fail integrity checks).
In a single-tenant database, id INTEGER PRIMARY KEY is fine. In a multi-tenant central DB, that same constraint causes silent data loss the moment two tenants happen to have overlapping id values. The P4 chain shipped this bug twice (in terminal_leases and execution_targets).
For any table that will hold rows from multiple tenants, the unique constraints should be (project_id, <natural_key>), not <natural_key> alone. Examples that bit P4:
terminal_leases.terminal_id UNIQUE → must be (project_id, terminal_id) UNIQUEexecution_targets.target_key UNIQUE → compositetag_combinations.tag_tuple UNIQUE → compositecode_snippets FTS5 row identity is rowid + project_id column; queries must always filter on project_idTwo patterns coexist in VNX:
project_id column with the runtime project's id, regardless of what the source row contained. Used by _import_table. Risk: silent skip on PK conflict (see T2 in bug taxonomy).dispatch_register, append_receipt) takes a project_id parameter and writes it explicitly. Used by per-project state code. Risk: callers forget; helper enforcement matters.Prefer pattern 2 for new code. When migrating legacy single-tenant data, pattern 1 is unavoidable — pair it with ON CONFLICT … DO UPDATE SET project_id=excluded.project_id so re-stamping wins on collision (see Section 3).
Per-project verification queries MUST include WHERE project_id = ?. The P4 verifier had a bug where code_snippets count was reported as the global total for every project; this only manifests because of FTS5 vtab semantics but the lesson is broader: never aggregate without scope in a multi-tenant verifier.
This decision shipped wrong in P4. Cheat sheet:
| Situation | Use |
|---|---|
| Re-running migration; rows might already exist; want to skip duplicates | INSERT OR IGNORE only if PK includes tenant scope |
| Same as above but PK is single-column | INSERT … ON CONFLICT(<pk>) DO UPDATE SET <safe_cols>=excluded.<safe_cols> (UPSERT) — be explicit about which columns are safe to overwrite |
| Multiple tenants might have same PK natural key | Composite (project_id, key) UNIQUE; INSERT OR IGNORE becomes safe again |
| Want strict "fail if duplicate" | Plain INSERT — sqlite raises IntegrityError |
| Replacing entire row | INSERT OR REPLACE — but this deletes-then-inserts, breaking FK CASCADE in surprising ways |
Default for migration code: composite UNIQUE + INSERT OR IGNORE. It's the simplest invariant: each tenant's row is uniquely identified, retries are no-ops, and there's no "stale row wins" surprise.
A migrator that can be safely re-run is worth orders of magnitude more than one that requires careful manual cleanup on every retry. Idempotency means:
imported_at or run_id so repeat imports are detectablep4_import_idempotency(project_id, source_table, source_rowid) records what's been doneThe P4 design uses ALTER-TABLE migrations layered on whatever central schema happened to exist. This works for incremental changes but fragile for greenfield (--fresh-central had to be added in R3 because empty central produced empty migration runs).
For new migrators, prefer explicit central schema file + descriptor-driven importer — see references/multi-tenant-patterns.md Section 3 for the pattern.
SQLite's transaction rollback (BEGIN/ROLLBACK) is automatic on Python sqlite3.Connection exceptions. But cross-DB rollback (e.g. central QI + central RC) requires explicit coordination — the P4 pattern uses _snapshot_central to create <db>.presnap.<phase>.<pid> files before risky migrations, then restores them on failure. This is correct only if every writer respects the contract.
The R4 perf bug: migration 0016_rebuild_fts5.sql had:
INSERT INTO code_snippets (...)
SELECT ..., COALESCE(
(SELECT project_id FROM snippet_metadata m
WHERE m.snippet_rowid = code_snippets_rebuild_tmp.rowid),
'vnx-dev'
)
FROM code_snippets_rebuild_tmp;
snippet_metadata.snippet_rowid was not indexed. For 727k snippets × 119k metadata rows = ~87B row scans = ~3-5h ETA.
Fix: add CREATE INDEX IF NOT EXISTS idx_snippet_metadata_rowid ON snippet_metadata(snippet_rowid); before the rebuild SELECT. Reduces to O(N log M) ≈ 17M ops, completes in seconds.
Rule: any migration with JOIN or correlated subquery involving a table ≥10k rows MUST include the supporting index in the same migration file.
Always run before shipping a migration that touches large tables:
EXPLAIN QUERY PLAN <your-INSERT/SELECT>;
Look for SCAN TABLE (bad for inner loops). Want to see SEARCH TABLE … USING INDEX ….
~5k rows/sec on a modern SSD, dominated by tokenizer work. For 727k rows expect ~150 sec on the rebuild alone, plus journal write overhead (~30% extra). If observed >10 min for ~1M rows, suspect missing index on subquery columns.
A verifier that shares code paths with the importer is structurally co-blind: any helper bug affects both, neither catches the other. Three rules:
sqlite3.Connection, ideally via a different code module. The P4 verifier opens via _compare_counts which reuses importer helpers; this is why R5 shipped a verifier-aggregate bug.project_id. A "central total" check is fine as a separate check, not as the per-project verification.project_id and it doesn't, the verifier must raise (or write to read_errors) — not silently fall back to global COUNT(*). The P4 R5 fix made _compare_counts strict about this.Apply each item before merging migration code. Each line corresponds to a real bug from the P4 history.
UNIQUE(project_id, key) not UNIQUE(key) alone (T3, T6 in references/bug-taxonomy.md)INSERT OR IGNORE only used where the unique constraint already includes project_id; otherwise switch to ON CONFLICT … DO UPDATE (T2)WHERE project_id = ?; no aggregate fallback (T4)DEFAULT '<value>' on a column added by ALTER must be reasoned about in the importer's stamp logic. If the default is "vnx-dev" or another sentinel that could mask un-stamped rows, add a NOT NULL CHECK in the same migration that forbids the sentinel post-migration (T1)JOIN or (SELECT … WHERE x = outer.y) against a table ≥10k rows includes the supporting CREATE INDEX (T7)read_errors; never silently degrade to count=0 (T8)<feature>_skipped / <feature>_failed table includes run_id and resolved_at to filter cross-run state (T9)Connection.backup() or wal_checkpoint(TRUNCATE) + cp, not bare shutil.copy2 (T10)references/bug-taxonomy.md — the 10 recurring patterns with VNX examples; READ FIRST when triggeredreferences/sqlite-fts5-gotchas.md — FTS5 rebuild patterns + perf trapsreferences/multi-tenant-patterns.md — composite key design, stamping strategies, descriptor-driven importersreferences/p4-postmortem-summary.md — links to full lessons doc + key code:line citationsintelligence-engineer instead. This skill is the generic SQLite layer; intelligence-engineer is the VNX domain layer.reviewer may request a database-engineer second-opinion review when a PR touches schemas/, _import_table-style code, or migration files.Database work still ships through the same code-review pipeline. The patterns in backend-developer/SKILL.md Section "Codex Defense Checklist" apply: atomic writes, fcntl locking on shared NDJSON, null guards, schema version checks. Treat that as a baseline and the Migration Defense Checklist above as additive for any migration-touching work.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
API developer specializing in clean, well-documented REST APIs
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
System architecture specialist for designing scalable solutions that handle complexity and evolve under change
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Backend developer creating fault-tolerant, scalable server-side solutions
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Multi-project Control Centre supervisor. Supervisors N per-project T0's from a single interactive Claude Code session. Read-only orchestration — no code writes.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
SEO data analysis, pattern identification, and actionable insights generation
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Systematic debugging specialist focused on root cause analysis
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.