✨ feat: WP-03 主体——ContentBlock 全类型、Run 事件重放、分页与分支(WP-03)
- 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 非法拒绝、分支切换与跨域
This commit is contained in:
1 parent
491136c770
commit
37c53862c4
6 files changed
+627
-23
No files matched your search
@@ -0,0 +1,49 @@
|
||||
"""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 ###
|
||||
Reference in new issue
Block a user