"""add hierarchical expense memory governance fields Revision ID: 20260716_0007 Revises: 20260716_0006 Create Date: 2026-07-16 12:05:00 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "20260716_0007" down_revision: str | None = "20260716_0006" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.add_column( "memory_entries", sa.Column( "origin_type", sa.String(length=24), server_default="learned", nullable=False, ), ) op.add_column( "memory_entries", sa.Column("managed_by", sa.String(length=255), nullable=True), ) op.add_column( "memory_entries", sa.Column("managed_at", sa.DateTime(timezone=True), nullable=True), ) op.add_column( "memory_entries", sa.Column("management_reason", sa.String(length=255), nullable=True), ) op.add_column( "memory_entries", sa.Column("policy_version", sa.String(length=64), nullable=True), ) op.drop_constraint( "ck_memory_entries_scope_type", "memory_entries", type_="check", ) op.create_check_constraint( "ck_memory_entries_scope_type", "memory_entries", "scope_type IN ('user', 'department', 'enterprise')", ) op.create_check_constraint( "ck_memory_entries_origin_type", "memory_entries", "origin_type IN ('learned', 'admin_managed')", ) op.create_check_constraint( "ck_memory_entries_scope_origin", "memory_entries", "(scope_type = 'user' AND origin_type = 'learned') OR " "(scope_type IN ('department', 'enterprise') " "AND origin_type = 'admin_managed')", ) op.create_check_constraint( "ck_memory_entries_enterprise_scope", "memory_entries", "scope_type != 'enterprise' OR scope_id = tenant_id", ) op.create_check_constraint( "ck_memory_entries_management_audit", "memory_entries", "(origin_type = 'learned' AND managed_by IS NULL " "AND managed_at IS NULL AND management_reason IS NULL) OR " "(origin_type = 'admin_managed' AND managed_by IS NOT NULL " "AND length(trim(managed_by)) > 0 AND managed_at IS NOT NULL " "AND management_reason IS NOT NULL " "AND length(trim(management_reason)) > 0 " "AND policy_version IS NOT NULL AND length(trim(policy_version)) > 0)", ) def downgrade() -> None: op.drop_constraint( "ck_memory_entries_management_audit", "memory_entries", type_="check", ) op.drop_constraint( "ck_memory_entries_enterprise_scope", "memory_entries", type_="check", ) op.drop_constraint( "ck_memory_entries_scope_origin", "memory_entries", type_="check", ) op.drop_constraint( "ck_memory_entries_origin_type", "memory_entries", type_="check", ) op.drop_constraint( "ck_memory_entries_scope_type", "memory_entries", type_="check", ) # 0006 只认识个人记忆。组织记忆无法无损映射回旧结构,因此降级时 # 先解除组织版本间的自引用并删除组织作用域记录,个人记忆完整保留。 op.execute( "UPDATE memory_entries SET superseded_by_id = NULL " "WHERE scope_type IN ('department', 'enterprise')" ) op.execute( "DELETE FROM memory_entries " "WHERE scope_type IN ('department', 'enterprise')" ) op.create_check_constraint( "ck_memory_entries_scope_type", "memory_entries", "scope_type = 'user'", ) op.drop_column("memory_entries", "policy_version") op.drop_column("memory_entries", "management_reason") op.drop_column("memory_entries", "managed_at") op.drop_column("memory_entries", "managed_by") op.drop_column("memory_entries", "origin_type")