✨ feat: WP-03 三界归真作用域与消息版本谱系(ADR-020 落地)
- 迁移 5:conversation.realm(默认真界,检查约束);message.realm 快照、canonized_at、parent_message_id 自引用与 variant_kind(origin/revision/candidate)
- 界域端点:GET conversation(realm/当前分支)与 POST realm(入境/出境显式切换,留审计——ADR-020 切换必须可见)
- 归真端点 POST /v1/messages/{id}/canonizations:scope 必须与消息界域一致(不一致 422);幂等(重复归真 200 原状态);境界归真产出 world.canonized Outbox 事件(世界知识候选入口,WP-04 消费);幻境归真仅标记试演记录
- 编辑版本 POST /v1/messages/{id}/revisions:新消息 variant=revision 引用原消息(markdown 修订同样经灵台引擎规范化),原消息永不修改
- 测试 44 项全绿(+8):界域快照、归真正/反/幂等、幻境试演、revision 原消息不变、跨域 401/403/404 fail-closed
This commit is contained in:
1 parent
37c53862c4
commit
603a683c14
6 files changed
+432
-6
No files matched your search
@@ -206,3 +206,96 @@ async def set_current_branch(
|
||||
db, domain.id, conversation_id, body.get("branch_id", "")
|
||||
)
|
||||
return {"id": conversation.id, "current_branch_id": conversation.current_branch_id or ""}
|
||||
|
||||
|
||||
@router.get("/{conversation_id}")
|
||||
async def get_conversation(
|
||||
conversation_id: str,
|
||||
db: DbSession,
|
||||
user: Annotated[User, Depends(get_current_user)],
|
||||
) -> dict[str, object]:
|
||||
domain = await _domain_of(db, user)
|
||||
conversation = await service.require_conversation(db, domain.id, conversation_id)
|
||||
from app.conversations.schemas import ConversationOut
|
||||
|
||||
return ConversationOut(
|
||||
id=conversation.id,
|
||||
title=conversation.title,
|
||||
current_branch_id=conversation.current_branch_id,
|
||||
created_at=conversation.created_at.isoformat(),
|
||||
).model_dump() | {"realm": conversation.realm}
|
||||
|
||||
|
||||
@router.post("/{conversation_id}/realm")
|
||||
async def set_realm(
|
||||
conversation_id: str,
|
||||
db: DbSession,
|
||||
user: Annotated[User, Depends(require_state_change)],
|
||||
request: Request,
|
||||
body: dict[str, str],
|
||||
) -> dict[str, str]:
|
||||
"""入境/出境:切换对话界域(显式、留审计)。"""
|
||||
domain = await _domain_of(db, user)
|
||||
conversation = await service.set_realm(
|
||||
db,
|
||||
domain.id,
|
||||
conversation_id,
|
||||
body.get("realm", ""),
|
||||
getattr(request.state, "trace_id", None),
|
||||
)
|
||||
return {"id": conversation.id, "realm": conversation.realm}
|
||||
|
||||
|
||||
message_router = APIRouter(prefix="/v1/messages", tags=["messages"])
|
||||
|
||||
|
||||
@message_router.post("/{message_id}/canonizations", status_code=201, response_model=None)
|
||||
async def canonize_message(
|
||||
message_id: str,
|
||||
db: DbSession,
|
||||
user: Annotated[User, Depends(require_state_change)],
|
||||
request: Request,
|
||||
body: dict[str, str],
|
||||
) -> JSONResponse | dict[str, str | None]:
|
||||
"""归真:携带 canonical_scope(ADR-020);重复归真幂等返回原状态(200)。"""
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
domain = await _domain_of(db, user)
|
||||
message, replayed = await service.canonize_message(
|
||||
db, domain.id, message_id, body.get("scope", ""), getattr(request.state, "trace_id", None)
|
||||
)
|
||||
payload = {
|
||||
"message_id": message.id,
|
||||
"canonical_scope": message.realm,
|
||||
"canonized_at": message.canonized_at.isoformat() if message.canonized_at else None,
|
||||
}
|
||||
if replayed:
|
||||
return JSONResponse(status_code=200, content=payload)
|
||||
return payload
|
||||
|
||||
|
||||
@message_router.post("/{message_id}/revisions", status_code=201)
|
||||
async def revise_message(
|
||||
message_id: str,
|
||||
db: DbSession,
|
||||
user: Annotated[User, Depends(require_state_change)],
|
||||
request: Request,
|
||||
body: MessageIn,
|
||||
) -> dict[str, object]:
|
||||
"""编辑版本:新消息引用原消息;原消息永不修改。"""
|
||||
domain = await _domain_of(db, user)
|
||||
revision = await service.revise_message(
|
||||
db, domain.id, message_id, body, getattr(request.state, "trace_id", None)
|
||||
)
|
||||
from app.conversations.schemas import MessageOut
|
||||
|
||||
return MessageOut(
|
||||
id=revision.id,
|
||||
conversation_id=revision.conversation_id,
|
||||
branch_id=revision.branch_id,
|
||||
role=revision.role,
|
||||
content=revision.content,
|
||||
parser_version=revision.parser_version,
|
||||
created_at=revision.created_at.isoformat(),
|
||||
).model_dump() | {"parent_message_id": revision.parent_message_id,
|
||||
"variant_kind": revision.variant_kind}
|
||||
Reference in new issue
Block a user