M6:缘契、人格与世界——契约版本链、铸魂分灵、河图境界与三界分流
- 缘契域 domains/yuanqi:covenants(当前版)+ covenant_events(不可变历史);提议—问心—铭契—撤回全链;边界变更立即生效;多维关系并存,服务层拒绝 affinity/好感度单一键;浪漫维度成人资格标记位 - 道体铸魂与分灵 domains/jingsheng/soul.py:soul_forgings 确认流(锚点变更 pending→confirmed 应用并签发新魂籍版本,确认前人物锚点不变);分灵创建新人物(继承资料与谱系 lineage 链、不继承原身份,魂籍从 v1 起) - 河图域 domains/hetu:persistent_worlds 境界 + world_entities/world_relations 境界作用域重建 + 世谱挂接;境界正史视图(scope=persistent_world 归真集合);canonize 后置推进正史计数 - 三界分流:reality→息壤提取(不变);persistent_world→境界实录+正史推进;simulation→只记试演实录;factual_records 增 scope 列——境界/幻境实录与真界本相隔离 - 正影打通:引文 target_type=memory 时被引记忆 superseded → 返回 successor_memory_id 提示,不静默替换 - 前端:人物详情页缘契面板(提议维度选择/铭契/撤回/事件史)+ 世界页重建(境界目录/实体/正史概览)+ covenants/worlds api 库 - 退役:旧 personality/drive/evolution/world 模块、服务、端点、仓储、schema、前端五页与组件样式;迁移 007 退役九张旧表与 characters.current_personality_version_id - 验收(test_m6_domains 六用例):多维无单一好感度/铭契版本链/事件不可抹除/撤回立即生效/边界即时/铸魂确认流+魂籍版本/分灵谱系/境界正史不进真界(实录 scope 隔离+真界候选=0)/引文正影后继 - 四套件:后端 379、前端 199、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 007 净库+±1 干净
This commit is contained in:
1 parent
684261f4b4
commit
7e23d6fa84
108 files changed
+2600
-9732
No files matched your search
@@ -29,89 +29,6 @@ async def test_health_and_defaults(client: AsyncClient) -> None:
|
||||
character = await client.get("/api/v1/characters/active")
|
||||
assert character.status_code == 200
|
||||
assert character.json()["id"].startswith("character_")
|
||||
personality = await client.get("/api/v1/personality/current")
|
||||
assert personality.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_personality_propose_approve_reject_and_rollback(client: AsyncClient) -> None:
|
||||
# 初始当前人格为 v1
|
||||
before = (await client.get("/api/v1/personality/current")).json()
|
||||
assert before["version"] == 1
|
||||
|
||||
# 空字段提议被拒(invalid_request)
|
||||
empty = await client.post(
|
||||
"/api/v1/personality/propose-update",
|
||||
json={"fields": {}, "reason": "空提议"},
|
||||
)
|
||||
assert empty.status_code == 400
|
||||
|
||||
# 提交 pending 建议(覆盖 string + json 字段)
|
||||
propose = await client.post(
|
||||
"/api/v1/personality/propose-update",
|
||||
json={
|
||||
"fields": {"identity_profile": "测试身份", "traits": {"warmth": 0.5}},
|
||||
"reason": "联调人格建议",
|
||||
},
|
||||
)
|
||||
assert propose.status_code == 201
|
||||
proposal = propose.json()
|
||||
assert proposal["status"] == "pending"
|
||||
assert proposal["fields"] == {"identity_profile": "测试身份", "traits": {"warmth": 0.5}}
|
||||
proposal_id = proposal["proposal_id"]
|
||||
|
||||
# 审核通过:派生新版本并切换当前指针,字段覆盖生效
|
||||
approve = await client.post(f"/api/v1/personality/proposals/{proposal_id}/approve")
|
||||
assert approve.status_code == 200
|
||||
applied = approve.json()
|
||||
assert applied["status"] == "applied"
|
||||
assert applied["resulting_version_id"]
|
||||
|
||||
after = (await client.get("/api/v1/personality/current")).json()
|
||||
assert after["version"] == before["version"] + 1
|
||||
assert after["identity_profile"] == "测试身份"
|
||||
assert after["traits"]["warmth"] == 0.5
|
||||
new_version_id = after["id"]
|
||||
|
||||
# 重复审核同一建议 → conflict
|
||||
again = await client.post(f"/api/v1/personality/proposals/{proposal_id}/approve")
|
||||
assert again.status_code == 409
|
||||
|
||||
# 驳回另一条建议:不生成版本,不改当前指针
|
||||
propose2 = await client.post(
|
||||
"/api/v1/personality/propose-update",
|
||||
json={"fields": {"expression_style": "被驳回"}, "reason": "测试驳回"},
|
||||
)
|
||||
pid2 = propose2.json()["proposal_id"]
|
||||
reject = await client.post(f"/api/v1/personality/proposals/{pid2}/reject")
|
||||
assert reject.status_code == 200
|
||||
assert reject.json()["status"] == "rejected"
|
||||
assert (await client.get("/api/v1/personality/current")).json()["id"] == new_version_id
|
||||
|
||||
# 回滚到 v1:当前指针切回,旧版本数不变
|
||||
versions = (await client.get("/api/v1/personality/versions")).json()
|
||||
v1 = next(v for v in versions["items"] if v["version"] == 1)
|
||||
rollback = await client.post(f"/api/v1/personality/versions/{v1['id']}/rollback")
|
||||
assert rollback.status_code == 200
|
||||
assert (await client.get("/api/v1/personality/current")).json()["id"] == v1["id"]
|
||||
|
||||
# 回滚到不存在的版本 → 404
|
||||
missing = await client.post("/api/v1/personality/versions/personality_nope/rollback")
|
||||
assert missing.status_code == 404
|
||||
|
||||
# 写操作均入起居注(personality_core 枢)
|
||||
# /admin/* 强制 require_admin 鉴权,联调测试在此局部绕过(鉴权本身覆盖见 test_admin_auth)
|
||||
app.dependency_overrides[require_admin] = lambda: None
|
||||
try:
|
||||
audit = await client.get("/api/v1/admin/audit/timeline?pivot=personality_core")
|
||||
assert audit.status_code == 200
|
||||
actions = {item["action"] for item in audit.json()["items"]}
|
||||
assert "personality.propose" in actions
|
||||
assert "personality.proposal.approved" in actions
|
||||
assert "personality.proposal.rejected" in actions
|
||||
assert "personality.rollback" in actions
|
||||
finally:
|
||||
app.dependency_overrides.pop(require_admin, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -147,38 +64,6 @@ async def test_mcp_server_crud(client: AsyncClient) -> None:
|
||||
assert deleted.status_code == 204
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_world_entities_crud(client: AsyncClient) -> None:
|
||||
"""河图世界实体 CRUD——覆盖 services/world 路径。"""
|
||||
created = await client.post(
|
||||
"/api/v1/world/entities",
|
||||
json={
|
||||
"name": "测试地点",
|
||||
"type": "location",
|
||||
"attributes": {"climate": "温暖"},
|
||||
"state_type": "fixed",
|
||||
"sources": [],
|
||||
},
|
||||
)
|
||||
assert created.status_code == 200
|
||||
entity_id = created.json()["id"]
|
||||
|
||||
listed = await client.get("/api/v1/world/entities")
|
||||
assert listed.status_code == 200
|
||||
assert len(listed.json()["items"]) >= 1
|
||||
|
||||
fetched = await client.get(f"/api/v1/world/entities/{entity_id}")
|
||||
assert fetched.status_code == 200
|
||||
assert fetched.json()["name"] == "测试地点"
|
||||
|
||||
# 世界实体无 PATCH/DELETE API,仅测试 GET 和建表
|
||||
deleted = await client.delete(f"/api/v1/world/entities/{entity_id}")
|
||||
assert deleted.status_code in (404, 405) # 无删除端点,接受未实现
|
||||
|
||||
gone = await client.get(f"/api/v1/world/entities/{entity_id}")
|
||||
assert gone.status_code in (200, 404)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_preferences_flow(client: AsyncClient) -> None:
|
||||
"""偏好设置读写——覆盖 services/preferences 路径。"""
|
||||
|
||||
Reference in new issue
Block a user