"""D2 — a credit limit stops being rounded to a whole number `chat_credit_quota.daily_credit_limit` is `bigint` in the database and `DECIMAL(12, 2)` on the model. PostgreSQL resolves that disagreement by rounding, silently, on write: INSERT ... VALUES (0.60::numeric(12,2)) -> stored: 1 INSERT ... VALUES (10.75::numeric(12,2)) -> stored: 11 INSERT ... VALUES (10.25::numeric(12,2)) -> stored: 10 So a tenant admin setting a limit of 0.60 credits gets 1 — a 67% overshoot — and nothing anywhere reports it. Whether that has bitten depends on whether anyone has entered a fraction, which is not a property worth relying on. Widening, so no value is at risk: every bigint currently stored is representable in `numeric(12, 2)`. `credits_used_today` and `total_credits_ever` are left as `bigint` deliberately — they count tokens, which are whole. The reverse casts back to `bigint` and therefore *does* round. That is the honest reverse of a widening: going back to a narrower type cannot preserve what the wider one held. It is written that way rather than left unimplemented so the migration can actually be reversed, but rolling this back after fractional limits have been set will round them. Found by `alembic revision --autogenerate`, which had never been run against this schema, and which after the model-registry fix reports this as the single remaining difference between the models and the database. Revision ID: d2_0_credit_limit_precision Revises: d1_0_plans_and_entitlements """ import sqlalchemy as sa from alembic import op revision = "d2_0_credit_limit_precision" down_revision = "d1_0_plans_and_entitlements" branch_labels = None depends_on = None def upgrade() -> None: op.alter_column( "chat_credit_quota", "daily_credit_limit", existing_type=sa.BigInteger(), type_=sa.DECIMAL(precision=12, scale=2), existing_nullable=False, postgresql_using="daily_credit_limit::numeric(12,2)", ) def downgrade() -> None: op.alter_column( "chat_credit_quota", "daily_credit_limit", existing_type=sa.DECIMAL(precision=12, scale=2), type_=sa.BigInteger(), existing_nullable=False, postgresql_using="round(daily_credit_limit)::bigint", )