feat: 导入 MemRelay 初始源码
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from memrelay import models # noqa: F401
|
||||
from memrelay.database import Base
|
||||
|
||||
config = context.config
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
context.configure(
|
||||
url=config.get_main_option("sqlalchemy.url"),
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
compare_type=True,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata, compare_type=True)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,25 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
"""Create the initial MemRelay schema."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260802_0001"
|
||||
down_revision: str | Sequence[str] | None = None
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"administrators",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("username", sa.String(100), nullable=False, unique=True),
|
||||
sa.Column("password_hash", sa.Text(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_table(
|
||||
"web_sessions",
|
||||
sa.Column("id", sa.String(96), primary_key=True),
|
||||
sa.Column(
|
||||
"administrator_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("administrators.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("csrf_digest", sa.String(64), nullable=False),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_web_sessions_administrator_id", "web_sessions", ["administrator_id"])
|
||||
op.create_index("ix_web_sessions_expires_at", "web_sessions", ["expires_at"])
|
||||
op.create_table(
|
||||
"mcp_tokens",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("token_digest", sa.String(64), nullable=False, unique=True),
|
||||
sa.Column("encrypted_token", sa.Text(), nullable=False),
|
||||
sa.Column("access_mode", sa.String(16), nullable=False),
|
||||
sa.Column("revoked", sa.Boolean(), nullable=False),
|
||||
sa.Column("last_used_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_mcp_tokens_token_digest", "mcp_tokens", ["token_digest"])
|
||||
op.create_index("ix_mcp_tokens_revoked", "mcp_tokens", ["revoked"])
|
||||
op.create_table(
|
||||
"projects",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("name", sa.String(200), nullable=False),
|
||||
sa.Column("slug", sa.String(200), nullable=False, unique=True),
|
||||
sa.Column("git_remote", sa.Text(), unique=True),
|
||||
sa.Column("archived", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_projects_slug", "projects", ["slug"])
|
||||
op.create_index("ix_projects_archived", "projects", ["archived"])
|
||||
op.create_table(
|
||||
"project_aliases",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="CASCADE")),
|
||||
sa.Column("kind", sa.String(32), nullable=False),
|
||||
sa.Column("value", sa.Text(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint("kind", "value", name="uq_project_alias_kind_value"),
|
||||
)
|
||||
op.create_index("ix_project_aliases_project_id", "project_aliases", ["project_id"])
|
||||
op.create_table(
|
||||
"memory_references",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="SET NULL")),
|
||||
sa.Column("scope", sa.String(16), nullable=False),
|
||||
sa.Column("memory_type", sa.String(32), nullable=False),
|
||||
sa.Column("path", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("title", sa.String(300), nullable=False),
|
||||
sa.Column("revision", sa.Integer(), nullable=False),
|
||||
sa.Column("status", sa.String(32), nullable=False),
|
||||
sa.Column("tags_json", sa.Text(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_memory_references_project_id", "memory_references", ["project_id"])
|
||||
op.create_index("ix_memory_references_scope", "memory_references", ["scope"])
|
||||
op.create_index("ix_memory_references_memory_type", "memory_references", ["memory_type"])
|
||||
op.create_index("ix_memory_references_status", "memory_references", ["status"])
|
||||
op.create_index(
|
||||
"ix_memory_scope_project_type",
|
||||
"memory_references",
|
||||
["scope", "project_id", "memory_type"],
|
||||
)
|
||||
op.create_table(
|
||||
"idempotency_records",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("operation", sa.String(100), nullable=False),
|
||||
sa.Column("request_id", sa.String(100), nullable=False),
|
||||
sa.Column("response_json", sa.Text(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint("operation", "request_id", name="uq_idempotency_request"),
|
||||
)
|
||||
op.create_table(
|
||||
"vault_connections",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("encrypted_server_url", sa.Text(), nullable=False),
|
||||
sa.Column("encrypted_account", sa.Text(), nullable=False),
|
||||
sa.Column("encrypted_password", sa.Text(), nullable=False),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False),
|
||||
sa.Column("status", sa.String(32), nullable=False),
|
||||
sa.Column("last_sync_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_error", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_table(
|
||||
"credential_mappings",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("lookup_key", sa.String(300), nullable=False),
|
||||
sa.Column("vault_item_id", sa.String(100), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint("lookup_key", name="uq_credential_mapping_lookup"),
|
||||
)
|
||||
op.create_index("ix_credential_mappings_lookup_key", "credential_mappings", ["lookup_key"])
|
||||
op.create_table(
|
||||
"file_records",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="SET NULL")),
|
||||
sa.Column("storage_path", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("name", sa.String(300), nullable=False),
|
||||
sa.Column("description", sa.Text()),
|
||||
sa.Column("version", sa.String(100)),
|
||||
sa.Column("purpose", sa.String(200)),
|
||||
sa.Column("tags_json", sa.Text(), nullable=False),
|
||||
sa.Column("mime_type", sa.String(200), nullable=False),
|
||||
sa.Column("size", sa.Integer(), nullable=False),
|
||||
sa.Column("checksum_sha256", sa.String(64), nullable=False),
|
||||
sa.Column("status", sa.String(32), nullable=False),
|
||||
sa.Column("modified_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_file_records_project_id", "file_records", ["project_id"])
|
||||
op.create_index("ix_file_records_name", "file_records", ["name"])
|
||||
op.create_index("ix_file_records_version", "file_records", ["version"])
|
||||
op.create_index("ix_file_records_purpose", "file_records", ["purpose"])
|
||||
op.create_index("ix_file_records_checksum_sha256", "file_records", ["checksum_sha256"])
|
||||
op.create_index("ix_file_records_status", "file_records", ["status"])
|
||||
op.create_index("ix_file_project_status", "file_records", ["project_id", "status"])
|
||||
op.create_table(
|
||||
"upload_tasks",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("target_path", sa.Text(), nullable=False),
|
||||
sa.Column("expected_size", sa.Integer(), nullable=False),
|
||||
sa.Column("expected_sha256", sa.String(64)),
|
||||
sa.Column("token_digest", sa.String(64), nullable=False, unique=True),
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("status", sa.String(32), nullable=False),
|
||||
sa.Column("error_message", sa.Text()),
|
||||
sa.Column("file_id", sa.String(36), sa.ForeignKey("file_records.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_upload_tasks_token_digest", "upload_tasks", ["token_digest"])
|
||||
op.create_index("ix_upload_tasks_expires_at", "upload_tasks", ["expires_at"])
|
||||
op.create_index("ix_upload_tasks_status", "upload_tasks", ["status"])
|
||||
op.create_table(
|
||||
"system_settings",
|
||||
sa.Column("key", sa.String(100), primary_key=True),
|
||||
sa.Column("value_json", sa.Text(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for table in (
|
||||
"system_settings",
|
||||
"upload_tasks",
|
||||
"file_records",
|
||||
"credential_mappings",
|
||||
"vault_connections",
|
||||
"idempotency_records",
|
||||
"memory_references",
|
||||
"project_aliases",
|
||||
"projects",
|
||||
"mcp_tokens",
|
||||
"web_sessions",
|
||||
"administrators",
|
||||
):
|
||||
op.drop_table(table)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Add upload task metadata."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260802_0002"
|
||||
down_revision: str | Sequence[str] | None = "20260802_0001"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("upload_tasks") as batch:
|
||||
batch.add_column(sa.Column("project_id", sa.String(36), nullable=True))
|
||||
batch.add_column(sa.Column("description", sa.Text(), nullable=True))
|
||||
batch.add_column(sa.Column("version", sa.String(100), nullable=True))
|
||||
batch.add_column(sa.Column("purpose", sa.String(200), nullable=True))
|
||||
batch.add_column(sa.Column("tags_json", sa.Text(), nullable=False, server_default="[]"))
|
||||
batch.add_column(
|
||||
sa.Column("overwrite_allowed", sa.Boolean(), nullable=False, server_default=sa.false())
|
||||
)
|
||||
batch.create_foreign_key(
|
||||
"fk_upload_tasks_project_id", "projects", ["project_id"], ["id"], ondelete="SET NULL"
|
||||
)
|
||||
batch.create_index("ix_upload_tasks_project_id", ["project_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("upload_tasks") as batch:
|
||||
batch.drop_index("ix_upload_tasks_project_id")
|
||||
batch.drop_constraint("fk_upload_tasks_project_id", type_="foreignkey")
|
||||
for column in (
|
||||
"overwrite_allowed",
|
||||
"tags_json",
|
||||
"purpose",
|
||||
"version",
|
||||
"description",
|
||||
"project_id",
|
||||
):
|
||||
batch.drop_column(column)
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Add API-key authentication fields to vault connections."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260802_0003"
|
||||
down_revision: str | Sequence[str] | None = "20260802_0002"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("vault_connections") as batch:
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"login_method",
|
||||
sa.String(16),
|
||||
nullable=False,
|
||||
server_default="password",
|
||||
)
|
||||
)
|
||||
batch.add_column(sa.Column("encrypted_client_secret", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("vault_connections") as batch:
|
||||
batch.drop_column("encrypted_client_secret")
|
||||
batch.drop_column("login_method")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Add directory records to the file catalog."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260802_0004"
|
||||
down_revision: str | Sequence[str] | None = "20260802_0003"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("file_records") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("is_directory", sa.Boolean(), nullable=False, server_default=sa.false())
|
||||
)
|
||||
batch.create_index("ix_file_records_is_directory", ["is_directory"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("file_records") as batch:
|
||||
batch.drop_index("ix_file_records_is_directory")
|
||||
batch.drop_column("is_directory")
|
||||
@@ -0,0 +1,477 @@
|
||||
"""Add AI curation and optional memory Git data model."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260804_0005"
|
||||
down_revision: str | Sequence[str] | None = "20260802_0004"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"usage_profiles",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("name", sa.String(100), nullable=False, unique=True),
|
||||
sa.Column("description", sa.Text()),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("is_shared", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_usage_profiles_enabled", "usage_profiles", ["enabled"])
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"workspace_type",
|
||||
sa.String(32),
|
||||
nullable=False,
|
||||
server_default="general",
|
||||
)
|
||||
)
|
||||
batch.create_index("ix_projects_workspace_type", ["workspace_type"])
|
||||
op.execute(
|
||||
"UPDATE projects SET workspace_type = 'development' "
|
||||
"WHERE git_remote IS NOT NULL AND TRIM(git_remote) <> ''"
|
||||
)
|
||||
with op.batch_alter_table("mcp_tokens") as batch:
|
||||
batch.add_column(sa.Column("usage_profile_id", sa.String(36)))
|
||||
batch.create_index("ix_mcp_tokens_usage_profile_id", ["usage_profile_id"])
|
||||
batch.create_foreign_key(
|
||||
"fk_mcp_tokens_usage_profile",
|
||||
"usage_profiles",
|
||||
["usage_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.add_column(sa.Column("usage_profile_id", sa.String(36)))
|
||||
batch.create_index("ix_memory_references_usage_profile_id", ["usage_profile_id"])
|
||||
batch.create_foreign_key(
|
||||
"fk_memory_references_usage_profile",
|
||||
"usage_profiles",
|
||||
["usage_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"curation_settings",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("timezone", sa.String(100), nullable=False, server_default="Asia/Shanghai"),
|
||||
sa.Column("context_input_tokens", sa.Integer(), nullable=False, server_default="32000"),
|
||||
sa.Column("context_output_tokens", sa.Integer(), nullable=False, server_default="8000"),
|
||||
sa.Column("task_max_calls", sa.Integer(), nullable=False, server_default="20"),
|
||||
sa.Column("task_max_tokens", sa.Integer(), nullable=False, server_default="200000"),
|
||||
sa.Column("batch_chars", sa.Integer(), nullable=False, server_default="80000"),
|
||||
sa.Column("max_merge_levels", sa.Integer(), nullable=False, server_default="4"),
|
||||
sa.Column("max_concurrency", sa.Integer(), nullable=False, server_default="1"),
|
||||
sa.Column("source_freshness_hours", sa.Integer(), nullable=False, server_default="24"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_curation_settings_enabled", "curation_settings", ["enabled"])
|
||||
op.create_table(
|
||||
"curation_model_configs",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("revision", sa.Integer(), nullable=False, unique=True),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("base_url", sa.Text(), nullable=False),
|
||||
sa.Column("encrypted_token", sa.Text()),
|
||||
sa.Column("text_model", sa.String(300), nullable=False),
|
||||
sa.Column("vision_model", sa.String(300)),
|
||||
sa.Column("protocol", sa.String(32), nullable=False, server_default="auto"),
|
||||
sa.Column("effective_protocol", sa.String(32)),
|
||||
sa.Column("stream", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("timeout_seconds", sa.Integer(), nullable=False, server_default="120"),
|
||||
sa.Column("probe_interval_seconds", sa.Integer(), nullable=False, server_default="300"),
|
||||
sa.Column("management_url", sa.Text()),
|
||||
sa.Column("capability_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_curation_model_configs_revision", "curation_model_configs", ["revision"])
|
||||
op.create_index("ix_curation_model_configs_active", "curation_model_configs", ["active"])
|
||||
op.create_table(
|
||||
"curation_dependency_state",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="unconfigured"),
|
||||
sa.Column("capability_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("last_check_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_success_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_failure_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("http_status", sa.Integer()),
|
||||
sa.Column("error_code", sa.String(100)),
|
||||
sa.Column("error_message", sa.Text()),
|
||||
sa.Column("next_probe_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_curation_dependency_state_status", "curation_dependency_state", ["status"])
|
||||
op.create_index(
|
||||
"ix_curation_dependency_state_next_probe_at",
|
||||
"curation_dependency_state",
|
||||
["next_probe_at"],
|
||||
)
|
||||
op.create_table(
|
||||
"curation_schedules",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("name", sa.String(200), nullable=False),
|
||||
sa.Column("trigger_type", sa.String(40), nullable=False),
|
||||
sa.Column("scope", sa.String(16), nullable=False, server_default="project"),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="CASCADE")),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("inheritance", sa.String(20), nullable=False, server_default="instance"),
|
||||
sa.Column("timezone", sa.String(100)),
|
||||
sa.Column("recurrence_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("missed_policy", sa.String(20), nullable=False, server_default="run_once"),
|
||||
sa.Column("last_scheduled_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_triggered_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("next_run_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="active"),
|
||||
sa.Column("last_error", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_curation_schedules_trigger_type", "curation_schedules", ["trigger_type"])
|
||||
op.create_index("ix_curation_schedules_project_id", "curation_schedules", ["project_id"])
|
||||
op.create_index("ix_curation_schedules_enabled", "curation_schedules", ["enabled"])
|
||||
op.create_index("ix_curation_schedules_next_run_at", "curation_schedules", ["next_run_at"])
|
||||
op.create_table(
|
||||
"curation_change_log",
|
||||
sa.Column("sequence", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("scope", sa.String(16), nullable=False),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="SET NULL")),
|
||||
sa.Column("source_type", sa.String(40), nullable=False),
|
||||
sa.Column("source_id", sa.String(100), nullable=False),
|
||||
sa.Column("change_type", sa.String(20), nullable=False),
|
||||
sa.Column("revision", sa.String(100)),
|
||||
sa.Column("content_hash", sa.String(64)),
|
||||
sa.Column("target_hint", sa.String(200)),
|
||||
sa.Column("summary", sa.Text()),
|
||||
sa.Column(
|
||||
"usage_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("usage_profiles.id", ondelete="SET NULL"),
|
||||
),
|
||||
sa.Column("origin", sa.String(20), nullable=False),
|
||||
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
for column in (
|
||||
"scope",
|
||||
"project_id",
|
||||
"source_type",
|
||||
"source_id",
|
||||
"usage_profile_id",
|
||||
"origin",
|
||||
"observed_at",
|
||||
):
|
||||
op.create_index(f"ix_curation_change_log_{column}", "curation_change_log", [column])
|
||||
op.create_table(
|
||||
"curation_jobs",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("scope", sa.String(16), nullable=False),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="SET NULL")),
|
||||
sa.Column(
|
||||
"usage_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("usage_profiles.id", ondelete="SET NULL"),
|
||||
),
|
||||
sa.Column("mode", sa.String(20), nullable=False, server_default="incremental"),
|
||||
sa.Column("trigger", sa.String(40), nullable=False),
|
||||
sa.Column("source_strategy", sa.String(20), nullable=False, server_default="auto"),
|
||||
sa.Column("target_documents_json", sa.Text(), nullable=False, server_default="[]"),
|
||||
sa.Column("coalesce_key", sa.String(200)),
|
||||
sa.Column("slot", sa.String(240), unique=True),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="queued"),
|
||||
sa.Column("last_success_cursor", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("latest_observed_cursor", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("merged_trigger_count", sa.Integer(), nullable=False, server_default="1"),
|
||||
sa.Column("trigger_summary_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column(
|
||||
"model_config_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_model_configs.id", ondelete="SET NULL"),
|
||||
),
|
||||
sa.Column("prompt_version", sa.String(100)),
|
||||
sa.Column("schema_version", sa.String(100)),
|
||||
sa.Column("lease_owner", sa.String(100)),
|
||||
sa.Column("lease_expires_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("retries", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("next_retry_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("error_code", sa.String(100)),
|
||||
sa.Column("error_message", sa.Text()),
|
||||
sa.Column("stats_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
for column in (
|
||||
"scope",
|
||||
"project_id",
|
||||
"usage_profile_id",
|
||||
"trigger",
|
||||
"coalesce_key",
|
||||
"status",
|
||||
"lease_owner",
|
||||
"lease_expires_at",
|
||||
"next_retry_at",
|
||||
):
|
||||
op.create_index(f"ix_curation_jobs_{column}", "curation_jobs", [column])
|
||||
op.create_table(
|
||||
"curation_attempts",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"job_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_jobs.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("attempt_number", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"model_config_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_model_configs.id", ondelete="SET NULL"),
|
||||
),
|
||||
sa.Column("effective_protocol", sa.String(32)),
|
||||
sa.Column("model", sa.String(300)),
|
||||
sa.Column("prompt_version", sa.String(100), nullable=False, server_default="1"),
|
||||
sa.Column("schema_version", sa.String(100), nullable=False, server_default="1"),
|
||||
sa.Column("budget_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="running"),
|
||||
sa.Column("error_code", sa.String(100)),
|
||||
sa.Column("error_message", sa.Text()),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
||||
sa.UniqueConstraint("job_id", "attempt_number", name="uq_curation_attempt"),
|
||||
)
|
||||
op.create_index("ix_curation_attempts_job_id", "curation_attempts", ["job_id"])
|
||||
op.create_table(
|
||||
"curated_documents",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("scope", sa.String(16), nullable=False),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="CASCADE")),
|
||||
sa.Column(
|
||||
"usage_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("usage_profiles.id", ondelete="SET NULL"),
|
||||
),
|
||||
sa.Column("document_type", sa.String(100), nullable=False),
|
||||
sa.Column("title", sa.String(300), nullable=False),
|
||||
sa.Column("path", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("revision", sa.Integer(), nullable=False, server_default="1"),
|
||||
sa.Column(
|
||||
"latest_job_id", sa.String(36), sa.ForeignKey("curation_jobs.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column("source_hash", sa.String(64), nullable=False),
|
||||
sa.Column("source_cursor", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("model", sa.String(300)),
|
||||
sa.Column("prompt_version", sa.String(100), nullable=False, server_default="1"),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="active"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint(
|
||||
"scope",
|
||||
"project_id",
|
||||
"usage_profile_id",
|
||||
"document_type",
|
||||
name="uq_curated_document",
|
||||
),
|
||||
)
|
||||
for column in ("scope", "project_id", "usage_profile_id", "document_type", "status"):
|
||||
op.create_index(f"ix_curated_documents_{column}", "curated_documents", [column])
|
||||
op.create_table(
|
||||
"curated_document_revisions",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"document_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curated_documents.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("revision", sa.Integer(), nullable=False),
|
||||
sa.Column("storage_path", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("job_id", sa.String(36), sa.ForeignKey("curation_jobs.id", ondelete="SET NULL")),
|
||||
sa.Column("model", sa.String(300)),
|
||||
sa.Column("source_hash", sa.String(64), nullable=False),
|
||||
sa.Column("change_summary", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.UniqueConstraint("document_id", "revision", name="uq_curated_document_revision"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curated_document_revisions_document_id",
|
||||
"curated_document_revisions",
|
||||
["document_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curated_document_revisions_job_id", "curated_document_revisions", ["job_id"]
|
||||
)
|
||||
op.create_table(
|
||||
"curation_sources",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"job_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_jobs.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("source_type", sa.String(40), nullable=False),
|
||||
sa.Column("source_id", sa.String(100), nullable=False),
|
||||
sa.Column("source_revision", sa.String(100)),
|
||||
sa.Column("content_hash", sa.String(64)),
|
||||
sa.Column("origin", sa.String(20), nullable=False),
|
||||
sa.Column("sent_to_model", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("disposition", sa.String(20), nullable=False, server_default="processed"),
|
||||
sa.Column("reason", sa.Text()),
|
||||
sa.UniqueConstraint("job_id", "source_type", "source_id", name="uq_curation_source"),
|
||||
)
|
||||
op.create_index("ix_curation_sources_job_id", "curation_sources", ["job_id"])
|
||||
op.create_table(
|
||||
"curation_model_calls",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"job_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_jobs.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"attempt_id", sa.String(36), sa.ForeignKey("curation_attempts.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column("purpose", sa.String(40), nullable=False),
|
||||
sa.Column("protocol", sa.String(32), nullable=False),
|
||||
sa.Column("stream", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("requested_model", sa.String(300), nullable=False),
|
||||
sa.Column("response_model", sa.String(300)),
|
||||
sa.Column("request_id", sa.String(300)),
|
||||
sa.Column("prompt_version", sa.String(100), nullable=False),
|
||||
sa.Column("input_tokens", sa.Integer()),
|
||||
sa.Column("output_tokens", sa.Integer()),
|
||||
sa.Column("latency_ms", sa.Integer()),
|
||||
sa.Column("status", sa.String(32), nullable=False),
|
||||
sa.Column("error_code", sa.String(100)),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
||||
)
|
||||
op.create_index("ix_curation_model_calls_job_id", "curation_model_calls", ["job_id"])
|
||||
op.create_index("ix_curation_model_calls_attempt_id", "curation_model_calls", ["attempt_id"])
|
||||
|
||||
op.create_table(
|
||||
"git_connections",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("mode", sa.String(20), nullable=False, server_default="single"),
|
||||
sa.Column("remote_url", sa.Text()),
|
||||
sa.Column("username", sa.String(500)),
|
||||
sa.Column("transport", sa.String(20), nullable=False, server_default="https"),
|
||||
sa.Column("credential_storage", sa.String(20), nullable=False, server_default="local"),
|
||||
sa.Column("encrypted_credential", sa.Text()),
|
||||
sa.Column("vault_item_id", sa.String(100)),
|
||||
sa.Column("default_branch", sa.String(200), nullable=False, server_default="main"),
|
||||
sa.Column("author_name", sa.String(200), nullable=False, server_default="MemRelay"),
|
||||
sa.Column(
|
||||
"author_email", sa.String(320), nullable=False, server_default="memrelay@localhost"
|
||||
),
|
||||
sa.Column("allow_write_test", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("allow_push_to_create", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
sa.Column("capability_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("last_error", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_git_connections_enabled", "git_connections", ["enabled"])
|
||||
op.create_table(
|
||||
"memory_repositories",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"connection_id", sa.String(36), sa.ForeignKey("git_connections.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column("mode", sa.String(20), nullable=False),
|
||||
sa.Column("scope", sa.String(16), nullable=False),
|
||||
sa.Column("project_id", sa.String(36), sa.ForeignKey("projects.id", ondelete="SET NULL")),
|
||||
sa.Column("local_path", sa.Text(), nullable=False, unique=True),
|
||||
sa.Column("remote_url", sa.Text()),
|
||||
sa.Column("branch", sa.String(200), nullable=False, server_default="main"),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="local"),
|
||||
sa.Column("current_commit", sa.String(64)),
|
||||
sa.Column("last_pushed_commit", sa.String(64)),
|
||||
sa.Column("pending_commits", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("archived_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_error", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
for column in ("connection_id", "scope", "project_id", "status"):
|
||||
op.create_index(f"ix_memory_repositories_{column}", "memory_repositories", [column])
|
||||
op.create_table(
|
||||
"git_sync_jobs",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("operation_id", sa.String(100), nullable=False, unique=True),
|
||||
sa.Column(
|
||||
"repository_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("memory_repositories.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("resource_id", sa.String(100)),
|
||||
sa.Column("action", sa.String(40), nullable=False),
|
||||
sa.Column("summary", sa.String(500), nullable=False),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="pending"),
|
||||
sa.Column("target_commit", sa.String(64)),
|
||||
sa.Column("attempts", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("next_retry_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("error_message", sa.Text()),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
for column in ("operation_id", "repository_id", "resource_id", "status", "next_retry_at"):
|
||||
op.create_index(f"ix_git_sync_jobs_{column}", "git_sync_jobs", [column])
|
||||
op.create_table(
|
||||
"runtime_leases",
|
||||
sa.Column("name", sa.String(100), primary_key=True),
|
||||
sa.Column("owner", sa.String(100), nullable=False),
|
||||
sa.Column("lease_expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_runtime_leases_owner", "runtime_leases", ["owner"])
|
||||
op.create_index("ix_runtime_leases_lease_expires_at", "runtime_leases", ["lease_expires_at"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for table in (
|
||||
"runtime_leases",
|
||||
"git_sync_jobs",
|
||||
"memory_repositories",
|
||||
"git_connections",
|
||||
"curation_model_calls",
|
||||
"curation_sources",
|
||||
"curated_document_revisions",
|
||||
"curated_documents",
|
||||
"curation_attempts",
|
||||
"curation_jobs",
|
||||
"curation_change_log",
|
||||
"curation_schedules",
|
||||
"curation_dependency_state",
|
||||
"curation_model_configs",
|
||||
"curation_settings",
|
||||
):
|
||||
op.drop_table(table)
|
||||
with op.batch_alter_table("mcp_tokens") as batch:
|
||||
batch.drop_constraint("fk_mcp_tokens_usage_profile", type_="foreignkey")
|
||||
batch.drop_index("ix_mcp_tokens_usage_profile_id")
|
||||
batch.drop_column("usage_profile_id")
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.drop_constraint("fk_memory_references_usage_profile", type_="foreignkey")
|
||||
batch.drop_index("ix_memory_references_usage_profile_id")
|
||||
batch.drop_column("usage_profile_id")
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.drop_index("ix_projects_workspace_type")
|
||||
batch.drop_column("workspace_type")
|
||||
op.drop_table("usage_profiles")
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Add reliability leases, schedule deduplication, and profile context."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260804_0006"
|
||||
down_revision: str | Sequence[str] | None = "20260804_0005"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.add_column(sa.Column("custom_curation_template", sa.Text()))
|
||||
|
||||
with op.batch_alter_table("web_sessions") as batch:
|
||||
batch.add_column(sa.Column("usage_profile_id", sa.String(36)))
|
||||
batch.create_index("ix_web_sessions_usage_profile_id", ["usage_profile_id"])
|
||||
batch.create_foreign_key(
|
||||
"fk_web_sessions_usage_profile",
|
||||
"usage_profiles",
|
||||
["usage_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
with op.batch_alter_table("curation_attempts") as batch:
|
||||
batch.add_column(sa.Column("attempt_key", sa.String(64)))
|
||||
op.execute("UPDATE curation_attempts SET attempt_key = id WHERE attempt_key IS NULL")
|
||||
with op.batch_alter_table("curation_attempts") as batch:
|
||||
batch.alter_column("attempt_key", nullable=False)
|
||||
batch.create_index("ix_curation_attempts_attempt_key", ["attempt_key"], unique=True)
|
||||
|
||||
with op.batch_alter_table("git_sync_jobs") as batch:
|
||||
batch.add_column(sa.Column("lease_owner", sa.String(140)))
|
||||
batch.add_column(sa.Column("lease_expires_at", sa.DateTime(timezone=True)))
|
||||
batch.create_index("ix_git_sync_jobs_lease_owner", ["lease_owner"])
|
||||
batch.create_index("ix_git_sync_jobs_lease_expires_at", ["lease_expires_at"])
|
||||
|
||||
op.create_table(
|
||||
"curation_schedule_triggers",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"schedule_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("curation_schedules.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("trigger_key", sa.String(180), nullable=False, unique=True),
|
||||
sa.Column("scheduled_for", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("job_ids_json", sa.Text(), nullable=False, server_default="[]"),
|
||||
sa.Column("status", sa.String(32), nullable=False, server_default="created"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_schedule_triggers_schedule_id",
|
||||
"curation_schedule_triggers",
|
||||
["schedule_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_schedule_triggers_trigger_key",
|
||||
"curation_schedule_triggers",
|
||||
["trigger_key"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_schedule_triggers_scheduled_for",
|
||||
"curation_schedule_triggers",
|
||||
["scheduled_for"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_schedule_triggers_status",
|
||||
"curation_schedule_triggers",
|
||||
["status"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("curation_schedule_triggers")
|
||||
with op.batch_alter_table("git_sync_jobs") as batch:
|
||||
batch.drop_index("ix_git_sync_jobs_lease_expires_at")
|
||||
batch.drop_index("ix_git_sync_jobs_lease_owner")
|
||||
batch.drop_column("lease_expires_at")
|
||||
batch.drop_column("lease_owner")
|
||||
with op.batch_alter_table("curation_attempts") as batch:
|
||||
batch.drop_index("ix_curation_attempts_attempt_key")
|
||||
batch.drop_column("attempt_key")
|
||||
with op.batch_alter_table("web_sessions") as batch:
|
||||
batch.drop_constraint("fk_web_sessions_usage_profile", type_="foreignkey")
|
||||
batch.drop_index("ix_web_sessions_usage_profile_id")
|
||||
batch.drop_column("usage_profile_id")
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.drop_column("custom_curation_template")
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Separate project identity from source checkout configuration."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260804_0007"
|
||||
down_revision: str | Sequence[str] | None = "20260804_0006"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def _legacy_clone_url(value: str) -> str:
|
||||
parsed = urlsplit(value)
|
||||
if parsed.scheme == "git" and parsed.hostname and parsed.path:
|
||||
port = f":{parsed.port}" if parsed.port else ""
|
||||
return f"https://{parsed.hostname}{port}{parsed.path}.git"
|
||||
return value
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.add_column(sa.Column("git_remote_normalized", sa.Text()))
|
||||
batch.add_column(
|
||||
sa.Column("curation_enabled", sa.Boolean(), nullable=False, server_default=sa.true())
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column("source_strategy", sa.String(20), nullable=False, server_default="auto")
|
||||
)
|
||||
batch.add_column(sa.Column("source_branch", sa.String(200)))
|
||||
batch.add_column(sa.Column("source_credential_item_id", sa.String(100)))
|
||||
batch.add_column(sa.Column("source_last_commit", sa.String(64)))
|
||||
batch.add_column(sa.Column("source_last_branch", sa.String(200)))
|
||||
batch.add_column(sa.Column("source_last_fetched_at", sa.DateTime(timezone=True)))
|
||||
batch.add_column(sa.Column("source_last_error", sa.Text()))
|
||||
batch.add_column(
|
||||
sa.Column("source_cache_used", sa.Boolean(), nullable=False, server_default=sa.false())
|
||||
)
|
||||
batch.add_column(sa.Column("last_agent_sync_at", sa.DateTime(timezone=True)))
|
||||
batch.add_column(sa.Column("last_agent_branch", sa.String(200)))
|
||||
batch.add_column(sa.Column("last_agent_commit", sa.String(64)))
|
||||
batch.add_column(sa.Column("last_agent_dirty", sa.Boolean()))
|
||||
|
||||
connection = op.get_bind()
|
||||
projects = connection.execute(sa.text("SELECT id, git_remote FROM projects")).mappings()
|
||||
for project in projects:
|
||||
remote = project["git_remote"]
|
||||
if not remote:
|
||||
continue
|
||||
connection.execute(
|
||||
sa.text(
|
||||
"UPDATE projects "
|
||||
"SET git_remote = :clone_url, git_remote_normalized = :normalized "
|
||||
"WHERE id = :project_id"
|
||||
),
|
||||
{
|
||||
"clone_url": _legacy_clone_url(remote),
|
||||
"normalized": remote,
|
||||
"project_id": project["id"],
|
||||
},
|
||||
)
|
||||
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.create_unique_constraint(
|
||||
"uq_projects_git_remote_normalized", ["git_remote_normalized"]
|
||||
)
|
||||
batch.create_index("ix_projects_curation_enabled", ["curation_enabled"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
connection = op.get_bind()
|
||||
connection.execute(
|
||||
sa.text(
|
||||
"UPDATE projects SET git_remote = git_remote_normalized "
|
||||
"WHERE git_remote_normalized IS NOT NULL"
|
||||
)
|
||||
)
|
||||
with op.batch_alter_table("projects") as batch:
|
||||
batch.drop_index("ix_projects_curation_enabled")
|
||||
batch.drop_constraint("uq_projects_git_remote_normalized", type_="unique")
|
||||
batch.drop_column("last_agent_dirty")
|
||||
batch.drop_column("last_agent_commit")
|
||||
batch.drop_column("last_agent_branch")
|
||||
batch.drop_column("last_agent_sync_at")
|
||||
batch.drop_column("source_cache_used")
|
||||
batch.drop_column("source_last_error")
|
||||
batch.drop_column("source_last_fetched_at")
|
||||
batch.drop_column("source_last_branch")
|
||||
batch.drop_column("source_last_commit")
|
||||
batch.drop_column("source_credential_item_id")
|
||||
batch.drop_column("source_branch")
|
||||
batch.drop_column("source_strategy")
|
||||
batch.drop_column("curation_enabled")
|
||||
batch.drop_column("git_remote_normalized")
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Persist curated document metadata for every revision."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260804_0008"
|
||||
down_revision: str | Sequence[str] | None = "20260804_0007"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("curated_documents") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("metadata_json", sa.Text(), nullable=False, server_default="{}")
|
||||
)
|
||||
with op.batch_alter_table("curated_document_revisions") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("metadata_json", sa.Text(), nullable=False, server_default="{}")
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("curated_document_revisions") as batch:
|
||||
batch.drop_column("metadata_json")
|
||||
with op.batch_alter_table("curated_documents") as batch:
|
||||
batch.drop_column("metadata_json")
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Add inherited curation policies and configurable source limits."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260804_0009"
|
||||
down_revision: str | Sequence[str] | None = "20260804_0008"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("curation_settings") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("retry_initial_seconds", sa.Integer(), nullable=False, server_default="300")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column("retry_max_seconds", sa.Integer(), nullable=False, server_default="3600")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column("max_source_files", sa.Integer(), nullable=False, server_default="1000")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"max_source_chars", sa.Integer(), nullable=False, server_default="20000000"
|
||||
)
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"text_file_max_bytes", sa.Integer(), nullable=False, server_default="2097152"
|
||||
)
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"rich_file_max_bytes", sa.Integer(), nullable=False, server_default="52428800"
|
||||
)
|
||||
)
|
||||
op.create_table(
|
||||
"curation_policy_overrides",
|
||||
sa.Column("key", sa.String(length=120), nullable=False),
|
||||
sa.Column("scope", sa.String(length=20), nullable=False),
|
||||
sa.Column("workspace_type", sa.String(length=32), nullable=True),
|
||||
sa.Column("project_id", sa.String(length=36), nullable=True),
|
||||
sa.Column("values_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("key"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_policy_overrides_scope",
|
||||
"curation_policy_overrides",
|
||||
["scope"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_policy_overrides_workspace_type",
|
||||
"curation_policy_overrides",
|
||||
["workspace_type"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_policy_overrides_project_id",
|
||||
"curation_policy_overrides",
|
||||
["project_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(
|
||||
"ix_curation_policy_overrides_project_id",
|
||||
table_name="curation_policy_overrides",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_curation_policy_overrides_workspace_type",
|
||||
table_name="curation_policy_overrides",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_curation_policy_overrides_scope",
|
||||
table_name="curation_policy_overrides",
|
||||
)
|
||||
op.drop_table("curation_policy_overrides")
|
||||
with op.batch_alter_table("curation_settings") as batch:
|
||||
batch.drop_column("rich_file_max_bytes")
|
||||
batch.drop_column("text_file_max_bytes")
|
||||
batch.drop_column("max_source_chars")
|
||||
batch.drop_column("max_source_files")
|
||||
batch.drop_column("retry_max_seconds")
|
||||
batch.drop_column("retry_initial_seconds")
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Add all-profiles MCP token scope.
|
||||
|
||||
Revision ID: 20260805_0010
|
||||
Revises: 20260804_0009
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "20260805_0010"
|
||||
down_revision = "20260804_0009"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("mcp_tokens") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("all_profiles", sa.Boolean(), nullable=False, server_default=sa.false())
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("mcp_tokens") as batch:
|
||||
batch.drop_column("all_profiles")
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Add bounded execution output events for curation jobs."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260806_0011"
|
||||
down_revision: str | Sequence[str] | None = "20260805_0010"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"curation_execution_events",
|
||||
sa.Column("sequence", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("job_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("attempt_id", sa.String(length=36), nullable=True),
|
||||
sa.Column("phase", sa.String(length=32), nullable=False),
|
||||
sa.Column("level", sa.String(length=16), nullable=False, server_default="info"),
|
||||
sa.Column("message_code", sa.String(length=80), nullable=False),
|
||||
sa.Column("details_json", sa.Text(), nullable=False, server_default="{}"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(["attempt_id"], ["curation_attempts.id"], ondelete="SET NULL"),
|
||||
sa.ForeignKeyConstraint(["job_id"], ["curation_jobs.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("sequence"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_execution_events_job_id",
|
||||
"curation_execution_events",
|
||||
["job_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_execution_events_attempt_id",
|
||||
"curation_execution_events",
|
||||
["attempt_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_execution_events_phase",
|
||||
"curation_execution_events",
|
||||
["phase"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_execution_events_job_created",
|
||||
"curation_execution_events",
|
||||
["job_id", "created_at"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(
|
||||
"ix_curation_execution_events_job_created",
|
||||
table_name="curation_execution_events",
|
||||
)
|
||||
op.drop_index("ix_curation_execution_events_phase", table_name="curation_execution_events")
|
||||
op.drop_index("ix_curation_execution_events_attempt_id", table_name="curation_execution_events")
|
||||
op.drop_index("ix_curation_execution_events_job_id", table_name="curation_execution_events")
|
||||
op.drop_table("curation_execution_events")
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Add derived memory curation lifecycle fields."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260806_0012"
|
||||
down_revision: str | Sequence[str] | None = "20260806_0011"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"curation_status",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
server_default="pending",
|
||||
)
|
||||
)
|
||||
batch.add_column(sa.Column("curated_revision", sa.Integer(), nullable=True))
|
||||
batch.add_column(sa.Column("curated_at", sa.DateTime(timezone=True), nullable=True))
|
||||
batch.add_column(
|
||||
sa.Column("covered_by_json", sa.Text(), nullable=False, server_default="[]")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column("superseded_by_json", sa.Text(), nullable=False, server_default="[]")
|
||||
)
|
||||
batch.add_column(sa.Column("latest_curation_job_id", sa.String(length=36), nullable=True))
|
||||
batch.create_index("ix_memory_references_curation_status", ["curation_status"])
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE curated_documents
|
||||
SET status = 'superseded'
|
||||
WHERE status = 'active'
|
||||
AND id NOT IN (
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY
|
||||
scope,
|
||||
COALESCE(project_id, ''),
|
||||
COALESCE(usage_profile_id, ''),
|
||||
document_type
|
||||
ORDER BY updated_at DESC, revision DESC, id DESC
|
||||
) AS position
|
||||
FROM curated_documents
|
||||
WHERE status = 'active'
|
||||
) ranked
|
||||
WHERE position = 1
|
||||
)
|
||||
"""
|
||||
)
|
||||
with op.batch_alter_table("curated_documents") as batch:
|
||||
batch.drop_constraint("uq_curated_document", type_="unique")
|
||||
op.execute(
|
||||
"""
|
||||
CREATE UNIQUE INDEX uq_active_curated_document
|
||||
ON curated_documents (
|
||||
scope,
|
||||
COALESCE(project_id, ''),
|
||||
COALESCE(usage_profile_id, ''),
|
||||
document_type
|
||||
)
|
||||
WHERE status = 'active'
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("uq_active_curated_document", table_name="curated_documents")
|
||||
with op.batch_alter_table("curated_documents") as batch:
|
||||
batch.create_unique_constraint(
|
||||
"uq_curated_document",
|
||||
["scope", "project_id", "usage_profile_id", "document_type"],
|
||||
)
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.drop_index("ix_memory_references_curation_status")
|
||||
batch.drop_column("latest_curation_job_id")
|
||||
batch.drop_column("superseded_by_json")
|
||||
batch.drop_column("covered_by_json")
|
||||
batch.drop_column("curated_at")
|
||||
batch.drop_column("curated_revision")
|
||||
batch.drop_column("curation_status")
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Persist upload activity provenance."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260806_0013"
|
||||
down_revision: str | Sequence[str] | None = "20260806_0012"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("upload_tasks") as batch:
|
||||
batch.add_column(sa.Column("usage_profile_id", sa.String(length=36), nullable=True))
|
||||
batch.add_column(
|
||||
sa.Column("origin", sa.String(length=20), nullable=False, server_default="user")
|
||||
)
|
||||
batch.create_index("ix_upload_tasks_usage_profile_id", ["usage_profile_id"])
|
||||
batch.create_foreign_key(
|
||||
"fk_upload_tasks_usage_profile_id",
|
||||
"usage_profiles",
|
||||
["usage_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("upload_tasks") as batch:
|
||||
batch.drop_constraint("fk_upload_tasks_usage_profile_id", type_="foreignkey")
|
||||
batch.drop_index("ix_upload_tasks_usage_profile_id")
|
||||
batch.drop_column("origin")
|
||||
batch.drop_column("usage_profile_id")
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Cumulative curation coverage marks and model fallback candidates."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260808_0014"
|
||||
down_revision: str | Sequence[str] | None = "20260806_0013"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.add_column(sa.Column("covered_reason", sa.String(length=16), nullable=True))
|
||||
batch.add_column(sa.Column("reviewed_revision", sa.Integer(), nullable=True))
|
||||
batch.add_column(sa.Column("reviewed_job_id", sa.String(length=36), nullable=True))
|
||||
|
||||
with op.batch_alter_table("curation_model_configs") as batch:
|
||||
batch.add_column(
|
||||
sa.Column("candidates_json", sa.Text(), nullable=False, server_default="[]")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"candidate_cooldown_seconds",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="600",
|
||||
)
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"curation_model_candidate_states",
|
||||
sa.Column("model_name", sa.String(length=300), primary_key=True),
|
||||
sa.Column("cooling_until", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("last_error_code", sa.String(length=100), nullable=True),
|
||||
sa.Column("last_error_message", sa.Text(), nullable=True),
|
||||
sa.Column("last_success_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("last_failure_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_curation_model_candidate_states_cooling_until",
|
||||
"curation_model_candidate_states",
|
||||
["cooling_until"],
|
||||
)
|
||||
|
||||
# Existing covered memories were all covered through citation; backfill the reason so
|
||||
# the UI does not show them as unexplained.
|
||||
op.execute(
|
||||
"UPDATE memory_references SET covered_reason = 'cited' "
|
||||
"WHERE curation_status = 'covered' AND covered_reason IS NULL"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(
|
||||
"ix_curation_model_candidate_states_cooling_until",
|
||||
table_name="curation_model_candidate_states",
|
||||
)
|
||||
op.drop_table("curation_model_candidate_states")
|
||||
|
||||
with op.batch_alter_table("curation_model_configs") as batch:
|
||||
batch.drop_column("candidate_cooldown_seconds")
|
||||
batch.drop_column("candidates_json")
|
||||
|
||||
with op.batch_alter_table("memory_references") as batch:
|
||||
batch.drop_column("reviewed_job_id")
|
||||
batch.drop_column("reviewed_revision")
|
||||
batch.drop_column("covered_reason")
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Curation output language setting."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260812_0015"
|
||||
down_revision: str | Sequence[str] | None = "20260808_0014"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("curation_settings") as batch:
|
||||
batch.add_column(
|
||||
sa.Column(
|
||||
"output_language",
|
||||
sa.String(length=16),
|
||||
nullable=False,
|
||||
server_default="auto",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("curation_settings") as batch:
|
||||
batch.drop_column("output_language")
|
||||
Reference in New Issue
Block a user