Skip to content

Convert the last five database gates by hand, and record why three stay (ER66 batch 6/6) - #372

Merged
haksungjang merged 1 commit into
mainfrom
er66-batch6-by-hand
Sep 4, 2026
Merged

Convert the last five database gates by hand, and record why three stay (ER66 batch 6/6)#372
haksungjang merged 1 commit into
mainfrom
er66-batch6-by-hand

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

The last of the six. Five modules converted by hand, three exempted with their
reasons, and two things the series turned up recorded where they will be found.

The five

The codemod declined all five, which is what it is for - each had its own shape.
test_component_approval_service.py is the interesting one: it had already built
a session-level _ALEMBIC_RAN flag so the migration ran once per session, which
is exactly what the shared helper now does. Converting it removed a duplicate
mechanism, not just a duplicate gate.

The three that stay, and why

An exemption means "this module may keep its own arrangement", and the list now
says why for each, because the reason is what tells the next person whether a
file can come off it.

Testing the tool itself - gating on the migration would delete the subject:
test_alembic_upgrade.py asserts that alembic upgrade head succeeds and that
alembic current then reports head; test_scan_dependency_fingerprint_migration.py
asserts the shape revision 0070 leaves and that its downgrade() fails loudly.
unit/core/test_config_database_url.py was already here for the same reason.

Migrating a different database - test_app_role_grant_matrix.py creates a
throwaway database and runs alembic against that, with DATABASE_URL repointed
in a subprocess, to test role-first-then-migrate ordering. The helper migrates
the configured database, which is not the one under test.

Worth distinguishing from a third thing that is not an exemption: needing the
database in a particular state. A module can arrange that itself, and
test_bootstrap_from_empty.py does - it migrates to head through the helper and
then truncates. Its name suggests otherwise, which is why each of these was read
rather than classified by name; the file whose name mentions emptiness needs
head, and the file whose name says nothing about databases builds its own.

What the caching cost

Every module used to run its own alembic upgrade head, so a module that
damaged the schema was silently repaired by the next one's migration. Nobody
designed that, and nothing depended on it - but it is gone now, and the symptom
of losing it would be a failure in an unrelated module much later, with the cause
far behind. A comment would not reach the person who writes such a test, so the
session asserts that the revision is where the migration left it. One query, and
it cannot name the guilty test; it says only that the schema moved. Verified by
planting a test that moves the pointer and does not restore it: the run reports
the schema revision changed during this run: 0083 -> 0001.

Both current mutators do restore: test_health_ready.py rewinds the pointer and
puts it back in finally, and test_backup_task_round_trip.py restores from its
own dump.

The guard's blind spot

conftest.py's Redis check reads REDIS_URL at session start; core/ratelimit.py
binds its storage at import. Anything changing the variable in between would leave
them watching different indexes. Nothing does today, for two independent reasons -
all seven writes are function-scoped monkeypatch.setenv, and every value names a
host that does not exist - and the docstring says how to re-check both, since
either reason could lapse on its own.

The series

193 modules, converted in five mechanical batches of 43, 33, 33, 38 and 38, plus
these five. Every batch matched its declared count and produced no conversion
shape outside the six recorded in batch 1. The declared-count check stopped a run
three times, and all three were arithmetic on my side rather than the codemod: a
file counted from the wrong directory, and twice wc -l on a list with no
trailing newline. It never once caught the conversion being wrong, which is worth
saying plainly - the weaker input in this work was the person counting.

Five modules the codemod declined are converted: they had their own shapes,
including one that had built its own once-per-session cache of the migration.
Three keep their own gate on purpose - two because running the migration is
what they test, one because it migrates a database it creates itself - and
the exemption list now carries that reason for each.

Also adds what the caching cost. Every module used to run its own migration,
so a module that damaged the schema was silently repaired by the next one.
Nobody designed that and nothing depended on it, but it is gone, and the
symptom would be an unrelated failure much later. The session now checks that
the revision is where the migration left it.

The Redis guard's docstring records that it reads REDIS_URL at session start
while the limiter binds at import, why nothing currently exploits that gap,
and how to check whether that is still true.
@haksungjang
haksungjang merged commit 75be838 into main Sep 4, 2026
24 checks passed
@haksungjang
haksungjang deleted the er66-batch6-by-hand branch September 4, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant