Refactors the migration system to follow the pixa pattern:
Changes
New file: internal/database/migrations/000.sql
Bootstrap migration that creates schema_migrations with version INTEGER PRIMARY KEY
Self-contained: includes both CREATE TABLE IF NOT EXISTS and INSERT OR IGNORE INTO schema_migrations (version) VALUES (0)
Refactored: internal/database/migrations.go
Go code no longer creates the migrations table inline — it reads and executes 000.sql as a bootstrap step before the normal migration loop
ParseMigrationVersion(filename) — exported function that extracts integer version from filenames like 001_initial.sql → 1
ApplyMigrations(ctx, db, log) — exported function for tests to apply schema without full fx lifecycle
bootstrapMigrationsTable — checks if table exists; if missing, runs 000.sql; if present, checks for legacy format
convertLegacyMigrations — one-time conversion for existing databases that stored versions as TEXT filenames (e.g. "001_initial.sql") to INTEGER versions (e.g. 1)
Transaction-wrapped migration application — each migration runs in a transaction for atomicity
Sentinel error ErrInvalidMigrationFilename for static error wrapping
TestApplyMigrationsIdempotent — double-apply is a no-op
TestApplyMigrationsLegacyConversion — simulates old TEXT-based table with filename entries, verifies conversion to integer format
Backward Compatibility
Existing databases with the old TEXT-based schema_migrations table are automatically converted on first run. The conversion:
Detects legacy entries (versions containing _)
Parses integer version from each legacy filename
Drops the old table
Creates the new INTEGER-based table via 000.sql
Re-inserts all parsed integer versions
All existing migrations (001-007) continue to work unchanged.
Closes #171
Refactors the migration system to follow the [pixa pattern](https://git.eeqj.de/sneak/pixa/pulls/36):
## Changes
### New file: `internal/database/migrations/000.sql`
- Bootstrap migration that creates `schema_migrations` with `version INTEGER PRIMARY KEY`
- Self-contained: includes both `CREATE TABLE IF NOT EXISTS` and `INSERT OR IGNORE INTO schema_migrations (version) VALUES (0)`
### Refactored: `internal/database/migrations.go`
- **Go code no longer creates the migrations table inline** — it reads and executes `000.sql` as a bootstrap step before the normal migration loop
- **`ParseMigrationVersion(filename)`** — exported function that extracts integer version from filenames like `001_initial.sql` → `1`
- **`ApplyMigrations(ctx, db, log)`** — exported function for tests to apply schema without full fx lifecycle
- **`bootstrapMigrationsTable`** — checks if table exists; if missing, runs `000.sql`; if present, checks for legacy format
- **`convertLegacyMigrations`** — one-time conversion for existing databases that stored versions as TEXT filenames (e.g. `"001_initial.sql"`) to INTEGER versions (e.g. `1`)
- **Transaction-wrapped migration application** — each migration runs in a transaction for atomicity
- Sentinel error `ErrInvalidMigrationFilename` for static error wrapping
### New file: `internal/database/migrations_test.go`
- `TestParseMigrationVersion` — valid/invalid filename parsing
- `TestApplyMigrationsFreshDatabase` — verifies bootstrap creates table, all migrations apply, application tables exist
- `TestApplyMigrationsIdempotent` — double-apply is a no-op
- `TestApplyMigrationsLegacyConversion` — simulates old TEXT-based table with filename entries, verifies conversion to integer format
## Backward Compatibility
Existing databases with the old TEXT-based `schema_migrations` table are automatically converted on first run. The conversion:
1. Detects legacy entries (versions containing `_`)
2. Parses integer version from each legacy filename
3. Drops the old table
4. Creates the new INTEGER-based table via `000.sql`
5. Re-inserts all parsed integer versions
All existing migrations (001-007) continue to work unchanged.
Refactors the migration system to follow the pixa pattern:
- Add 000.sql bootstrap migration that creates schema_migrations with
INTEGER PRIMARY KEY version column
- Go code no longer creates the migrations table inline; it reads and
executes 000.sql as a bootstrap step before the normal migration loop
- Export ParseMigrationVersion and ApplyMigrations for test use
- Add legacy TEXT-to-INTEGER conversion for existing databases that
stored migration versions as filenames (e.g. '001_initial.sql')
- Wrap individual migration application in transactions for safety
- Add comprehensive tests for version parsing, fresh database bootstrap,
idempotent re-application, and legacy conversion
000_migration.sql — contains ONLY the creation of the migrations tracking table itself. Nothing else.
The PR creates internal/database/migrations/000.sql instead of 000_migration.sql. All other migrations in this repo follow the NNN_description.sql pattern (001_initial.sql, 002_remove_container_id.sql, etc.). The bootstrap migration should be 000_migration.sql to match both the policy and the existing naming convention.
All must be updated to reference 000_migration.sql.
2. Legacy conversion is not transaction-safe
rebuildMigrationsTable() in internal/database/migrations.go performs a destructive sequence (DROP TABLE → CREATE TABLE → INSERT loop) without wrapping it in a transaction. If the process crashes between the DROP and the final INSERT, the user's migration tracking data is lost and the database enters an inconsistent state. The PR already wraps normal migration application in transactions via applyMigrationTx() — the same protection should apply to the legacy conversion path, which is arguably the most dangerous operation in this code.
No changes to Makefile, Dockerfile, .golangci.yml, or CI config. No weakened assertions. ✅
README Consistency
README does not expose migration implementation details. No update needed. ✅
Verdict: FAIL
The bootstrap migration file must be renamed from 000.sql to 000_migration.sql per REPO_POLICIES.md, and all hardcoded references updated.
rebuildMigrationsTable() must wrap the DROP/CREATE/INSERT sequence in a transaction for data safety.
## Review: [PR #172](https://git.eeqj.de/sneak/upaas/pulls/172) — Move schema_migrations table creation into 000.sql
### Policy Divergences
**1. Bootstrap migration filename violates REPO_POLICIES.md naming convention**
REPO_POLICIES.md states:
> `000_migration.sql` — contains ONLY the creation of the migrations tracking table itself. Nothing else.
The PR creates `internal/database/migrations/000.sql` instead of `000_migration.sql`. All other migrations in this repo follow the `NNN_description.sql` pattern (`001_initial.sql`, `002_remove_container_id.sql`, etc.). The bootstrap migration should be `000_migration.sql` to match both the policy and the existing naming convention.
Affected locations that hardcode the filename:
- `internal/database/migrations.go` — `applyBootstrapMigration()`: `migrationsFS.ReadFile("migrations/000.sql")`
- `internal/database/migrations.go` — `rebuildMigrationsTable()`: `migrationsFS.ReadFile("migrations/000.sql")`
- `internal/database/migrations.go` — `bootstrapMigrationsTable()` doc comment references `000.sql`
All must be updated to reference `000_migration.sql`.
**2. Legacy conversion is not transaction-safe**
`rebuildMigrationsTable()` in `internal/database/migrations.go` performs a destructive sequence (DROP TABLE → CREATE TABLE → INSERT loop) without wrapping it in a transaction. If the process crashes between the DROP and the final INSERT, the user's migration tracking data is lost and the database enters an inconsistent state. The PR already wraps normal migration application in transactions via `applyMigrationTx()` — the same protection should apply to the legacy conversion path, which is arguably the most dangerous operation in this code.
### Requirements Checklist ([issue #171](https://git.eeqj.de/sneak/upaas/issues/171))
| Requirement | Status |
|---|---|
| `schema_migrations` table DDL lives in a `.sql` file, not inline Go code | ✅ Met |
| `version` column is `INTEGER PRIMARY KEY` (not TEXT) | ✅ Met |
| Bootstrap `.sql` is self-contained (CREATE TABLE + INSERT OR IGNORE) | ✅ Met |
| Go code does zero INSERTs for the bootstrap — just reads and executes the SQL file | ✅ Met |
### Test Coverage
| Exported API | Test | Status |
|---|---|---|
| `ParseMigrationVersion()` | `TestParseMigrationVersion` | ✅ Covered |
| `ApplyMigrations()` | `TestApplyMigrationsFreshDatabase`, `TestApplyMigrationsIdempotent`, `TestApplyMigrationsLegacyConversion` | ✅ Covered |
| `ErrInvalidMigrationFilename` | Tested via `ParseMigrationVersion` error cases | ✅ Covered |
### Build Result
`docker build .` — **PASS** ✅
### Cheating Check
No changes to Makefile, Dockerfile, `.golangci.yml`, or CI config. No weakened assertions. ✅
### README Consistency
README does not expose migration implementation details. No update needed. ✅
### Verdict: **FAIL**
1. The bootstrap migration file must be renamed from `000.sql` to `000_migration.sql` per REPO_POLICIES.md, and all hardcoded references updated.
2. `rebuildMigrationsTable()` must wrap the DROP/CREATE/INSERT sequence in a transaction for data safety.
- Rename bootstrap migration from 000.sql to 000_migration.sql per
REPO_POLICIES.md naming convention (NNN_description.sql pattern)
- Update all hardcoded references in migrations.go
- Wrap rebuildMigrationsTable() DROP/CREATE/INSERT sequence in a
single transaction for crash safety
- Update test case filename to match renamed file
1. Bootstrap migration renamed to 000_migration.sql
Renamed internal/database/migrations/000.sql → internal/database/migrations/000_migration.sql per REPO_POLICIES.md naming convention (NNN_description.sql pattern). Updated all hardcoded references:
applyBootstrapMigration() — ReadFile path and error messages
rebuildMigrationsTable() — ReadFile path and error messages
bootstrapMigrationsTable() — doc comment
Test case in TestParseMigrationVersion updated from 000.sql to 000_migration.sql
2. Legacy conversion wrapped in transaction
rebuildMigrationsTable() now wraps the entire DROP TABLE → CREATE TABLE → INSERT loop in a single transaction (db.BeginTx / tx.Commit). If any step fails or the process crashes mid-conversion, the transaction rolls back and no data is lost. The file read (migrationsFS.ReadFile) is done before the transaction begins so the transaction window is minimal.
## Rework Summary
Fixed both review findings from [PR #172](https://git.eeqj.de/sneak/upaas/pulls/172):
### 1. Bootstrap migration renamed to `000_migration.sql`
Renamed `internal/database/migrations/000.sql` → `internal/database/migrations/000_migration.sql` per REPO_POLICIES.md naming convention (`NNN_description.sql` pattern). Updated all hardcoded references:
- `applyBootstrapMigration()` — ReadFile path and error messages
- `rebuildMigrationsTable()` — ReadFile path and error messages
- `bootstrapMigrationsTable()` — doc comment
- Test case in `TestParseMigrationVersion` updated from `000.sql` to `000_migration.sql`
### 2. Legacy conversion wrapped in transaction
`rebuildMigrationsTable()` now wraps the entire DROP TABLE → CREATE TABLE → INSERT loop in a single transaction (`db.BeginTx` / `tx.Commit`). If any step fails or the process crashes mid-conversion, the transaction rolls back and no data is lost. The file read (`migrationsFS.ReadFile`) is done before the transaction begins so the transaction window is minimal.
### Build Result
`docker build .` — **PASS** ✅ (all tests pass, lint clean, binary builds)
Pre-1.0 software with no installed base — no need to handle
converting from old TEXT-based schema_migrations format.
Removed: convertLegacyMigrations, ensureBootstrapVersion,
readLegacyVersions, rebuildMigrationsTable, and
TestApplyMigrationsLegacyConversion.
Simplified bootstrapMigrationsTable to only check existence
and run 000_migration.sql if missing.
Per sneak's instruction, removed all legacy/old-style migration table conversion code. This is pre-1.0 software with no installed base — no need to handle converting from old TEXT-based format.
Removed
convertLegacyMigrations() — detected legacy TEXT filename entries and orchestrated conversion
ensureBootstrapVersion() — inserted version 0 if missing (only used in legacy path)
readLegacyVersions() — read and parsed legacy TEXT version entries
rebuildMigrationsTable() — transactional DROP/CREATE/INSERT for table rebuild
TestApplyMigrationsLegacyConversion — test that simulated old TEXT-based table and verified conversion
Simplified
bootstrapMigrationsTable() — now just checks if table exists; if missing, runs 000_migration.sql. If table exists, returns nil. No legacy format detection.
No references to legacy conversion in README. No update needed. ✅
## Rework Summary — Remove Legacy Conversion
Per [sneak's instruction](https://git.eeqj.de/sneak/upaas/pulls/172#issuecomment-1323), removed all legacy/old-style migration table conversion code. This is pre-1.0 software with no installed base — no need to handle converting from old TEXT-based format.
### Removed
- **`convertLegacyMigrations()`** — detected legacy TEXT filename entries and orchestrated conversion
- **`ensureBootstrapVersion()`** — inserted version 0 if missing (only used in legacy path)
- **`readLegacyVersions()`** — read and parsed legacy TEXT version entries
- **`rebuildMigrationsTable()`** — transactional DROP/CREATE/INSERT for table rebuild
- **`TestApplyMigrationsLegacyConversion`** — test that simulated old TEXT-based table and verified conversion
### Simplified
- **`bootstrapMigrationsTable()`** — now just checks if table exists; if missing, runs `000_migration.sql`. If table exists, returns nil. No legacy format detection.
### Net change: -213 lines, +2 lines
### Build Result
`docker build .` — **PASS** ✅ (all tests pass, lint clean, binary builds)
### README
No references to legacy conversion in README. No update needed. ✅
internal/database/migrations/000_migration.sql contains DDL; Go code just reads and executes it via applyBootstrapMigration()
version column is INTEGER PRIMARY KEY
✅ Met
000_migration.sql line 4: version INTEGER PRIMARY KEY
000_migration.sql is self-contained (CREATE TABLE + INSERT OR IGNORE)
✅ Met
File contains both CREATE TABLE IF NOT EXISTS and INSERT OR IGNORE INTO schema_migrations (version) VALUES (0)
Go code does zero INSERTs for bootstrap
✅ Met
applyBootstrapMigration() only does db.ExecContext(ctx, string(content)) — no Go-side INSERT statements
Human Comment Compliance
sneak requested: remove all legacy conversion code (pre-1.0, no installed base) → ✅Fully addressed. All legacy functions (convertLegacyMigrations, ensureBootstrapVersion, readLegacyVersions, rebuildMigrationsTable) and TestApplyMigrationsLegacyConversion are gone. bootstrapMigrationsTable() is now a clean check-and-create.
ApplyMigrations exported for test use without full fx lifecycle — good design
ParseMigrationVersion handles edge cases (empty, no extension, non-numeric, underscore-prefixed)
migrate() method delegates cleanly to ApplyMigrations(ctx, d.database, d.log)
Net change is well-structured: -48 old lines, +272 new lines across 3 files
Verdict: PASS✅
All four issue requirements implemented correctly. Sneak's rework request fully addressed. Code is clean, tested, and builds successfully. No policy violations.
## Re-Review: [PR #172](https://git.eeqj.de/sneak/upaas/pulls/172) — Move schema_migrations table creation into 000.sql
Third review pass after legacy conversion code removal per [sneak's instruction](https://git.eeqj.de/sneak/upaas/pulls/172#issuecomment-1323).
### Requirements Checklist ([issue #171](https://git.eeqj.de/sneak/upaas/issues/171))
| Requirement | Status | Evidence |
|---|---|---|
| `schema_migrations` DDL in `.sql` file, not inline Go | ✅ Met | `internal/database/migrations/000_migration.sql` contains DDL; Go code just reads and executes it via `applyBootstrapMigration()` |
| `version` column is `INTEGER PRIMARY KEY` | ✅ Met | `000_migration.sql` line 4: `version INTEGER PRIMARY KEY` |
| `000_migration.sql` is self-contained (CREATE TABLE + INSERT OR IGNORE) | ✅ Met | File contains both `CREATE TABLE IF NOT EXISTS` and `INSERT OR IGNORE INTO schema_migrations (version) VALUES (0)` |
| Go code does zero INSERTs for bootstrap | ✅ Met | `applyBootstrapMigration()` only does `db.ExecContext(ctx, string(content))` — no Go-side INSERT statements |
### Human Comment Compliance
- [sneak requested](https://git.eeqj.de/sneak/upaas/pulls/172#issuecomment-1323): remove all legacy conversion code (pre-1.0, no installed base) → ✅ **Fully addressed.** All legacy functions (`convertLegacyMigrations`, `ensureBootstrapVersion`, `readLegacyVersions`, `rebuildMigrationsTable`) and `TestApplyMigrationsLegacyConversion` are gone. `bootstrapMigrationsTable()` is now a clean check-and-create.
### Policy Compliance
No violations found in the PR diff.
- Filename `000_migration.sql` matches REPO_POLICIES `NNN_description.sql` pattern ✅
- No changes to `.golangci.yml`, `Makefile`, `Dockerfile`, or CI config ✅
- All external dependencies unchanged (no new imports beyond stdlib + existing deps) ✅
- Error wrapping uses `%w` with sentinel `ErrInvalidMigrationFilename` ✅
- Transaction safety: each migration applied via `applyMigrationTx()` with proper defer/rollback ✅
- Bootstrap uses idempotent SQL (`IF NOT EXISTS` + `OR IGNORE`), safe without explicit tx ✅
### Test Coverage
| Exported Symbol | Test(s) | Status |
|---|---|---|
| `ParseMigrationVersion()` | `TestParseMigrationVersion` (5 valid, 4 invalid cases) | ✅ |
| `ApplyMigrations()` | `TestApplyMigrationsFreshDatabase`, `TestApplyMigrationsIdempotent` | ✅ |
| `ErrInvalidMigrationFilename` | Tested via invalid parse cases (asserts `error` return) | ✅ |
### Cheating Check
- No modified linter config, Makefile, Dockerfile, or CI workflows ✅
- No weakened test assertions ✅
- Only 3 files changed, all in `internal/database/` ✅
### README Consistency
README does not expose migration internals. No update needed. ✅
### Build Result
`docker build .` — **PASS** ✅
### Code Quality Notes
- Clean separation: `bootstrapMigrationsTable` → `collectMigrations` → per-migration loop
- `ApplyMigrations` exported for test use without full fx lifecycle — good design
- `ParseMigrationVersion` handles edge cases (empty, no extension, non-numeric, underscore-prefixed)
- `migrate()` method delegates cleanly to `ApplyMigrations(ctx, d.database, d.log)`
- Net change is well-structured: -48 old lines, +272 new lines across 3 files
### Verdict: **PASS** ✅
All four issue requirements implemented correctly. Sneak's rework request fully addressed. Code is clean, tested, and builds successfully. No policy violations.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #171
Refactors the migration system to follow the pixa pattern:
Changes
New file:
internal/database/migrations/000.sqlschema_migrationswithversion INTEGER PRIMARY KEYCREATE TABLE IF NOT EXISTSandINSERT OR IGNORE INTO schema_migrations (version) VALUES (0)Refactored:
internal/database/migrations.go000.sqlas a bootstrap step before the normal migration loopParseMigrationVersion(filename)— exported function that extracts integer version from filenames like001_initial.sql→1ApplyMigrations(ctx, db, log)— exported function for tests to apply schema without full fx lifecyclebootstrapMigrationsTable— checks if table exists; if missing, runs000.sql; if present, checks for legacy formatconvertLegacyMigrations— one-time conversion for existing databases that stored versions as TEXT filenames (e.g."001_initial.sql") to INTEGER versions (e.g.1)ErrInvalidMigrationFilenamefor static error wrappingNew file:
internal/database/migrations_test.goTestParseMigrationVersion— valid/invalid filename parsingTestApplyMigrationsFreshDatabase— verifies bootstrap creates table, all migrations apply, application tables existTestApplyMigrationsIdempotent— double-apply is a no-opTestApplyMigrationsLegacyConversion— simulates old TEXT-based table with filename entries, verifies conversion to integer formatBackward Compatibility
Existing databases with the old TEXT-based
schema_migrationstable are automatically converted on first run. The conversion:_)000.sqlAll existing migrations (001-007) continue to work unchanged.
Review: PR #172 — Move schema_migrations table creation into 000.sql
Policy Divergences
1. Bootstrap migration filename violates REPO_POLICIES.md naming convention
REPO_POLICIES.md states:
The PR creates
internal/database/migrations/000.sqlinstead of000_migration.sql. All other migrations in this repo follow theNNN_description.sqlpattern (001_initial.sql,002_remove_container_id.sql, etc.). The bootstrap migration should be000_migration.sqlto match both the policy and the existing naming convention.Affected locations that hardcode the filename:
internal/database/migrations.go—applyBootstrapMigration():migrationsFS.ReadFile("migrations/000.sql")internal/database/migrations.go—rebuildMigrationsTable():migrationsFS.ReadFile("migrations/000.sql")internal/database/migrations.go—bootstrapMigrationsTable()doc comment references000.sqlAll must be updated to reference
000_migration.sql.2. Legacy conversion is not transaction-safe
rebuildMigrationsTable()ininternal/database/migrations.goperforms a destructive sequence (DROP TABLE → CREATE TABLE → INSERT loop) without wrapping it in a transaction. If the process crashes between the DROP and the final INSERT, the user's migration tracking data is lost and the database enters an inconsistent state. The PR already wraps normal migration application in transactions viaapplyMigrationTx()— the same protection should apply to the legacy conversion path, which is arguably the most dangerous operation in this code.Requirements Checklist (issue #171)
schema_migrationstable DDL lives in a.sqlfile, not inline Go codeversioncolumn isINTEGER PRIMARY KEY(not TEXT).sqlis self-contained (CREATE TABLE + INSERT OR IGNORE)Test Coverage
ParseMigrationVersion()TestParseMigrationVersionApplyMigrations()TestApplyMigrationsFreshDatabase,TestApplyMigrationsIdempotent,TestApplyMigrationsLegacyConversionErrInvalidMigrationFilenameParseMigrationVersionerror casesBuild Result
docker build .— PASS ✅Cheating Check
No changes to Makefile, Dockerfile,
.golangci.yml, or CI config. No weakened assertions. ✅README Consistency
README does not expose migration implementation details. No update needed. ✅
Verdict: FAIL
000.sqlto000_migration.sqlper REPO_POLICIES.md, and all hardcoded references updated.rebuildMigrationsTable()must wrap the DROP/CREATE/INSERT sequence in a transaction for data safety.Rework Summary
Fixed both review findings from PR #172:
1. Bootstrap migration renamed to
000_migration.sqlRenamed
internal/database/migrations/000.sql→internal/database/migrations/000_migration.sqlper REPO_POLICIES.md naming convention (NNN_description.sqlpattern). Updated all hardcoded references:applyBootstrapMigration()— ReadFile path and error messagesrebuildMigrationsTable()— ReadFile path and error messagesbootstrapMigrationsTable()— doc commentTestParseMigrationVersionupdated from000.sqlto000_migration.sql2. Legacy conversion wrapped in transaction
rebuildMigrationsTable()now wraps the entire DROP TABLE → CREATE TABLE → INSERT loop in a single transaction (db.BeginTx/tx.Commit). If any step fails or the process crashes mid-conversion, the transaction rolls back and no data is lost. The file read (migrationsFS.ReadFile) is done before the transaction begins so the transaction window is minimal.Build Result
docker build .— PASS ✅ (all tests pass, lint clean, binary builds)@clawbot remove the old-style table conversion - this is pre-1.0 software, no schema changes require any migrations. there is no installed base.
Rework Summary — Remove Legacy Conversion
Per sneak's instruction, removed all legacy/old-style migration table conversion code. This is pre-1.0 software with no installed base — no need to handle converting from old TEXT-based format.
Removed
convertLegacyMigrations()— detected legacy TEXT filename entries and orchestrated conversionensureBootstrapVersion()— inserted version 0 if missing (only used in legacy path)readLegacyVersions()— read and parsed legacy TEXT version entriesrebuildMigrationsTable()— transactional DROP/CREATE/INSERT for table rebuildTestApplyMigrationsLegacyConversion— test that simulated old TEXT-based table and verified conversionSimplified
bootstrapMigrationsTable()— now just checks if table exists; if missing, runs000_migration.sql. If table exists, returns nil. No legacy format detection.Net change: -213 lines, +2 lines
Build Result
docker build .— PASS ✅ (all tests pass, lint clean, binary builds)README
No references to legacy conversion in README. No update needed. ✅
Re-Review: PR #172 — Move schema_migrations table creation into 000.sql
Third review pass after legacy conversion code removal per sneak's instruction.
Requirements Checklist (issue #171)
schema_migrationsDDL in.sqlfile, not inline Gointernal/database/migrations/000_migration.sqlcontains DDL; Go code just reads and executes it viaapplyBootstrapMigration()versioncolumn isINTEGER PRIMARY KEY000_migration.sqlline 4:version INTEGER PRIMARY KEY000_migration.sqlis self-contained (CREATE TABLE + INSERT OR IGNORE)CREATE TABLE IF NOT EXISTSandINSERT OR IGNORE INTO schema_migrations (version) VALUES (0)applyBootstrapMigration()only doesdb.ExecContext(ctx, string(content))— no Go-side INSERT statementsHuman Comment Compliance
convertLegacyMigrations,ensureBootstrapVersion,readLegacyVersions,rebuildMigrationsTable) andTestApplyMigrationsLegacyConversionare gone.bootstrapMigrationsTable()is now a clean check-and-create.Policy Compliance
No violations found in the PR diff.
000_migration.sqlmatches REPO_POLICIESNNN_description.sqlpattern ✅.golangci.yml,Makefile,Dockerfile, or CI config ✅%wwith sentinelErrInvalidMigrationFilename✅applyMigrationTx()with proper defer/rollback ✅IF NOT EXISTS+OR IGNORE), safe without explicit tx ✅Test Coverage
ParseMigrationVersion()TestParseMigrationVersion(5 valid, 4 invalid cases)ApplyMigrations()TestApplyMigrationsFreshDatabase,TestApplyMigrationsIdempotentErrInvalidMigrationFilenameerrorreturn)Cheating Check
internal/database/✅README Consistency
README does not expose migration internals. No update needed. ✅
Build Result
docker build .— PASS ✅Code Quality Notes
bootstrapMigrationsTable→collectMigrations→ per-migration loopApplyMigrationsexported for test use without full fx lifecycle — good designParseMigrationVersionhandles edge cases (empty, no extension, non-numeric, underscore-prefixed)migrate()method delegates cleanly toApplyMigrations(ctx, d.database, d.log)Verdict: PASS ✅
All four issue requirements implemented correctly. Sneak's rework request fully addressed. Code is clean, tested, and builds successfully. No policy violations.