Skip to content

Commit 73c8485

Browse files
committed
test: guard migration identity by full filename stem
1 parent 79c3889 commit 73c8485

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

docs/PIPELINE-MIGRATION.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,25 @@ surfaces disabled, and treats disappearance as `not-observed`, never closure.
1818
Failures preserve the previous validated release and emit a restricted
1919
failure report. Acquisition terms approval and publication approval remain
2020
separate human gates.
21+
22+
## Migration runner contract
23+
24+
`pipeline/scripts/maintenance/apply-migrations.py` discovers every `*.sql` file
25+
in the migration directory and sorts the paths lexically. The migration identity
26+
stored in `uec.schema_migrations.version` is the complete filename stem, not the
27+
numeric prefix. Consequently, `020_release_manifests.sql` and
28+
`020_suppression_aware_v2_history.sql` have distinct identities and are both
29+
applied in that lexical order. Migration files must not be renamed, edited,
30+
deleted, squashed, or resequenced after they have been applied; a new change
31+
gets a new filename.
32+
33+
The runner records the SHA-256 digest of each migration and refuses to execute
34+
an already-recorded identity when its file content has changed. Database setup
35+
and each migration run in separate transactions. A failed migration rolls back
36+
its SQL and ledger insert, while earlier successful migrations remain recorded
37+
so a subsequent run can retry and resume at the failed identity.
38+
39+
The database connection is retried up to ten times after an operational
40+
connection error, waiting one second between attempts. Connection retries do
41+
not alter migration ordering or checksum behavior. These rules are tested by
42+
`pipeline/tests/test_apply_migrations.py` without connecting to a database.

pipeline/tests/test_apply_migrations.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ def execute(self, query, params=None):
6363

6464

6565
class MigrationRunnerTests(unittest.TestCase):
66+
def test_existing_020_migrations_use_distinct_full_stem_identities(self):
67+
migration_dir = Path(__file__).parents[1] / "migrations"
68+
69+
files = MODULE.migration_files(migration_dir)
70+
names = [path.name for path in files]
71+
stems = [path.stem for path in files]
72+
73+
first = "020_release_manifests.sql"
74+
second = "020_suppression_aware_v2_history.sql"
75+
self.assertIn(first, names)
76+
self.assertIn(second, names)
77+
self.assertNotEqual(Path(first).stem, Path(second).stem)
78+
self.assertLess(stems.index(Path(first).stem), stems.index(Path(second).stem))
79+
6680
def test_failed_migration_is_not_recorded_and_retry_resumes(self):
6781
with tempfile.TemporaryDirectory() as directory:
6882
root = Path(directory)

0 commit comments

Comments
 (0)