- ContentBlock 全 21 类型 Pydantic 判别联合(与 openapi 契约一一对齐,extra=forbid);系统产生类 block(tool_call/tool_result/agent_status/approval_request/artifact)拒绝客户端伪造(契约 3.5) - 迁移 4:run_event 表((run_id, sequence) 唯一、事件类型检查约束)+ conversation.current_branch_id - Run 事件持久化:状态事件、每 block completed、message.completed 与权威状态同事务写入;sequence 单调从 0;SSE 改为重放持久化事件(不再现场构造),delta/progress 留待真实模型流(WP-05),不伪造 - GET messages:opaque cursor 分页((created_at,id) 复合键、base64 编码、limit 1-200、has_more) - branches:列表 + 当前路径切换(采用≠归真);跨会话/不存在分支 422 fail-closed - 测试 36 项全绿:21 类型接受、runtime block 拒绝、事件重放序号单调、分页无重叠、cursor 非法拒绝、分支切换与跨域
50 lines
2.2 KiB
Python
50 lines
2.2 KiB
Python
"""Run 事件流与当前分支
|
|
|
|
Revision ID: 5cf6d328c10a
|
|
Revises: 4cc407ec8268
|
|
Create Date: 2026-09-10 13:13:16.305296
|
|
|
|
"""
|
|
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 = '5cf6d328c10a'
|
|
down_revision: str | Sequence[str] | None = '4cc407ec8268'
|
|
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('run_event',
|
|
sa.Column('id', sa.String(), nullable=False),
|
|
sa.Column('run_id', sa.String(), nullable=False),
|
|
sa.Column('sequence', sa.Integer(), nullable=False),
|
|
sa.Column('type', sa.String(), nullable=False),
|
|
sa.Column('data', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
|
sa.CheckConstraint("type IN ('content.block.started', 'content.block.delta', 'content.block.progress', 'content.block.completed', 'content.block.failed', 'content.block.cancelled', 'message.completed', 'run.status.changed')", name=op.f('ck_run_event_type_values')),
|
|
sa.ForeignKeyConstraint(['run_id'], ['run.id'], name=op.f('fk_run_event_run'), ondelete='cascade'),
|
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_run_event'))
|
|
)
|
|
op.create_index('uq_run_event_run_sequence', 'run_event', ['run_id', 'sequence'], unique=True)
|
|
op.add_column('conversation', sa.Column('current_branch_id', sa.String(), nullable=True))
|
|
op.create_foreign_key(op.f('fk_conversation_branch'), 'conversation', 'branch', ['current_branch_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_conversation_branch'), 'conversation', type_='foreignkey')
|
|
op.drop_column('conversation', 'current_branch_id')
|
|
op.drop_index('uq_run_event_run_sequence', table_name='run_event')
|
|
op.drop_table('run_event')
|
|
# ### end Alembic commands ###
|