Files
hunjing/backend/alembic/versions/008_形仪域_真形锚点_主仪化仪定相_声音档案与克隆授权.py
T
chenxin b419e0afde M7:形仪与语音——真形锚点、候选定相、声音档案与克隆授权、ASR 接入
- 形仪域 domains/xingyi:form_anchors(稳定辨识特征/年龄表现/许可禁忌/可变范围——生成内容不可静默覆盖锚点);forms(主仪/化仪/文字形仪,candidate→adopt 定相治理:未定相不进呈现位、主仪唯一、新主仪自动退役旧主仪);voice_profiles(三来源,授权克隆必填用途与传播范围,revoke-grant 撤回立即生效,assert-usable 合成前置校验拒绝撤回/过期)
- ASR:元炁 SenseVoice /v1/audio/transcriptions 透传(Whisper 兼容 multipart);YUANQI_BASE_URL 未配置时 503 不降级不伪造——多模态关闭纪律
- 前端形仪工坊(人物详情页):真形锚点编辑(特征逐行解析)/候选登记定相废弃/声音档案建立与授权撤回;头像呈现位改走定相主仪(forms/primary);xingyi api 库 + mock + 组件 4 用例
- 退役:旧 avatar 模块(services/endpoints/avatar、avatar_dir 配置、avatar_url 字段、前端直传组件与测试)
- 验收(test_m7_xingyi 三用例):未定相不成为正式形仪(含主仪唯一与旧主仪 retired 断言);克隆授权可撤回(缺用途拒/撤回即拒/过期同理/预设不受约束);关闭全部多模态后文字相伴完整可用(ASR 503 时镜树缘契百工照常——回归)
- 四套件:后端 376、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 008 净库+±1 干净
2026-08-27 20:26:52 +08:00

91 lines
4.5 KiB
Python

"""形仪域_真形锚点_主仪化仪定相_声音档案与克隆授权
Revision ID: 008
Revises: 007
Create Date: 2026-08-27 20:15:34.665285
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '008'
down_revision: Union[str, Sequence[str], None] = '007'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('form_anchors',
sa.Column('id', sa.String(length=80), nullable=False),
sa.Column('character_id', sa.String(length=80), nullable=False),
sa.Column('features_json', sa.Text(), nullable=False),
sa.Column('age_presentation', sa.String(length=40), nullable=False),
sa.Column('permissions_json', sa.Text(), nullable=False),
sa.Column('mutable_scope_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),
sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_form_anchors_character_id'), 'form_anchors', ['character_id'], unique=False)
op.create_table('forms',
sa.Column('id', sa.String(length=80), nullable=False),
sa.Column('character_id', sa.String(length=80), nullable=False),
sa.Column('kind', sa.String(length=20), nullable=False),
sa.Column('status', sa.String(length=20), nullable=False),
sa.Column('source', sa.String(length=20), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.Column('media_ref', sa.String(length=500), nullable=True),
sa.Column('live2d_json', sa.Text(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('adopted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_forms_character_id'), 'forms', ['character_id'], unique=False)
op.create_index(op.f('ix_forms_kind'), 'forms', ['kind'], unique=False)
op.create_index(op.f('ix_forms_status'), 'forms', ['status'], unique=False)
op.create_table('voice_profiles',
sa.Column('id', sa.String(length=80), nullable=False),
sa.Column('character_id', sa.String(length=80), nullable=False),
sa.Column('source', sa.String(length=30), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('params_json', sa.Text(), nullable=False),
sa.Column('grant_purpose', sa.String(length=200), nullable=True),
sa.Column('grant_expires_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('grant_distribution', sa.String(length=200), nullable=True),
sa.Column('grant_revoked_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_voice_profiles_character_id'), 'voice_profiles', ['character_id'], unique=False)
op.create_index(op.f('ix_voice_profiles_is_active'), 'voice_profiles', ['is_active'], unique=False)
op.add_column('pending_confirmations', sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True))
op.add_column('pending_confirmations', sa.Column('brief_json', sa.Text(), server_default='{}', nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('pending_confirmations', 'brief_json')
op.drop_column('pending_confirmations', 'expires_at')
op.drop_index(op.f('ix_voice_profiles_is_active'), table_name='voice_profiles')
op.drop_index(op.f('ix_voice_profiles_character_id'), table_name='voice_profiles')
op.drop_table('voice_profiles')
op.drop_index(op.f('ix_forms_status'), table_name='forms')
op.drop_index(op.f('ix_forms_kind'), table_name='forms')
op.drop_index(op.f('ix_forms_character_id'), table_name='forms')
op.drop_table('forms')
op.drop_index(op.f('ix_form_anchors_character_id'), table_name='form_anchors')
op.drop_table('form_anchors')
# ### end Alembic commands ###