Files
GTI_GERENCIADOR_FISCAL/central/alembic/versions/0019_fix_timestamp_defaults.py
T
gtiandClaude Opus 5.5 a11209d123 fix: make the migration chain runnable from an empty database
0001 called Base.metadata.create_all(), building the schema from the
current models, so on a fresh PostgreSQL 0002 collided on
datasets.raw_sha256 and `alembic upgrade head` could never run from zero.

0001 is now an explicit, frozen baseline: the seven base tables exactly as
the real history created them, before 0002/0006 extended them. Databases
already past 0001 never re-run it. Proof: a fresh `upgrade head` produces a
schema identical (pg_dump --schema-only) to the long-lived dev database.

0019 repairs a latent bug found by that comparison: 0006 used
server_default="now()" as a string, which PostgreSQL froze at the instant
the column was created, so every Agent and Job got the same created_at.
It also adds three indexes the models declare but no migration created.

central/tests/test_migration_baseline.py guards (via AST) against
migrations importing the models or using string timestamp defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 00:30:51 -03:00

44 lines
1.7 KiB
Python

"""Fix frozen timestamp defaults from 0006 and add model indexes never migrated.
0006 declared ``server_default="now()"`` as a plain string. SQLAlchemy renders
that as the literal ``'now()'``, which PostgreSQL casts to a timestamp once,
when the column is created — every later row got that same fixed instant
instead of the insert time (agents.created_at/updated_at, jobs.created_at).
The models rely on the server default, so Jobs and Agents registered after
0006 all carried the migration's timestamp. Existing rows are left as they
are: their real creation time is not recoverable.
The models also declare ``index=True`` on three columns that no migration
ever indexed.
Revision ID: 0019_fix_timestamp_defaults
Revises: 0018_blocker_installation
"""
from alembic import op
import sqlalchemy as sa
revision = "0019_fix_timestamp_defaults"
down_revision = "0018_blocker_installation"
branch_labels = None
depends_on = None
_TIMESTAMP_COLUMNS = (("agents", "created_at"), ("agents", "updated_at"), ("jobs", "created_at"))
_MISSING_INDEXES = (
("ix_kb_observations_tenant_id", "kb_observations", "tenant_id"),
("ix_kb_observations_company_key", "kb_observations", "company_key"),
("ix_tenant_product_context_company_key", "tenant_product_context", "company_key"),
)
def upgrade():
for table, column in _TIMESTAMP_COLUMNS:
op.alter_column(table, column, server_default=sa.func.now(), existing_type=sa.DateTime(timezone=True), existing_nullable=False)
for name, table, column in _MISSING_INDEXES:
op.create_index(name, table, [column])
def downgrade():
# The frozen-timestamp defaults are a bug and are deliberately not restored.
for name, table, _column in reversed(_MISSING_INDEXES):
op.drop_index(name, table_name=table)