Files
temp/alembic/versions/d0_001_changeset_artifacts.py
freemo e3fcce413b feat(changeset): persist changesets and diff artifacts
Add SQLite persistence for ChangeSet entries and ToolInvocation records
via new changeset_repository module implementing the ChangeSetStore protocol.

- Alembic migration d0_001 creates changeset_entries and tool_invocations tables
- SqliteChangeSetStore, ChangeSetEntryRepository, ToolInvocationRepository
- PlanApplyService.cleanup_changeset() for cancel/failure cleanup
- Behave tests (17 scenarios), Robot tests (5 cases), ASV benchmarks
- Reference documentation in docs/reference/changeset.md

Closes #163
2026-02-26 02:47:14 +00:00

98 lines
3.5 KiB
Python

"""Add changeset_entries and tool_invocations tables.
Provides persistent storage for ChangeSet entries and ToolInvocation
records, replacing the in-memory stores for production use.
Revision ID: d0_001_changeset_artifacts
Revises: c0_002_merge_skill_registry
Create Date: 2026-02-24 00:00:00
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "d0_001_changeset_artifacts"
down_revision: str | Sequence[str] | None = "c0_002_merge_skill_registry"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Create changeset_entries and tool_invocations tables."""
op.create_table(
"changeset_entries",
sa.Column("entry_id", sa.String(26), primary_key=True),
sa.Column("changeset_id", sa.String(26), nullable=False),
sa.Column("plan_id", sa.String(26), nullable=False),
sa.Column("resource_id", sa.String(26), nullable=False),
sa.Column("tool_name", sa.String(255), nullable=False),
sa.Column("operation", sa.String(20), nullable=False),
sa.Column("path", sa.String(1024), nullable=False),
sa.Column("before_hash", sa.String(64), nullable=True),
sa.Column("after_hash", sa.String(64), nullable=True),
sa.Column("before_mode", sa.Integer, nullable=True),
sa.Column("after_mode", sa.Integer, nullable=True),
sa.Column("timestamp", sa.String(30), nullable=False),
)
op.create_index(
"ix_changeset_entries_changeset_id",
"changeset_entries",
["changeset_id"],
)
op.create_index(
"ix_changeset_entries_plan_id",
"changeset_entries",
["plan_id"],
)
op.create_index(
"ix_changeset_entries_path",
"changeset_entries",
["path"],
)
op.create_table(
"tool_invocations",
sa.Column("invocation_id", sa.String(26), primary_key=True),
sa.Column("changeset_id", sa.String(26), nullable=True),
sa.Column("plan_id", sa.String(26), nullable=False),
sa.Column("tool_name", sa.String(255), nullable=False),
sa.Column("skill_name", sa.String(255), nullable=True),
sa.Column("arguments_json", sa.Text, nullable=True),
sa.Column("result_json", sa.Text, nullable=True),
sa.Column("error", sa.Text, nullable=True),
sa.Column("success", sa.Boolean, nullable=False, server_default="1"),
sa.Column("duration_ms", sa.Float, nullable=False, server_default="0.0"),
sa.Column("started_at", sa.String(30), nullable=False),
sa.Column("completed_at", sa.String(30), nullable=True),
sa.Column("change_ids_json", sa.Text, nullable=True),
sa.Column("sequence_number", sa.Integer, nullable=False, server_default="0"),
sa.Column("sandbox_path", sa.String(1024), nullable=True),
sa.Column("resource_refs_json", sa.Text, nullable=True),
sa.Column("provider_metadata_json", sa.Text, nullable=True),
)
op.create_index(
"ix_tool_invocations_changeset_id",
"tool_invocations",
["changeset_id"],
)
op.create_index(
"ix_tool_invocations_plan_id",
"tool_invocations",
["plan_id"],
)
op.create_index(
"ix_tool_invocations_tool_name",
"tool_invocations",
["tool_name"],
)
def downgrade() -> None:
"""Drop changeset_entries and tool_invocations tables."""
op.drop_table("tool_invocations")
op.drop_table("changeset_entries")