From 9e1da4af3da8d4403b2c23e757589b9ae7c5f36c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 20:46:36 -0400 Subject: [PATCH] docs(6207): migrations 0088 and 0090-0094 state their rationale in terms of the data 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 --- .../0088_rule_trigger_tier_tags_relations.py | 7 +++---- alembic/versions/0090_rule_verification.py | 10 ++++------ alembic/versions/0091_task_kind_spike.py | 5 ++--- alembic/versions/0092_note_verification.py | 11 ++++------- alembic/versions/0093_rule_versions.py | 13 +++++-------- alembic/versions/0094_rule_usage_events.py | 17 +++++++---------- 6 files changed, 25 insertions(+), 38 deletions(-) diff --git a/alembic/versions/0088_rule_trigger_tier_tags_relations.py b/alembic/versions/0088_rule_trigger_tier_tags_relations.py index 502f147f..5644d746 100644 --- a/alembic/versions/0088_rule_trigger_tier_tags_relations.py +++ b/alembic/versions/0088_rule_trigger_tier_tags_relations.py @@ -1,5 +1,4 @@ -"""rules gain a trigger, a tier, canon tags and typed edges (milestone 307 -step 3, decision note 3026) +"""rules gain a trigger, a tier, canon tags and typed edges Revision ID: 0088 Revises: 0087 @@ -31,8 +30,8 @@ down_revision = "0087" branch_labels = None depends_on = None -# Kept in one place so upgrade and the CHECK agree by construction (rule 36: -# a whitelisted value means DROP + ADD CONSTRAINT in the same migration — +# Kept in one place so upgrade and the CHECK agree by construction (a +# whitelisted value means DROP + ADD CONSTRAINT in the same migration — # there is no prior constraint here, so the pair is created together). _TIERS = ("always_on", "conditional") _RELATION_KINDS = ("co_surfaces", "overrides", "elaborates") diff --git a/alembic/versions/0090_rule_verification.py b/alembic/versions/0090_rule_verification.py index 0ba64a98..d148dbba 100644 --- a/alembic/versions/0090_rule_verification.py +++ b/alembic/versions/0090_rule_verification.py @@ -1,5 +1,4 @@ """a rule can carry its own check — verify_with, expires_when, verified_at -(milestone 312 step 1) Revision ID: 0090 Revises: 0089 @@ -11,10 +10,9 @@ know they did. A CONSTRAINT is a fact about someone else's software: a runner's shell, a bot's config, a tool that exists. Nobody is present when that goes false. -Milestone 307's rulebook audit found nine stale sites. Every one was a -constraint; not one norm had rotted. One of them had been telling every -session to skip database-backed tests for weeks while the integration lane -sat green in the workflow. +What goes stale in a rulebook is the constraints, not the norms — and a +stale constraint keeps instructing every session that loads it, long after +the world it described has moved. Three nullable columns, so a rule can say how to check itself: @@ -31,7 +29,7 @@ none of them. 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." That signal only works if the field stays empty wherever it belongs empty. -No CHECK constraint is involved, so rule 36 does not apply here. Nothing is +No CHECK constraint is involved. Nothing is backfilled: a migration cannot invent a check any more than 0088 could invent a trigger. """ diff --git a/alembic/versions/0091_task_kind_spike.py b/alembic/versions/0091_task_kind_spike.py index 5b03374c..82c6bfb6 100644 --- a/alembic/versions/0091_task_kind_spike.py +++ b/alembic/versions/0091_task_kind_spike.py @@ -1,5 +1,4 @@ """task_kind gains 'spike' — the investigation, not the change -(milestone 312 step 5) Revision ID: 0091 Revises: 0090 @@ -12,12 +11,12 @@ end of it. "Find out whether the runner can be given a bash shell" is not work, and filing it as work makes a finished investigation look like an abandoned change. -It is the record a failed check asks for. Milestone 312 gave rules a +It is the record a failed check asks for. Rules carry a `verify_with`; when one of those fails, the rule is wrong and the next move is often to go and find out what replaced it. `notes.arose_from_id` already exists (0065), so that constraint -> spike link needs no further schema. -Rule 36: `task_kind` is gated by a CHECK whitelist, so the value and the +`task_kind` is gated by a CHECK whitelist, so the value and the widened constraint land in the SAME migration — DROP then ADD, exactly as 0065 did when it introduced 'issue'. Adding the value and constraining it later leaves a window where the database accepts anything. diff --git a/alembic/versions/0092_note_verification.py b/alembic/versions/0092_note_verification.py index 52f73d1e..e98b5c81 100644 --- a/alembic/versions/0092_note_verification.py +++ b/alembic/versions/0092_note_verification.py @@ -1,5 +1,4 @@ """a note can carry its own check — verify_with, expires_when, verified_at -(milestone 317 step 1) Revision ID: 0092 Revises: 0091 @@ -32,8 +31,7 @@ Three nullable columns: 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 +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 @@ -44,7 +42,7 @@ 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 +No CHECK constraint is involved. Nothing is backfilled: a migration cannot invent a check. """ import sqlalchemy as sa @@ -69,9 +67,8 @@ def upgrade() -> None: # # 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. + # 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: diff --git a/alembic/versions/0093_rule_versions.py b/alembic/versions/0093_rule_versions.py index 10401961..9f45c464 100644 --- a/alembic/versions/0093_rule_versions.py +++ b/alembic/versions/0093_rule_versions.py @@ -1,4 +1,4 @@ -"""rules gain an edit history — rule_versions (milestone 323 step 1) +"""rules gain an edit history — rule_versions Revision ID: 0093 Revises: 0092 @@ -7,12 +7,9 @@ Create Date: 2026-08-29 The sibling `note_versions` has had for a long time. A note's every meaningful edit is snapshotted, and the design-system note calls that history "the changelog". A RULE — which binds behaviour on every session that loads it — -had nothing: an edit destroyed what it used to say, with no record anywhere. - -Rescoping rule 79 on 2026-08-29 is what surfaced it. The superseded statement -had to be hand-copied into a task log to survive the edit (#3237), which is -not a process, it is a person remembering. The more consequential record had -the weaker protection. +had nothing: an edit destroyed what it used to say, with no record anywhere, +and keeping a superseded statement meant a person remembering to copy it +somewhere. The more consequential record had the weaker protection. Three things are deliberately NOT copied from note_versions, and each is a guard that exists there for a reason that does not hold here: @@ -31,7 +28,7 @@ guard that exists there for a reason that does not hold here: CASCADE: deleting a user must not erase the history of the rules they edited. The edit still happened and the rule still binds because of it. -No CHECK constraint, so rule 36 does not apply. Nothing is backfilled — a +No CHECK constraint is involved. Nothing is backfilled — a migration cannot invent the text a rule used to have, and inventing "the current text, as of now" would be worse than an empty history, because it would look like a record of an edit that never occurred. diff --git a/alembic/versions/0094_rule_usage_events.py b/alembic/versions/0094_rule_usage_events.py index 365650d6..028eca5d 100644 --- a/alembic/versions/0094_rule_usage_events.py +++ b/alembic/versions/0094_rule_usage_events.py @@ -1,4 +1,4 @@ -"""add rule_usage_events — was a surfaced rule ever read? (milestone 333 step 1) +"""add rule_usage_events — was a surfaced rule ever read? Revision ID: 0094 Revises: 0093 @@ -9,18 +9,15 @@ 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` +WHAT IT MEASURES. A standing-rule arm that returns something on every call +and clears its threshold every time 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 +for sharing there is. 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 — @@ -34,8 +31,8 @@ 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, +No CHECK on `event`, matching the note twin. DROP + ADD 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.