Files
temp/alembic/versions/a5_003_spec_aligned_actions.py

351 lines
14 KiB
Python

"""Replace actions_v3 with spec-aligned actions table.
This migration drops the old actions_v3 table (ULID PK) and creates a new
actions table using the namespaced_name as the primary key per spec. It also
creates the action_invariants and action_arguments child tables.
The lifecycle_plans table is dropped first (FK dependency) and recreated by
a5_004.
Revision ID: a5_003_spec_aligned_actions
Revises: a5_002_lifecycle_plans
Create Date: 2026-02-13 00:00:00
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "a5_003_spec_aligned_actions"
down_revision: str | Sequence[str] | None = "a5_002_lifecycle_plans"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Drop legacy tables and create spec-aligned actions + child tables."""
# --- Drop legacy tables (FK order: lifecycle_plans first, then actions_v3) ---
op.drop_index("ix_lifecycle_plans_action", table_name="lifecycle_plans")
op.drop_index("ix_lifecycle_plans_created", table_name="lifecycle_plans")
op.drop_index("ix_lifecycle_plans_root", table_name="lifecycle_plans")
op.drop_index("ix_lifecycle_plans_parent", table_name="lifecycle_plans")
op.drop_index("ix_lifecycle_plans_state", table_name="lifecycle_plans")
op.drop_index("ix_lifecycle_plans_phase", table_name="lifecycle_plans")
op.drop_table("lifecycle_plans")
op.drop_index("ix_actions_v3_short_name", table_name="actions_v3")
op.drop_index("ix_actions_v3_state", table_name="actions_v3")
op.drop_index("ix_actions_v3_namespace", table_name="actions_v3")
op.drop_table("actions_v3")
# --- Create spec-aligned actions table (namespaced_name PK) ---
op.create_table(
"actions",
# Primary key: the full namespaced name (e.g., "local/code-review")
sa.Column("namespaced_name", sa.String(255), nullable=False),
# Extracted components for filtering
sa.Column("namespace", sa.String(100), nullable=False),
sa.Column("name", sa.String(150), nullable=False),
# Descriptions
sa.Column("description", sa.Text(), nullable=False),
sa.Column("long_description", sa.Text(), nullable=True),
# Definition of done - explicit testable completion criteria
sa.Column("definition_of_done", sa.Text(), nullable=False),
# Actor references (namespaced names)
sa.Column("strategy_actor", sa.String(255), nullable=False),
sa.Column("execution_actor", sa.String(255), nullable=False),
sa.Column("review_actor", sa.String(255), nullable=True),
sa.Column("apply_actor", sa.String(255), nullable=True),
sa.Column("estimation_actor", sa.String(255), nullable=True),
sa.Column("invariant_actor", sa.String(255), nullable=True),
# Behavior flags
sa.Column("automation_profile", sa.String(255), nullable=True),
sa.Column(
"reusable",
sa.Boolean(),
nullable=False,
server_default=sa.text("1"),
),
sa.Column(
"read_only",
sa.Boolean(),
nullable=False,
server_default=sa.text("0"),
),
# Inputs schema - JSON Schema dict for validation
sa.Column("inputs_schema_json", sa.Text(), nullable=True),
# State management
sa.Column(
"state",
sa.String(20),
nullable=False,
server_default="available",
),
# Metadata
sa.Column("created_by", sa.String(255), nullable=True),
sa.Column("tags_json", sa.Text(), nullable=False, server_default="[]"),
# Timestamps (ISO-8601 strings for portability)
sa.Column("created_at", sa.String(30), nullable=False),
sa.Column("updated_at", sa.String(30), nullable=False),
# Constraints
sa.PrimaryKeyConstraint("namespaced_name"),
sa.CheckConstraint(
"state IN ('available', 'archived')",
name="ck_actions_state",
),
)
# Indices for common queries
op.create_index("ix_actions_namespace", "actions", ["namespace"], unique=False)
op.create_index("ix_actions_state", "actions", ["state"], unique=False)
op.create_index("ix_actions_name", "actions", ["name"], unique=False)
# --- Create action_invariants table ---
op.create_table(
"action_invariants",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column(
"action_name",
sa.String(255),
sa.ForeignKey(
"actions.namespaced_name",
ondelete="CASCADE",
name="fk_action_invariants_action",
),
nullable=False,
),
sa.Column("invariant_text", sa.Text(), nullable=False),
sa.Column("position", sa.Integer(), nullable=False),
sa.Column("created_at", sa.String(30), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
"ix_action_invariants_action",
"action_invariants",
["action_name"],
unique=False,
)
op.create_index(
"ix_action_invariants_position",
"action_invariants",
["action_name", "position"],
unique=False,
)
# --- Create action_arguments table ---
op.create_table(
"action_arguments",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column(
"action_name",
sa.String(255),
sa.ForeignKey(
"actions.namespaced_name",
ondelete="CASCADE",
name="fk_action_arguments_action",
),
nullable=False,
),
sa.Column("name", sa.String(100), nullable=False),
sa.Column(
"arg_type",
sa.String(20),
nullable=False,
server_default="string",
),
sa.Column(
"requirement",
sa.String(20),
nullable=False,
server_default="required",
),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("default_value_json", sa.Text(), nullable=True),
sa.Column("min_value", sa.Float(), nullable=True),
sa.Column("max_value", sa.Float(), nullable=True),
sa.Column("validation_pattern", sa.String(500), nullable=True),
sa.Column("position", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("action_name", "name", name="uq_action_arguments_name"),
sa.CheckConstraint(
"arg_type IN ('string', 'integer', 'float', 'boolean', 'list')",
name="ck_action_arguments_type",
),
sa.CheckConstraint(
"requirement IN ('required', 'optional')",
name="ck_action_arguments_requirement",
),
)
op.create_index(
"ix_action_arguments_action",
"action_arguments",
["action_name"],
unique=False,
)
op.create_index(
"ix_action_arguments_position",
"action_arguments",
["action_name", "position"],
unique=False,
)
def downgrade() -> None:
"""Drop spec-aligned tables and restore legacy tables."""
# Drop new tables
op.drop_index("ix_action_arguments_position", table_name="action_arguments")
op.drop_index("ix_action_arguments_action", table_name="action_arguments")
op.drop_table("action_arguments")
op.drop_index("ix_action_invariants_position", table_name="action_invariants")
op.drop_index("ix_action_invariants_action", table_name="action_invariants")
op.drop_table("action_invariants")
op.drop_index("ix_actions_name", table_name="actions")
op.drop_index("ix_actions_state", table_name="actions")
op.drop_index("ix_actions_namespace", table_name="actions")
op.drop_table("actions")
# Restore legacy actions_v3 table
op.create_table(
"actions_v3",
sa.Column("action_id", sa.String(26), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("namespace", sa.String(100), nullable=False),
sa.Column("short_name", sa.String(150), nullable=False),
sa.Column("short_description", sa.Text(), nullable=True),
sa.Column("long_description", sa.Text(), nullable=True),
sa.Column("definition_of_done", sa.Text(), nullable=False),
sa.Column("strategy_actor", sa.String(255), nullable=False),
sa.Column("execution_actor", sa.String(255), nullable=False),
sa.Column("estimation_actor", sa.String(255), nullable=True),
sa.Column("review_actor", sa.String(255), nullable=True),
sa.Column("apply_actor", sa.String(255), nullable=True),
sa.Column("inputs_schema", sa.Text(), nullable=False, server_default="[]"),
sa.Column("state", sa.String(20), nullable=False, server_default="draft"),
sa.Column(
"reusable", sa.Boolean(), nullable=False, server_default=sa.text("1")
),
sa.Column(
"read_only", sa.Boolean(), nullable=False, server_default=sa.text("0")
),
sa.Column("created_by", sa.String(255), nullable=True),
sa.Column("tags", sa.Text(), nullable=False, server_default="[]"),
sa.Column("created_at", sa.String(30), nullable=False),
sa.Column("updated_at", sa.String(30), nullable=False),
sa.PrimaryKeyConstraint("action_id"),
sa.UniqueConstraint("name", name="uq_actions_v3_name"),
sa.CheckConstraint(
"state IN ('available', 'draft', 'archived')",
name="ck_actions_v3_state",
),
)
op.create_index(
"ix_actions_v3_namespace", "actions_v3", ["namespace"], unique=False
)
op.create_index("ix_actions_v3_state", "actions_v3", ["state"], unique=False)
op.create_index(
"ix_actions_v3_short_name", "actions_v3", ["short_name"], unique=False
)
# Restore legacy lifecycle_plans table
op.create_table(
"lifecycle_plans",
sa.Column("plan_id", sa.String(26), nullable=False),
sa.Column("parent_plan_id", sa.String(26), nullable=True),
sa.Column("root_plan_id", sa.String(26), nullable=True),
sa.Column("action_id", sa.String(26), nullable=False),
sa.Column("phase", sa.String(20), nullable=False),
sa.Column("state", sa.String(20), nullable=False),
sa.Column("attempt", sa.Integer(), nullable=False, server_default=sa.text("1")),
sa.Column(
"automation_level", sa.String(30), nullable=False, server_default="manual"
),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("namespace", sa.String(100), nullable=False),
sa.Column("short_name", sa.String(150), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("definition_of_done", sa.Text(), nullable=True),
sa.Column("strategy_actor", sa.String(255), nullable=True),
sa.Column("execution_actor", sa.String(255), nullable=True),
sa.Column("project_ids", sa.Text(), nullable=False, server_default="[]"),
sa.Column("arguments", sa.Text(), nullable=True),
sa.Column("strategy_context", sa.Text(), nullable=True),
sa.Column("execution_log", sa.Text(), nullable=True),
sa.Column("changeset_id", sa.String(26), nullable=True),
sa.Column("sandbox_refs", sa.Text(), nullable=True),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("error_details", sa.Text(), nullable=True),
sa.Column("created_by", sa.String(255), nullable=True),
sa.Column("tags", sa.Text(), nullable=False, server_default="[]"),
sa.Column(
"reusable", sa.Boolean(), nullable=False, server_default=sa.text("1")
),
sa.Column(
"read_only", sa.Boolean(), nullable=False, server_default=sa.text("0")
),
sa.Column("created_at", sa.String(30), nullable=False),
sa.Column("updated_at", sa.String(30), nullable=False),
sa.Column("strategize_started_at", sa.String(30), nullable=True),
sa.Column("strategize_completed_at", sa.String(30), nullable=True),
sa.Column("execute_started_at", sa.String(30), nullable=True),
sa.Column("execute_completed_at", sa.String(30), nullable=True),
sa.Column("apply_started_at", sa.String(30), nullable=True),
sa.Column("applied_at", sa.String(30), nullable=True),
sa.PrimaryKeyConstraint("plan_id"),
sa.ForeignKeyConstraint(
["parent_plan_id"],
["lifecycle_plans.plan_id"],
ondelete="SET NULL",
name="fk_lifecycle_plans_parent",
),
sa.ForeignKeyConstraint(
["root_plan_id"],
["lifecycle_plans.plan_id"],
ondelete="SET NULL",
name="fk_lifecycle_plans_root",
),
sa.ForeignKeyConstraint(
["action_id"],
["actions_v3.action_id"],
ondelete="RESTRICT",
name="fk_lifecycle_plans_action",
),
sa.CheckConstraint(
"phase IN ('action', 'strategize', 'execute', 'apply', 'applied')",
name="ck_lifecycle_plans_phase",
),
sa.CheckConstraint(
"state IN ('available', 'draft', 'archived', "
"'queued', 'processing', 'errored', 'complete', 'cancelled')",
name="ck_lifecycle_plans_state",
),
sa.CheckConstraint(
"automation_level IN ('manual', 'review_before_apply', 'full_automation')",
name="ck_lifecycle_plans_automation",
),
)
op.create_index(
"ix_lifecycle_plans_phase", "lifecycle_plans", ["phase"], unique=False
)
op.create_index(
"ix_lifecycle_plans_state", "lifecycle_plans", ["state"], unique=False
)
op.create_index(
"ix_lifecycle_plans_parent", "lifecycle_plans", ["parent_plan_id"], unique=False
)
op.create_index(
"ix_lifecycle_plans_root", "lifecycle_plans", ["root_plan_id"], unique=False
)
op.create_index(
"ix_lifecycle_plans_created", "lifecycle_plans", ["created_at"], unique=False
)
op.create_index(
"ix_lifecycle_plans_action", "lifecycle_plans", ["action_id"], unique=False
)