"""service_seen — the learned roster that makes a stopped part observable. Milestone 365. Nothing in FabledCurator knew what was SUPPOSED to be running: `celery inspect` reports the workers that answer, so a dead worker was a shorter list rather than a red light, and the only surface that could tell an operator otherwise was Portainer. This table is the memory that turns an absence into something the app can see. Keyed on the queue set for a celery role and on agent_id for the GPU agent — NOT on the celery worker name, which here is `celery@` and is minted fresh on every deploy. See the model docstring for why that choice is the whole design. ## First migration on the collapsed baseline 0089 is the single generated baseline that replaced revisions 0001..0089 (milestone 328). This is the first revision written on top of it, so it is also the first evidence that the chain steps forward from the collapse rather than merely reproducing the schema — which nothing had demonstrated yet. An existing install is at 0089 because it ran the real 0089; a fresh one is at 0089 because it ran the baseline. Both arrive here identically, which was the property the collapse was designed around. Revision ID: 0090 Revises: 0089 Create Date: 2026-09-02 """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0090" down_revision: Union[str, None] = "0089" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.create_table( "service_seen", sa.Column("key", sa.String(length=128), nullable=False), sa.Column("kind", sa.String(length=16), nullable=False), sa.Column("display_name", sa.String(length=64), nullable=False), sa.Column( "first_seen_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "last_seen_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("details", sa.JSON(), nullable=False), sa.PrimaryKeyConstraint("key", name=op.f("pk_service_seen")), ) # No secondary indexes, deliberately: one row per moving part means every # read is a handful of rows and an index would be write cost buying # nothing (#3301 removed seven of exactly that shape). def downgrade() -> None: op.drop_table("service_seen")