✨ 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
@@ -154,6 +154,9 @@ class Conversation(Base):
|
||||
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
title: Mapped[str] = mapped_column(String, nullable=False, default="未命名对话")
|
||||
current_branch_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("branch.id", ondelete="set null", use_alter=True), nullable=True
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
@@ -249,3 +252,31 @@ class IdempotencyRecord(Base):
|
||||
__table_args__ = (
|
||||
Index("uq_idempotency_owner_route_key", "owner_domain_id", "route", "key", unique=True),
|
||||
)
|
||||
|
||||
|
||||
class RunEvent(Base):
|
||||
"""Run 事件流:持久化后可重放;sequence 在 Run 内单调(契约第 5 节)。"""
|
||||
|
||||
__tablename__ = "run_event"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: new_id("event_"))
|
||||
run_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("run.id", ondelete="cascade"), nullable=False
|
||||
)
|
||||
sequence: Mapped[int] = mapped_column(nullable=False)
|
||||
type: Mapped[str] = mapped_column(String, nullable=False)
|
||||
data: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
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="type_values",
|
||||
),
|
||||
# Run 内 sequence 单调且唯一(重放去重的服务端保证)
|
||||
Index("uq_run_event_run_sequence", "run_id", "sequence", unique=True),
|
||||
)
|
||||
Reference in new issue
Block a user