305 lines
14 KiB
Python
305 lines
14 KiB
Python
#!/usr/bin/env python3
|
||
"""事实增量对 muse-example 真实库的集成测试。
|
||
|
||
覆盖:
|
||
- 提案登记幂等:同候选重复 deltaId 拒绝(不得覆盖);
|
||
- ChapterCommit 原子性:已批准增量与正文同一事务落账本;任一增量非法则整体回滚,
|
||
正文也不写入;
|
||
- 账本绑定:source_block_revision + command_id 与正文提交一致;
|
||
- 账本 append-only:UPDATE/DELETE 被触发器拒绝。
|
||
|
||
测试数据 unittest-delta- 前缀隔离;清理时短暂禁用账本防删触发器(try/finally 恢复)。
|
||
|
||
跑法(需 Tailscale 内网可达 muse-example):
|
||
.venv/bin/python tests/skills/决定正文候选去留/test_fact_delta_db.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import pathlib
|
||
import sys
|
||
import uuid
|
||
|
||
from psycopg.errors import RaiseException
|
||
|
||
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
||
SKILLS_DIR = PROJECT_ROOT / ".agent" / "skills"
|
||
SCRIPT_DIR = PROJECT_ROOT / "muse" / "content" / "work" / "skills" / "sovereignty" / "决定正文候选去留" / "scripts"
|
||
for path in (SCRIPT_DIR,):
|
||
if str(path) not in sys.path:
|
||
sys.path.insert(0, str(path))
|
||
|
||
from fact_delta import FactDeltaError, propose_fact_deltas # noqa: E402
|
||
from muse_db import connect # noqa: E402
|
||
from write_canonical import accept # noqa: E402
|
||
|
||
SUFFIX = uuid.uuid4().hex[:10]
|
||
WORK_TITLE = f"unittest-delta-{SUFFIX}"
|
||
BODY = f"林深把黑纹缠上手臂,异种核心在胸腔里低鸣。何岚站在舱门口没有说话。{SUFFIX}"
|
||
_command_ids: list[str] = []
|
||
_candidate_ids: list[int] = []
|
||
_delta_ids: list[int] = []
|
||
_work_id: int | None = None
|
||
|
||
|
||
def _sha(text: str) -> str:
|
||
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||
|
||
|
||
def _cleanup() -> None:
|
||
if _work_id is None:
|
||
return
|
||
with connect() as conn:
|
||
try:
|
||
conn.execute(
|
||
"ALTER TABLE example_fact_ledger DISABLE TRIGGER trg_example_fact_ledger_append_only")
|
||
conn.execute(
|
||
"ALTER TABLE example_user_decision DISABLE TRIGGER trg_example_user_decision_append_only")
|
||
try:
|
||
if _candidate_ids:
|
||
conn.execute("DELETE FROM example_user_decision WHERE candidate_id = ANY(%s)",
|
||
(_candidate_ids,))
|
||
if _command_ids:
|
||
conn.execute("DELETE FROM muse_content_command_log WHERE command_id = ANY(%s)",
|
||
(_command_ids,))
|
||
conn.execute("DELETE FROM example_fact_ledger WHERE work_id=%s", (_work_id,))
|
||
conn.execute("DELETE FROM example_fact_delta WHERE work_id=%s", (_work_id,))
|
||
conn.execute("DELETE FROM muse_content_block_source_attribution WHERE work_id=%s",
|
||
(_work_id,))
|
||
conn.execute("DELETE FROM muse_content_block WHERE work_id=%s", (_work_id,))
|
||
conn.execute("DELETE FROM muse_content_chapter WHERE work_id=%s", (_work_id,))
|
||
if _candidate_ids:
|
||
conn.execute("DELETE FROM example_candidate WHERE id = ANY(%s)",
|
||
(_candidate_ids,))
|
||
conn.execute("DELETE FROM muse_content_work WHERE id=%s", (_work_id,))
|
||
conn.commit()
|
||
finally:
|
||
conn.execute(
|
||
"ALTER TABLE example_fact_ledger ENABLE TRIGGER trg_example_fact_ledger_append_only")
|
||
conn.execute(
|
||
"ALTER TABLE example_user_decision ENABLE TRIGGER trg_example_user_decision_append_only")
|
||
conn.commit()
|
||
except Exception:
|
||
conn.rollback()
|
||
raise
|
||
|
||
|
||
def _setup() -> int:
|
||
global _work_id
|
||
with connect() as conn:
|
||
work_id = conn.execute(
|
||
"INSERT INTO muse_content_work(owner_user_id, title, status, creator) "
|
||
"VALUES (1,%s,'writing','unittest') RETURNING id", (WORK_TITLE,)).fetchone()[0]
|
||
conn.execute(
|
||
"INSERT INTO muse_content_chapter(work_id, title, order_no, creator) "
|
||
"VALUES (%s,'测试章',1,'unittest')", (work_id,))
|
||
conn.commit()
|
||
_work_id = work_id
|
||
return work_id
|
||
|
||
|
||
def _insert_candidate(work_id: int, *, version: str) -> int:
|
||
with connect() as conn:
|
||
candidate_id = conn.execute(
|
||
"INSERT INTO example_candidate(work_id, target_chapter, run_id, attempt, run_type, "
|
||
"candidate_version, candidate_sha256, candidate_body, quality_policy_version, mode, "
|
||
"source_role, state, acceptance_eligible, semantic_status, semantic_report_sha256, creator) "
|
||
"VALUES (%s,1,%s,1,'production',%s,%s,%s,'writer-production-v1','continuation','writer',"
|
||
"'passed',TRUE,'passed',%s,'unittest') RETURNING id",
|
||
(work_id, f"unittest-delta-run-{SUFFIX}-{version}", version, _sha(BODY), BODY,
|
||
_sha("sem:" + BODY))).fetchone()[0]
|
||
conn.commit()
|
||
_candidate_ids.append(candidate_id)
|
||
return candidate_id
|
||
|
||
|
||
def _command_id(tag: str) -> str:
|
||
cid = f"unittest-delta-{tag}-{SUFFIX}"
|
||
_command_ids.append(cid)
|
||
return cid
|
||
|
||
|
||
def _good_delta(delta_id="delta-1"):
|
||
return {"deltaId": delta_id, "deltaType": "hook_advanced",
|
||
"payload": {"hookId": "hook-voice", "action": "advanced",
|
||
"dueWindow": {"fromChapter": 3, "toChapter": 6}},
|
||
"evidenceQuote": "异种核心在胸腔里低鸣"}
|
||
|
||
|
||
def test_propose_is_idempotent_reject(work_id: int) -> None:
|
||
candidate_sha = _sha(BODY)
|
||
out = propose_fact_deltas(
|
||
work_id=work_id, target_chapter=1, run_id=None,
|
||
candidate_sha256_bare=candidate_sha, candidate_body=BODY,
|
||
deltas=[_good_delta("delta-prop-1"), _good_delta("delta-prop-2")])
|
||
assert out["status"] == "proposed" and len(out["proposed_ids"]) == 2
|
||
_delta_ids.extend(out["proposed_ids"])
|
||
# 同候选重复 deltaId:失败关闭,不得覆盖
|
||
try:
|
||
propose_fact_deltas(
|
||
work_id=work_id, target_chapter=1, run_id=None,
|
||
candidate_sha256_bare=candidate_sha, candidate_body=BODY,
|
||
deltas=[_good_delta("delta-prop-1")])
|
||
raise AssertionError("重复 deltaId 必须被拒绝")
|
||
except AssertionError:
|
||
raise
|
||
except FactDeltaError as exc:
|
||
assert exc.code == "FACT_DELTA_DUPLICATE_ID"
|
||
with connect(readonly=True) as conn:
|
||
count = int(conn.execute(
|
||
"SELECT COUNT(*) FROM example_fact_delta WHERE candidate_sha256=%s AND delta_id='delta-prop-1'",
|
||
(candidate_sha,)).fetchone()[0])
|
||
assert count == 1
|
||
|
||
|
||
def test_commit_applies_approved_deltas_atomically(work_id: int) -> None:
|
||
candidate_id = _insert_candidate(work_id, version="1")
|
||
command_id = _command_id("commit-1")
|
||
result = accept(candidate_id, rationale="unittest", expected_revision=0,
|
||
command_id=command_id,
|
||
approved_deltas=[_good_delta("delta-acc-1"), _good_delta("delta-acc-2")])
|
||
assert result["status"] == "accepted" and len(result["delta_ids"]) == 2
|
||
_delta_ids.extend(result["delta_ids"])
|
||
with connect(readonly=True) as conn:
|
||
ledger_rows = conn.execute(
|
||
"SELECT delta_type, subject_key, source_block_revision, command_id, "
|
||
"source_candidate_sha256 FROM example_fact_ledger WHERE work_id=%s ORDER BY id",
|
||
(work_id,)).fetchall()
|
||
delta_rows = conn.execute(
|
||
"SELECT status, source_revision, decided_by FROM example_fact_delta WHERE id = ANY(%s)",
|
||
(result["delta_ids"],)).fetchall()
|
||
assert len(ledger_rows) == 2
|
||
for row in ledger_rows:
|
||
assert row[0] == "hook_advanced"
|
||
assert row[1] == "hook-voice"
|
||
assert row[2] == result["revision"], "账本必须绑同事务的正文 revision"
|
||
assert row[3] == command_id, "账本必须与正文提交同一幂等键"
|
||
assert row[4] == _sha(BODY)
|
||
for row in delta_rows:
|
||
assert row == ("accepted", result["revision"], "1")
|
||
|
||
|
||
def test_invalid_delta_rolls_back_entire_commit(work_id: int) -> None:
|
||
candidate_id = _insert_candidate(work_id, version="2")
|
||
command_id = _command_id("commit-bad")
|
||
forged = {"deltaId": "delta-forged", "deltaType": "setting_added",
|
||
"payload": {"factType": "污染规则", "text": "编造的设定"},
|
||
"evidenceQuote": "这句话根本不在正文里"}
|
||
try:
|
||
# expected_revision=1:上一测试已把正文块写到 rev1,给对的值才能真正走到增量校验
|
||
accept(candidate_id, rationale="unittest", expected_revision=1,
|
||
command_id=command_id, approved_deltas=[_good_delta("delta-ok"), forged])
|
||
raise AssertionError("非法增量必须中止整个提交")
|
||
except AssertionError:
|
||
raise
|
||
except FactDeltaError as exc:
|
||
assert exc.code == "FACT_DELTA_QUOTE_NOT_FOUND", exc
|
||
with connect(readonly=True) as conn:
|
||
blocks = int(conn.execute(
|
||
"SELECT COUNT(*) FROM muse_content_block WHERE work_id=%s AND deleted=false",
|
||
(work_id,)).fetchone()[0])
|
||
ledger = int(conn.execute(
|
||
"SELECT COUNT(*) FROM example_fact_ledger WHERE work_id=%s", (work_id,)).fetchone()[0])
|
||
delta_acc = int(conn.execute(
|
||
"SELECT COUNT(*) FROM example_fact_delta WHERE work_id=%s AND status='accepted'",
|
||
(work_id,)).fetchone()[0])
|
||
state = conn.execute(
|
||
"SELECT state FROM example_candidate WHERE id=%s", (candidate_id,)).fetchone()[0]
|
||
# 整体回滚:正文块、账本、accepted 提案全部不得出现(上一条测试已落的 2 行账本不变)
|
||
assert blocks == 1, "非法增量不得写入正文"
|
||
assert ledger == 2, "非法增量不得半合并进账本"
|
||
assert delta_acc == 2, "合法增量也不得单独生效(同事务)"
|
||
assert state == "passed"
|
||
|
||
|
||
def test_propose_then_approve_no_unique_collision(work_id: int) -> None:
|
||
"""抽取先登记提案、用户批准后随正文接受:翻态正道,不撞唯一键。"""
|
||
|
||
candidate_id = _insert_candidate(work_id, version="3")
|
||
candidate_sha = _sha(BODY)
|
||
out = propose_fact_deltas(
|
||
work_id=work_id, target_chapter=1, run_id=None,
|
||
candidate_sha256_bare=candidate_sha, candidate_body=BODY,
|
||
deltas=[_good_delta("delta-flow-1")])
|
||
assert out["status"] == "proposed"
|
||
_delta_ids.extend(out["proposed_ids"])
|
||
command_id = _command_id("commit-flow")
|
||
# 前面的测试把正文块写到 rev1(非法增量整体回滚不改变它),给 rev1 才走到增量合并
|
||
result = accept(candidate_id, rationale="unittest", expected_revision=1,
|
||
command_id=command_id, approved_deltas=[_good_delta("delta-flow-1")])
|
||
assert result["status"] == "accepted" and len(result["delta_ids"]) == 1
|
||
assert result["revision"] == 2
|
||
with connect(readonly=True) as conn:
|
||
row = conn.execute(
|
||
"SELECT status, decided_by, source_revision FROM example_fact_delta WHERE id=%s",
|
||
(result["delta_ids"][0],)).fetchone()
|
||
# 按 delta_ref 精确查(同 sha 的候选在本文件多个测试里复用过,按 sha 过滤会串)
|
||
ledger = conn.execute(
|
||
"SELECT delta_ref, source_block_revision, command_id FROM example_fact_ledger "
|
||
"WHERE delta_ref=%s", (result["delta_ids"][0],)).fetchall()
|
||
assert row == ("accepted", "1", result["revision"]), "提案行应翻 accepted 并绑正文 revision"
|
||
assert len(ledger) == 1, "该增量在账本中恰有一行"
|
||
assert ledger[0][0] == result["delta_ids"][0]
|
||
assert ledger[0][1] == result["revision"], "账本绑同事务正文 revision"
|
||
assert ledger[0][2] == command_id, "账本绑同一幂等键"
|
||
|
||
# 已裁决的提案不得再次接受(稳定错误码,不是裸 DB 异常);
|
||
# expected_revision 给当前 rev2,确保穿过 revision 校验真正走到增量阶段
|
||
candidate_id_2 = _insert_candidate(work_id, version="4")
|
||
try:
|
||
accept(candidate_id_2, rationale="unittest", expected_revision=2,
|
||
command_id=_command_id("commit-flow-2"),
|
||
approved_deltas=[_good_delta("delta-flow-1")])
|
||
raise AssertionError("已 accepted 的提案不得再次接受")
|
||
except AssertionError:
|
||
raise
|
||
except FactDeltaError as exc:
|
||
assert exc.code == "FACT_DELTA_ALREADY_DECIDED"
|
||
except Exception as exc:
|
||
raise AssertionError(f"必须是稳定错误码,得到 {type(exc).__name__}: {exc}")
|
||
|
||
|
||
def test_ledger_append_only(work_id: int) -> None:
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute("UPDATE example_fact_ledger SET subject_key='hacked' WHERE work_id=%s",
|
||
(work_id,))
|
||
conn.commit()
|
||
raise AssertionError("账本必须 append-only")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute("DELETE FROM example_fact_ledger WHERE work_id=%s", (work_id,))
|
||
conn.commit()
|
||
raise AssertionError("账本必须 append-only")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
|
||
|
||
def main() -> None:
|
||
work_id = _setup()
|
||
tests = (
|
||
("提案登记幂等拒绝", test_propose_is_idempotent_reject),
|
||
("ChapterCommit 原子合并增量", test_commit_applies_approved_deltas_atomically),
|
||
("非法增量整体回滚", test_invalid_delta_rolls_back_entire_commit),
|
||
("提案→批准翻态不撞唯一键", test_propose_then_approve_no_unique_collision),
|
||
("账本 append-only", test_ledger_append_only),
|
||
)
|
||
try:
|
||
for name, test in tests:
|
||
test(work_id)
|
||
print(f"PASS: {name}")
|
||
print("PASS:事实增量真实库集成测试全部通过")
|
||
finally:
|
||
_cleanup()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|