Milestone/step labels, record ids, the rescoped-rule date and the 296-call measurement go; each header states what the change protects. "rule 36" citations become the DROP + ADD practice they named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
3.4 KiB
Python
78 lines
3.4 KiB
Python
"""a note can carry its own check — verify_with, expires_when, verified_at
|
|
|
|
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 (gated at the service): 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. 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 a real
|
|
# plan says otherwise, add it then 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")
|