forked from HAL9000/cleveragents-core
351 lines
14 KiB
Python
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
|
|
)
|