Adds projection_retired_at, projection_retired_reason and derived_from_commit. Retirement cannot be expressed by deletion — hub-native records reference workplans with RESTRICT — nor by `status`, since an archived workplan was closed by its owner while a retired one is simply no longer derived by the forge. Those are different facts and must not share a field. Discovered while applying this: central's schema is two revisions behind the code it runs. review_contracts does not exist there although its migration ships in the serving image, and there is no migration mechanism at all — bare uvicorn CMD, no chart-declared job. Recorded as STATE-WP-0083-T07, which now blocks T03. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""workplan projection retirement (STATE-WP-0083-T03)
|
|
|
|
A forge-derived projection must be able to say that a record no longer derives
|
|
from the forge. It cannot say so by deleting the row: progress events, tasks,
|
|
decisions and review contracts reference workplans with ON DELETE RESTRICT, and
|
|
those hub-native records must survive (ADR-010 decision 4, ADR-012 decision 7 as
|
|
amended 2026-08-25).
|
|
|
|
Nor can it say so through `status`. A workplan that is `archived` was closed by
|
|
its owner; one that no longer derives is a different fact, and overloading the
|
|
same field would destroy the distinction exactly where it matters.
|
|
|
|
Revision ID: d1a2b3c4e5f6
|
|
Revises: c9e5a1b3d7f2
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "d1a2b3c4e5f6"
|
|
down_revision = "c9e5a1b3d7f2"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column(
|
|
"workplans",
|
|
sa.Column("projection_retired_at", sa.DateTime(timezone=True), nullable=True),
|
|
)
|
|
op.add_column(
|
|
"workplans",
|
|
sa.Column("projection_retired_reason", sa.String(length=255), nullable=True),
|
|
)
|
|
op.add_column(
|
|
"workplans",
|
|
sa.Column("derived_from_commit", sa.String(length=40), nullable=True),
|
|
)
|
|
op.create_index(
|
|
"ix_workplans_projection_retired_at",
|
|
"workplans",
|
|
["projection_retired_at"],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_workplans_projection_retired_at", table_name="workplans")
|
|
op.drop_column("workplans", "derived_from_commit")
|
|
op.drop_column("workplans", "projection_retired_reason")
|
|
op.drop_column("workplans", "projection_retired_at")
|