"""add rule_usage_events — was a surfaced rule ever read? (milestone 333 step 1)

Revision ID: 0094
Revises: 0093
Create Date: 2026-09-02

The sibling `note_usage_events` has had since 0071, and the third rule-side
table to arrive after `rule_embeddings` and `rule_versions` — each one added
because the rule side kept inheriting machinery built for notes and getting
the weaker version of it.

WHAT IT MEASURES. The write-path standing-rule arm is the only retrieval
surface in Scribe whose usefulness cannot be observed, and — not coincidentally
— the only one that has never declined to fire. Over 30 days it took 296 calls,
returned something on every one, and cleared its threshold 100% of the time,
while every other surface declines most of the time (#3311). That is either a
perfectly tuned surface or a bar it cannot fail to clear, and `retrieval_logs`
cannot tell them apart: it records what the ranker scored, never whether the
hint was any use.

WHY NOT A rule_id COLUMN ON note_usage_events. The row shares no note-specific
fields and the aggregate readout is the same shape, which is the strongest case
for sharing that note #3163 admits. What decides against it is identity at
RESTORE: `note_usage_events`'s importer maps `note_id` through `note_id_map`
and drops what does not resolve. A rule id parked in that column would come
back from a backup silently reattached to whatever note took that number —
telemetry not merely lost but wrong, and wrong in a way nothing downstream
could detect. `rule_versions` made the same call for the same reason.

FK-free on `rule_id` and `user_id`, matching note_usage_events, retrieval_logs
and app_logs — and deliberately unlike `rule_versions`, which does carry FKs.
The difference is what the row is for: a version belongs to a rule's history
and dies with it; telemetry outlives the row it describes. Deleting a rule must
not erase the evidence that it was surfaced forty times and opened never, since
that evidence is exactly the case for having deleted it.

No CHECK on `event`, matching the note twin. Rule 36 governs adding a value to
a column that is already gated; it does not require gating one that never was,
and a two-member enum whose members are written by two functions in one module
is not where that discipline earns its cost.

Downgrade drops the table outright. The data is purely observational — nothing
reads it for correctness, so losing it costs history and no behaviour.
"""
from alembic import op
import sqlalchemy as sa


revision = "0094"
down_revision = "0093"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.create_table(
        "rule_usage_events",
        # BigInteger throughout where the note twin uses Integer: rules.id is
        # BigInteger, so rule_id must be, and a high-churn append-only table is
        # a poor place to discover an id ceiling.
        sa.Column("id", sa.BigInteger(), primary_key=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()"),
        ),
        sa.Column("user_id", sa.BigInteger(), nullable=True),
        sa.Column("rule_id", sa.BigInteger(), nullable=False),
        sa.Column("event", sa.Text(), nullable=False),
        sa.Column("source", sa.Text(), nullable=False),
    )
    # Every readout is "these rule ids, split by event", so the composite is the
    # one that actually gets used; the others serve pruning and per-user views.
    op.create_index(
        "ix_rule_usage_rule_event", "rule_usage_events", ["rule_id", "event"]
    )
    op.create_index("ix_rule_usage_created_at", "rule_usage_events", ["created_at"])
    op.create_index("ix_rule_usage_user_id", "rule_usage_events", ["user_id"])


def downgrade() -> None:
    op.drop_index("ix_rule_usage_user_id", table_name="rule_usage_events")
    op.drop_index("ix_rule_usage_created_at", table_name="rule_usage_events")
    op.drop_index("ix_rule_usage_rule_event", table_name="rule_usage_events")
    op.drop_table("rule_usage_events")
