Migrate gocron data
Treat migrations as compatibility code. Preserve existing installations and all
three supported databases; do not optimize only for a fresh SQLite database.
Compatibility invariants
- Upgrade existing installations in place; never require recreating the
database or discarding tasks, configuration, users, or logs.
- Validate from an
N-1 schema/data fixture and every older schema directly
affected by the change. A fresh database alone is not evidence.
- Prefer additive, idempotent changes and compatible defaults on SQLite,
MySQL, and PostgreSQL. Do not drop or reinterpret persisted data in a minor
or patch release.
- Do not advance the migration version until all work succeeds. A failed
migration must remain diagnosable and safely retryable.
- Document downgrade safety and backup/recovery steps. If downgrade can lose or
corrupt data, stop and obtain explicit approval before implementation.
Performance invariants
- Test against a representative populated database, not only a tiny fixture.
Record row counts and migration duration for performance-sensitive changes.
- For populated-table changes, assess full scans, lock/transaction duration,
temporary disk, and write amplification on SQLite, MySQL, and PostgreSQL.
- On SQLite, keep write transactions short and avoid per-row commits. Use safe
bounded batches for large backfills and test busy/lock behavior with readers.
- Add indexes only for demonstrated query patterns; inspect the query plan and
account for index build time, disk growth, and write overhead.
- Prefer resumable/idempotent batches when a single transaction could block
startup or exceed reasonable memory or disk. State the interruption behavior.
Establish the change
- Inspect
cmd/gocron/gocron.go, internal/models/migration.go, the affected
models, and nearby migration tests before editing.
- Determine whether the change affects an existing table, creates a new table,
or transforms existing data. Do not create an empty migration for a release
with no schema or data change.
- Derive the migration id from the target
AppVersion using the repository's
established conversion. Never reuse an id. If the release version is not
known and a new id is required, stop and ask for it.
Implement atomically
For a new persisted model or schema change:
- Add a new unique id to
versionIds in chronological release order.
- Add the matching
migration.upgradeForNNN entry at the same index.
- Implement
upgradeForNNN with the transaction passed to it; return every
error instead of logging and continuing.
- Add new install-time models to the
Install table slice. Do not add an
existing-table column there as a substitute for an upgrade.
- Make the upgrade idempotent where practical. Guard data rewrites that would
corrupt values on a second run.
- Avoid database-specific SQL. When unavoidable, branch on the GORM dialect
and implement SQLite, MySQL, and PostgreSQL behavior explicitly.
- Add a focused migration test that builds the
N-1 pre-upgrade schema (and
older affected schemas), runs the upgrade, proves existing rows and values
survive, and exercises a second run when idempotency is expected.
Do not rely only on GORM AutoMigrate when data must be renamed, remapped,
backfilled, deduplicated, or constrained.
Verify
Run:
python3 .agents/skills/migration/scripts/check_migration.py
go test -race ./internal/models/...
go test -race ./cmd/gocron/...
If a specific migration was added, pass its id to the structural checker:
python3 .agents/skills/migration/scripts/check_migration.py 191
Then invoke $verify before committing. Report the migration id, affected
tables, upgrade path, downgrade/backup implications, database-specific risks,
exact tests run, and performance evidence when the change has material risk.
1---2name: migration3description: Create, review, or verify gocron database migrations across SQLite, MySQL, and PostgreSQL. Use when adding or changing GORM models, columns, indexes, constraints, persisted settings, migration version ids, Install tables, upgradeForNNN functions, or migration tests.4---56# Migrate gocron data78Treat migrations as compatibility code. Preserve existing installations and all9three supported databases; do not optimize only for a fresh SQLite database.1011## Compatibility invariants1213- Upgrade existing installations in place; never require recreating the14 database or discarding tasks, configuration, users, or logs.15- Validate from an `N-1` schema/data fixture and every older schema directly16 affected by the change. A fresh database alone is not evidence.17- Prefer additive, idempotent changes and compatible defaults on SQLite,18 MySQL, and PostgreSQL. Do not drop or reinterpret persisted data in a minor19 or patch release.20- Do not advance the migration version until all work succeeds. A failed21 migration must remain diagnosable and safely retryable.22- Document downgrade safety and backup/recovery steps. If downgrade can lose or23 corrupt data, stop and obtain explicit approval before implementation.2425## Performance invariants2627- Test against a representative populated database, not only a tiny fixture.28 Record row counts and migration duration for performance-sensitive changes.29- For populated-table changes, assess full scans, lock/transaction duration,30 temporary disk, and write amplification on SQLite, MySQL, and PostgreSQL.31- On SQLite, keep write transactions short and avoid per-row commits. Use safe32 bounded batches for large backfills and test busy/lock behavior with readers.33- Add indexes only for demonstrated query patterns; inspect the query plan and34 account for index build time, disk growth, and write overhead.35- Prefer resumable/idempotent batches when a single transaction could block36 startup or exceed reasonable memory or disk. State the interruption behavior.3738## Establish the change3940- Inspect `cmd/gocron/gocron.go`, `internal/models/migration.go`, the affected41 models, and nearby migration tests before editing.42- Determine whether the change affects an existing table, creates a new table,43 or transforms existing data. Do not create an empty migration for a release44 with no schema or data change.45- Derive the migration id from the target `AppVersion` using the repository's46 established conversion. Never reuse an id. If the release version is not47 known and a new id is required, stop and ask for it.4849## Implement atomically5051For a new persisted model or schema change:52531. Add a new unique id to `versionIds` in chronological release order.542. Add the matching `migration.upgradeForNNN` entry at the same index.553. Implement `upgradeForNNN` with the transaction passed to it; return every56 error instead of logging and continuing.574. Add new install-time models to the `Install` table slice. Do not add an58 existing-table column there as a substitute for an upgrade.595. Make the upgrade idempotent where practical. Guard data rewrites that would60 corrupt values on a second run.616. Avoid database-specific SQL. When unavoidable, branch on the GORM dialect62 and implement SQLite, MySQL, and PostgreSQL behavior explicitly.637. Add a focused migration test that builds the `N-1` pre-upgrade schema (and64 older affected schemas), runs the upgrade, proves existing rows and values65 survive, and exercises a second run when idempotency is expected.6667Do not rely only on GORM `AutoMigrate` when data must be renamed, remapped,68backfilled, deduplicated, or constrained.6970## Verify7172Run:7374```bash75python3 .agents/skills/migration/scripts/check_migration.py76go test -race ./internal/models/...77go test -race ./cmd/gocron/...78```7980If a specific migration was added, pass its id to the structural checker:8182```bash83python3 .agents/skills/migration/scripts/check_migration.py 19184```8586Then invoke `$verify` before committing. Report the migration id, affected87tables, upgrade path, downgrade/backup implications, database-specific risks,88exact tests run, and performance evidence when the change has material risk.