Files
hunjing/backend/alembic/versions/f7bf2b464f1f_插件生态_registry_版本与安装.py
T
chenxin 9ef2568c3b ✨ feat: WP-07 插件生态——Registry、三态分离与调用门(ADR-001/008)
- 迁移 14:plugin(kind 四形态、撤架三态)、plugin_version(Manifest JSONB + 规范化内容哈希签名、(plugin,version) 唯一)、plugin_installation(enabled + granted_permissions、(domain,plugin) 唯一)
- Registry:发布版本(Manifest 必须声明 permissions,签名=canonical 哈希);撤架阻断新调用与新安装,历史审计保留(验证:撤架后 invoked 审计仍在)
- 三态分离(购买≠安装≠启用≠授权):安装默认 disabled+零授权;启用必须显式授予权限子集(越界 422);调用门逐级校验(未装/未启/撤架各 403)
- 升级:Manifest 新增权限必须重新确认(未确认 422 且明示新增项);确认后升级成功
- 调用:按 Manifest response_template 真实执行(声明式确定性回填);remote/isolated 形态明示 501 随沙箱交付(WP-14),不静默降级;审计含版本哈希(快照语义)
- 权限标识统一 group:item 格式
- 测试 86 项全绿(+4):三态流、越界、升级确认、撤架阻断与历史保留
2026-09-12 10:18:20 +08:00

83 lines
4.6 KiB
Python

"""插件生态:Registry、版本与安装
Revision ID: f7bf2b464f1f
Revises: bd36c0a63816
Create Date: 2026-09-12 10:15:58.892690
"""
from collections.abc import Sequence
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
# revision identifiers, used by Alembic.
revision: str = 'f7bf2b464f1f'
down_revision: str | Sequence[str] | None = 'bd36c0a63816'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('plugin',
sa.Column('name', sa.String(), nullable=False),
sa.Column('publisher_domain_id', sa.String(), nullable=False),
sa.Column('kind', sa.String(), nullable=False),
sa.Column('status', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.CheckConstraint("kind IN ('declarative', 'remote', 'isolated_process', 'container')", name=op.f('ck_plugin_kind_values')),
sa.CheckConstraint("status IN ('published', 'suspended', 'removed')", name=op.f('ck_plugin_status_values')),
sa.ForeignKeyConstraint(['publisher_domain_id'], ['personal_domain.id'], name=op.f('fk_plugin_personal_domain'), ondelete='cascade'),
sa.PrimaryKeyConstraint('name', name=op.f('pk_plugin'))
)
op.create_index(op.f('ix_plugin_publisher_domain_id'), 'plugin', ['publisher_domain_id'], unique=False)
op.create_table('plugin_version',
sa.Column('id', sa.String(), nullable=False),
sa.Column('plugin_name', sa.String(), nullable=False),
sa.Column('version', sa.String(), nullable=False),
sa.Column('manifest', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column('content_hash', sa.String(), nullable=False),
sa.Column('status', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.CheckConstraint("status IN ('published', 'suspended', 'removed')", name=op.f('ck_plugin_version_status_values')),
sa.ForeignKeyConstraint(['plugin_name'], ['plugin.name'], name=op.f('fk_plugin_version_plugin'), ondelete='cascade'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_plugin_version'))
)
op.create_index(op.f('ix_plugin_version_plugin_name'), 'plugin_version', ['plugin_name'], unique=False)
op.create_index('uq_plugin_version', 'plugin_version', ['plugin_name', 'version'], unique=True)
op.create_table('plugin_installation',
sa.Column('id', sa.String(), nullable=False),
sa.Column('domain_id', sa.String(), nullable=False),
sa.Column('plugin_name', sa.String(), nullable=False),
sa.Column('version_id', sa.String(), nullable=False),
sa.Column('enabled', sa.Boolean(), nullable=False),
sa.Column('granted_permissions', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['domain_id'], ['personal_domain.id'], name=op.f('fk_plugin_installation_personal_domain'), ondelete='cascade'),
sa.ForeignKeyConstraint(['plugin_name'], ['plugin.name'], name=op.f('fk_plugin_installation_plugin'), ondelete='cascade'),
sa.ForeignKeyConstraint(['version_id'], ['plugin_version.id'], name=op.f('fk_plugin_installation_plugin_version'), ondelete='restrict'),
sa.PrimaryKeyConstraint('id', name=op.f('pk_plugin_installation'))
)
op.create_index(op.f('ix_plugin_installation_domain_id'), 'plugin_installation', ['domain_id'], unique=False)
op.create_index('uq_plugin_installation', 'plugin_installation', ['domain_id', 'plugin_name'], unique=True)
op.create_foreign_key(op.f('fk_project_project_version'), 'project', 'project_version', ['current_version_id'], ['id'], ondelete='set null', use_alter=True)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('fk_project_project_version'), 'project', type_='foreignkey')
op.drop_index('uq_plugin_installation', table_name='plugin_installation')
op.drop_index(op.f('ix_plugin_installation_domain_id'), table_name='plugin_installation')
op.drop_table('plugin_installation')
op.drop_index('uq_plugin_version', table_name='plugin_version')
op.drop_index(op.f('ix_plugin_version_plugin_name'), table_name='plugin_version')
op.drop_table('plugin_version')
op.drop_index(op.f('ix_plugin_publisher_domain_id'), table_name='plugin')
op.drop_table('plugin')
# ### end Alembic commands ###