"""rule_usage_events carry an outcome, not just a read (#4212, milestone 419) Revision ID: 0106 Revises: 0105 Create Date: 2026-09-20 Milestone 419's first step. `rule_usage_events` can say a rule was SURFACED and that it was PULLED. It cannot say what happened next, so these two sessions leave identical telemetry: - a rule surfaced, opened, and followed; - a rule surfaced, opened, and silently ignored. The second is the more urgent by a distance, and it is the one the readout cannot name. That is the whole of what this milestone is about, measured on a session where three of seven misses were caught by the operator and none by the system. TWO NEW EVENT VALUES, AND NO CHECK MIGRATION. `event` was created in 0094 as plain `sa.Text()` with no constraint — checked in the migration itself, not assumed from the model — so `applied` and `departed` join `surfaced` and `pulled` without a DROP/ADD pair. Rule 36 governs CHECK-whitelisted columns and this is not one; noted explicitly because the next reader will reach for rule 36 here, and should be able to see in one place why it does not bite. THE THIRD STATE IS DERIVED, AND THAT IS NOT A SHORTCUT. Read-and-silently- unchanged is the absence of an outcome, and it has to be: an agent that knew it was ignoring a rule would not be ignoring it. There is no honest way to ask for that event, so nothing here tries. `applied` and `departed` are reported; the third state is what is left over when a rule was pulled and neither arrived. A schema that offered an `ignored` value would collect nothing and read as though it had measured something, which is the #3311 failure — a statistic that cannot vary being mistaken for a finding. `detail` CARRIES THE WHY OF A DEPARTURE, and is the reason this is a column rather than two more bare event strings. A departure without its reason is indistinguishable from a miss when someone reads the table back, so the two states the milestone wants to tell apart would collapse again one layer down. Nullable because `applied` needs no argument — following a rule is the unremarkable case, and demanding prose for it would make the cheap event expensive and stop it being recorded at all. NO NEW INDEX. Every outcome readout starts from a set of rule ids and narrows by event, which is exactly `ix_rule_usage_rule_event` (rule_id, event) from 0094. Adding a `detail` index would serve no query anyone has — the column is read, never filtered on. """ import sqlalchemy as sa from alembic import op revision = "0106" down_revision = "0105" branch_labels = None depends_on = None def upgrade() -> None: op.add_column( "rule_usage_events", sa.Column("detail", sa.Text(), nullable=True), ) def downgrade() -> None: op.drop_column("rule_usage_events", "detail")