- 认证:Web Session(__Host- Cookie、HttpOnly/Secure/SameSite=Lax、CSPRNG opaque token 数据库只存哈希)、CSRF 双提交、Origin 校验、dev 登录/登出端点;未认证 401、缺 CSRF 403
- 中间件:Hunjing-Request-ID 回显/生成(request_ UUIDv4)+ W3C traceparent 校验透传;JSON 结构化日志与敏感头脱敏
- 迁移 2:conversation/branch/message/run/idempotency_record 五表(含检查约束与复合索引);Session 补 token/csrf 哈希列
- 垂直切片:POST /v1/conversations/{id}/messages 按 Idempotency-Key 幂等(同键同体 200 重放、异体 409),同事务原子写 Message+Run+Audit+Outbox+幂等记录;ContentBlock text/markdown 判别联合(未知类型 422)
- SSE:GET /v1/runs/{id}/events 返回快照与终止状态事件 envelope
- 测试 26 项全绿:IDOR 跨域 404 fail-closed、失败注入回滚无孤儿(Run 崩溃后 message/run/audit/outbox/幂等全零)、SSE 事件序、auth 边界;ruff/mypy strict 干净
- 修复真实问题:asyncpg 跨事件循环(anyio/pytest-asyncio 双 loop、模块级 engine 改为 app 实例持有)、httpx 拒存 http 下 Secure Cookie
61 lines
1.4 KiB
Python
61 lines
1.4 KiB
Python
"""对话域 schema:ContentBlock 判别联合(垂直切片子集)。
|
||
|
||
完整 21 类型随 WP-03 落地;本切片登记 text/markdown 并拒绝未知类型,
|
||
契约真源为 openapi/openapi.yaml。
|
||
"""
|
||
|
||
from typing import Annotated, Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
|
||
|
||
class BlockBase(BaseModel):
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
id: str | None = None # 创建请求可省略,由服务端生成
|
||
status: Literal["pending", "streaming", "completed", "failed", "cancelled"] | None = None
|
||
|
||
|
||
class TextBlock(BlockBase):
|
||
type: Literal["text"]
|
||
text: str
|
||
language: str | None = None
|
||
|
||
|
||
class MarkdownBlock(BlockBase):
|
||
type: Literal["markdown"]
|
||
text: str
|
||
flavor: Literal["hunjing-gfm-v1"] = "hunjing-gfm-v1"
|
||
|
||
|
||
ContentBlock = Annotated[TextBlock | MarkdownBlock, Field(discriminator="type")]
|
||
|
||
|
||
class MessageIn(BaseModel):
|
||
model_config = ConfigDict(extra="forbid")
|
||
|
||
content: list[ContentBlock] = Field(min_length=1, max_length=64)
|
||
start_run: bool = True
|
||
|
||
|
||
class MessageOut(BaseModel):
|
||
id: str
|
||
conversation_id: str
|
||
branch_id: str
|
||
role: str
|
||
content: list[dict[str, object]]
|
||
created_at: str
|
||
|
||
|
||
class RunOut(BaseModel):
|
||
id: str
|
||
conversation_id: str | None
|
||
status: str
|
||
created_at: str
|
||
completed_at: str | None = None
|
||
|
||
|
||
class MessageCreatedOut(BaseModel):
|
||
message: MessageOut
|
||
run: RunOut | None
|