Skip to content
Back to skills

Schema Migration Checklist

ASecurity

Use when adding a positions column, a new SQLite table, or modifying schema. Triggers on "add column X to positions", "create table Y", "schema migration N".

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
data-aipythongobashsqldatabase

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add anvilfilbert/Auto-Crypto-Tradingjournal --skill schema-migration-checklist --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Schema Migration Checklist?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Schema Migration Checklist
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anvilfilbert-schema-migration-checklist/badge)](https://www.skillsdirectory.com/skills/anvilfilbert-schema-migration-checklist)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: schema-migration-checklist
description: Use when adding a positions column, a new SQLite table, or modifying schema. Triggers on "add column X to positions", "create table Y", "schema migration N".
---

# Schema Migration Checklist

All schema changes go through `database.py::init_db()` via the `_apply(version, name, sql)` idempotent migration system. Migrations run at app startup and persist their version in `schema_version` table.

## Current state (as of 2026-05-24)

- Latest migration: **v58** (positions.ai_score_at_open)
- Pattern file: `database.py` lines 228-491
- All migrations must be IDEMPOTENT — they run on every startup; `_apply()` skips if already recorded.

## When NOT to add a column

Before adding a column to `positions`, check:
- Can it be computed from existing fields? → Don't store it; compute on read.
- Is it sparse (most rows NULL)? → Consider a separate table with FK to positions.id.
- Is it write-once and bounded? → Column is fine.

Adding to `positions` is a one-way door (rarely worth removing later). Be deliberate.

## Checklist

### 1. Choose the migration version
- Find the highest `_apply(N, ...)` call. New migration = N+1.
- Pattern: `grep -n "_apply([0-9]" database.py | tail -3` to confirm.

### 2. Choose the right column type
- INTEGER: counts, booleans (0/1), enums (cast keyword to int)
- REAL: prices, scores, percentages
- TEXT: keyword strings, JSON blobs, ISO timestamps
- DEFAULT clauses: explicit defaults reduce NULL ambiguity downstream

### 3. Add the migration call
- File: `database.py`, in the appropriate section (positions columns clustered around lines 228-490)
- Pattern:
  ```python
  _apply(N, "positions.<column>",
         "ALTER TABLE positions ADD COLUMN <column> <TYPE> DEFAULT <value>")
  ```
- Group with related migrations (e.g., skill provenance lives at v52-57; calibration at v58).

### 4. Update CLAUDE.md
- File: `CLAUDE.md`, "Database" section (currently lists migrations 47-58).
- Add a line: `**<Feature>:** \`positions.<column> <TYPE>\` (migration N, added YYYY-MM-DD). <One-sentence purpose>. Populated by <where>.`

### 5. Populate the column on new entries
- If populated at trade-open: edit `trading/executor.py::_insert_open_position` INSERT statement + add to parameter list.
- If populated at trade-close: edit `executor._mark_closed()` or wherever close happens.
- If populated post-hoc by analytics: no insert change needed.
- ALWAYS confirm the column value flows from the signal/orchestrator level into `_insert_open_position`. Check `trading/orchestrator.py` for the signal dict construction.

### 6. Backfill historical data (if applicable)
- For columns where historical population is needed: write a one-shot script in `scripts/backfill_<column>.py`.
- Must be idempotent (safe to re-run).
- Run on Pi via scp + python3.

### 7. Update analytics/reporting
- File: `analytics.py`, `ai_rulebook.py`, etc.
- If the column is meant to be in cohort aggregations: add a slice query.
- If meant for a UI page: surface via a route + JS render.

### 8. Tests
- File: `tests/test_database_migrations.py`
- Add a test confirming the column exists after `init_db()`.
- Pattern:
  ```python
  def test_migration_<N>_<column>(in_memory_db):
      cur = in_memory_db.execute("PRAGMA table_info(positions)")
      cols = {r[1]: r[2] for r in cur.fetchall()}
      assert "<column>" in cols
      assert cols["<column>"] == "<TYPE>"
  ```

### 9. **Deploy — bytecode cache nuke is REQUIRED**
- Per `feedback_pi_bytecode_cache.md` memory: deploys touching `database.py::init_db()` or `config.py::snapshot()` MUST nuke __pycache__ before restart.
- Sync via `/tmp/deploy_audit.exp`
- Restart via `/tmp/nuke_cache.exp` (NOT `restart_pi.exp` — the cached `init_db()` won't pick up the new migration without cache wipe).
- Verify: `journalctl -u trading-journal | grep "Applied migration <N>"` should appear once.

### 10. Post-deploy verification
- Query the schema directly:
  ```sql
  SELECT version, name FROM schema_version WHERE version = <N>;
  ```
- If empty: migration didn't run. Check journalctl for errors.
- If present: confirm a new INSERT populates the column with the right value.

### 11. Backup AFTER verification (per deploy routine)
- `bash ~/trading-journal/scripts/backup_db.sh`

## Red flags

- "I'll skip the cache nuke" → stop. The cached `init_db()` bytecode WILL skip your new migration silently. Lesson from 2026-05-22 incident.
- "I'll mutate the column type later" → stop. SQLite ALTER COLUMN is painful. Choose the type carefully now.
- "I'll let the migration run a non-idempotent operation" → stop. Every migration must be safe to re-run.
- "I'll backfill with a non-idempotent script" → stop. Backfill scripts often need re-runs; write them idempotent (`UPDATE ... WHERE <column> IS NULL`).
- "I'll add a column that 95% of rows will be NULL on" → reconsider. A side table with FK is better for sparse data.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.