"""a note can carry its own check — verify_with, expires_when, verified_at
(milestone 317 step 1)

Revision ID: 0092
Revises: 0091
Create Date: 2026-08-28

The sibling of 0090, which gave rules the same three columns. Same
distinction, one table over:

A NORM is a decision — no truth value, and it changes only when its author
changes it, which they know they did. A CONSTRAINT is a fact about someone
else's software, and nobody is present when it goes false.

Notes hold far more constraints than rules do, and hold them for longer. A
cross-project reference note asserting what a signing service does on a
duplicate upload, or how a forge numbers its CI runs, is believed by every
project that reads it, and there is nothing in the record that says when
anyone last looked. `note_supersessions` only fires once a human has read
the note, disagreed, and written the correction — which is the case where
the note was already believed.

Three nullable columns:

- `verify_with` — how to tell whether this is still true. A command, a path,
  a URL, a query. Prose is allowed; something runnable is better.
- `expires_when` — the STATE under which it stops being true. Deliberately
  not a date: constraints do not expire on a schedule, they expire when the
  world underneath them moves.
- `verified_at` — when the check last passed. NULL means never checked, and
  sorts FIRST in the sweep: unexamined outranks examined-long-ago.

WHICH ROWS THESE ARE FOR. `notes` is one table holding notes, tasks,
snippets and processes, so these columns land on all of them. Only non-task,
non-snippet records are OFFERED them (milestone 317 decisions 1 and 2, gated
at the service in step 2): a task's decay is its status, and a snippet
already carries a richer, location-aware verdict in `data.verification`. The
columns exist on the other rows and stay null there; a gate that lives in
the schema would have meant a partial index or a CHECK across three columns
to express something the write path can say in two lines.

All three optional, because most notes should set none of them — the whole
value of the sweep is that its output is short. A null `verify_with` is not
an omission; it is the honest marker of "this one is a decision, and there
is nothing to go and check."

No CHECK constraint is involved, so rule 36 does not apply. Nothing is
backfilled: a migration cannot invent a check.
"""
import sqlalchemy as sa
from alembic import op

revision = "0092"
down_revision = "0091"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.add_column("notes", sa.Column("verify_with", sa.Text(), nullable=True))
    op.add_column("notes", sa.Column("expires_when", sa.Text(), nullable=True))
    op.add_column(
        "notes",
        sa.Column("verified_at", sa.DateTime(timezone=True), nullable=True),
    )
    # No index, for 0090's reason — the sweep runs when a human asks, never on
    # a request path — but the margin is thinner here and worth naming. `rules`
    # is hundreds of rows; `notes` is thousands and grows with every session.
    #
    # Still a sequential scan's job at this size, and an index on
    # (verified_at) filtered to `verify_with IS NOT NULL` would be maintained
    # on every note write to serve one operator-initiated query. If step 3's
    # live acceptance measures otherwise, add it there against a real plan
    # rather than guessing here.


def downgrade() -> None:
    op.drop_column("notes", "verified_at")
    op.drop_column("notes", "expires_when")
    op.drop_column("notes", "verify_with")
