From 6f5705fe0292ecdd74179eb118acb58369d8904e Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Fri, 7 Aug 2026 11:31:26 +0000 Subject: [PATCH] fix(P22): migration safety (REQ-137, F19, C-34) ---ci--- project: orca phase: 22 milestone: v0.12 status: execute ---/ci--- copyFile now atomic (temp + rename; was os.WriteFile which could leave a partial DB on crash). migrateDBSchema now opens with foreign_keys(ON) (was journal_mode only). REQ-137/F19. Build + tests green. The --accept-identity-migration gate is enforced in the upgrade CLI (P07 password removal; documented in the migration guide). --- internal/migration/migrate.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/migration/migrate.go b/internal/migration/migrate.go index 0bbb0c3..388fbe1 100644 --- a/internal/migration/migrate.go +++ b/internal/migration/migrate.go @@ -189,7 +189,7 @@ func alreadyMigrated(dir string) bool { // added it; v0.11 is single-namespace-per-DB). This mirrors the // internal/store/migrate.go pattern but operates on a copied DB. func migrateDBSchema(dbPath string) error { - db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)") + db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(ON)") if err != nil { return fmt.Errorf("open %s: %w", dbPath, err) } @@ -273,6 +273,8 @@ func fileExists(path string) bool { } // copyFile copies src to dst preserving the file mode. +// copyFile copies src to dst atomically (temp + rename). REQ-137/F19: +// a crash mid-copy must not leave a partial DB file. func copyFile(src, dst string) error { data, err := os.ReadFile(src) if err != nil { @@ -282,7 +284,11 @@ func copyFile(src, dst string) error { if err != nil { return err } - return os.WriteFile(dst, data, info.Mode().Perm()) + tmp := dst + ".tmp" + if err := os.WriteFile(tmp, data, info.Mode().Perm()); err != nil { + return fmt.Errorf("copyFile: write tmp: %w", err) + } + return os.Rename(tmp, dst) } // GetCAImporter returns the package-level CA importer (set via