"""Cache the interactive tray app version on the endpoint state. Adds endpointstates.trayversion: the file version of the ShopDB endpoint tray app, reported by the agent alongside its own agentversion. The tray and the agent service ship and update independently (they are separate exes and can be pushed apart), so the tray version is stored as its own field rather than assumed equal to the agent (AGENT-CONTRACT.md 2). Nullable so an endpoint with no tray installed, or an older agent that predates the field, reads as "unknown" rather than an empty string. Idempotent: the column-add checks the live schema first, so a db.create_all() site (which built the column from the model already) and a re-run both stay clean on strict MySQL 8. """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = 'endpoints0011trayversion' down_revision = 'endpoints0010renamepolicy' branch_labels = None depends_on = None def _has_column(table_name, column_name): from sqlalchemy import inspect inspector = inspect(op.get_bind()) if not inspector.has_table(table_name): return False return any(col['name'] == column_name for col in inspector.get_columns(table_name)) def upgrade(): if _has_column('endpointstates', 'trayversion'): return op.add_column( 'endpointstates', sa.Column('trayversion', sa.String(length=40), nullable=True)) def downgrade(): if not _has_column('endpointstates', 'trayversion'): return op.drop_column('endpointstates', 'trayversion')