范围(不含 design-story-foundation、docs/、humanization/README.md 等进行中改动):
1. 新增 harness/ 控制平面
- skill_harness.py 静态审计:32 个运行时 Skill 的 frontmatter/manifest/文档污染,当前 0 问题
- run_selected.py 选择性执行器:manifest 与磁盘一一对账、依赖阻断、
空跑与 skip-only 失败关闭、AST 测试形状门
- manifests/skills.json:32 个 Skill 的合同责任方与协作领域登记
- manifests/test-inventory.json:81 个测试资产登记
- specs/skill-testing.md 与 README.md:测试分层、证据边界与 harness 职责
2. 实现测试从 .claude/skills/*/scripts/ 迁至 tests/skills/<skill>/
- 71 个测试文件迁移并修复项目根与临时目录运行导入
- 数据库触发器测试宽泛异常收窄为 psycopg.errors.RaiseException
- 抽取离线大测试拆出真实 PG smoke(默认阻断,不计入离线通过)
- 抽取 presence 去重边界拆出独立测试:493 + 78 = 571 项检查不变
3. 运行时文档清理
- 13 个 SKILL.md 移除自测/离线验证段落、测试命令与测试文件事实源表述,
只保留运行时合同;业务运行合同、额度、授权与离线模式均保留
4. SoT 同步
- AGENTS.md:新增 Skill 领域索引(7 个合同责任方分组,覆盖 32 个运行时 Skill)
- 领域 07:测试入口改由 harness/manifests/ 登记,SKILL.md 不承载测试命令
- humanization 覆盖矩阵:活动测试路径同步迁移
验证证据: harness 自测 15 项 + runner 自测 13 项通过;静态审计 32 Skill / 0 问题;
73 个非数据库测试通过;8 个集成条目中 6 个 PostgreSQL 项被依赖门明确阻断;
py_compile 与 git diff --check 通过。未连接 PostgreSQL、网络、真实模型或额度。
已知边界: 真正 skill_behavior_eval 仍为 0,尚未验证任何 Skill 自然语言行为;
evaluate-frozen-replay 的 raw 存储边界冲突留待单独治理。
186 lines
7.1 KiB
Python
186 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
||
"""PostgresCasStateStore 对 muse-example 真实库的集成测试。
|
||
|
||
验证离线 fake 无法覆盖的部分:DB 触发器的迁移方向闭集、revision 恰好 +1、
|
||
PASSED 终态、身份不可变,以及条件 UPDATE 在真实并发语义下的行计数。
|
||
测试行用 unittest-cas- 前缀的 run_id,结束前物理清理(本表是可变注册表,无 append-only 约束)。
|
||
|
||
跑法(需 Tailscale 内网可达 muse-example):
|
||
.venv/bin/python tests/skills/write-next-chapter/test_candidate_cas_db.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pathlib
|
||
import sys
|
||
import uuid
|
||
|
||
from psycopg.errors import RaiseException
|
||
|
||
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
||
SKILLS_DIR = PROJECT_ROOT / ".claude" / "skills"
|
||
SCRIPT_DIR = SKILLS_DIR / "write-next-chapter" / "scripts"
|
||
for path in (SCRIPT_DIR, SKILLS_DIR / "access-database" / "scripts"):
|
||
if str(path) not in sys.path:
|
||
sys.path.insert(0, str(path))
|
||
|
||
from candidate_cas import PostgresCasStateStore # noqa: E402
|
||
from db import connect # noqa: E402
|
||
|
||
PREFIX = f"unittest-cas-{uuid.uuid4().hex[:8]}-"
|
||
_run_ids: list[str] = []
|
||
|
||
|
||
def _cleanup() -> None:
|
||
if not _run_ids:
|
||
return
|
||
with connect() as conn:
|
||
for run_id in _run_ids:
|
||
conn.execute("DELETE FROM example_candidate_cas WHERE run_id=%s", (run_id,))
|
||
conn.commit()
|
||
|
||
|
||
def _new_run_id(tag: str) -> str:
|
||
run_id = f"{PREFIX}{tag}"
|
||
_run_ids.append(run_id)
|
||
return run_id
|
||
|
||
|
||
def test_full_lifecycle_and_contention() -> None:
|
||
store = PostgresCasStateStore(work_id=0, target_chapter=None, creator="unittest")
|
||
run_id = _new_run_id("lifecycle")
|
||
draft = store.create(run_id, attempt=1, candidate_version=1)
|
||
assert (draft.state, draft.revision) == ("DRAFT", 1)
|
||
# 重复建链失败关闭
|
||
try:
|
||
store.create(run_id, attempt=1, candidate_version=1)
|
||
raise AssertionError("重复建链必须失败")
|
||
except Exception as exc:
|
||
assert getattr(exc, "code", None) == "CAS_CONFLICT", exc
|
||
checking = store.transition(draft, "CHECKING")
|
||
assert checking is not None and checking.revision == 2
|
||
# 旧 token 重放不命中
|
||
assert store.transition(draft, "CHECKING") is None
|
||
rejected = store.transition(checking, "REJECTED")
|
||
assert rejected is not None and rejected.state == "REJECTED"
|
||
# start_next 守卫:非递增身份拒绝
|
||
assert store.start_next(rejected, attempt=1, candidate_version=2) is None
|
||
assert store.start_next(rejected, attempt=2, candidate_version=1) is None
|
||
second = store.start_next(rejected, attempt=2, candidate_version=2)
|
||
assert second is not None and (second.state, second.revision) == ("DRAFT", 4)
|
||
checking2 = store.transition(second, "CHECKING")
|
||
passed = store.transition(checking2, "PASSED")
|
||
assert passed is not None and passed.state == "PASSED"
|
||
assert store.latest(run_id) == passed
|
||
|
||
|
||
def test_passed_is_terminal() -> None:
|
||
store = PostgresCasStateStore(creator="unittest")
|
||
run_id = _new_run_id("terminal")
|
||
draft = store.create(run_id, attempt=1, candidate_version=1)
|
||
checking = store.transition(draft, "CHECKING")
|
||
passed = store.transition(checking, "PASSED")
|
||
# 存储层对 PASSED 出发的迁移方向闭集拒绝(返回 None,不下探到库)
|
||
assert store.transition(passed, "REJECTED") is None
|
||
assert store.transition(passed, "CHECKING") is None
|
||
assert store.start_next(passed, attempt=2, candidate_version=2) is None
|
||
assert store.latest(run_id).state == "PASSED"
|
||
|
||
|
||
def test_db_trigger_rejects_malformed_direct_updates() -> None:
|
||
store = PostgresCasStateStore(creator="unittest")
|
||
run_id = _new_run_id("trigger")
|
||
store.create(run_id, attempt=1, candidate_version=1)
|
||
# 绕过存储直接写非法形状,触发器必须拒绝
|
||
bad_updates = (
|
||
# revision 跳号
|
||
"UPDATE example_candidate_cas SET state='CHECKING', revision=revision+2 WHERE run_id=%s",
|
||
# DRAFT 直接到 PASSED
|
||
"UPDATE example_candidate_cas SET state='PASSED', revision=revision+1 WHERE run_id=%s",
|
||
# 同轮迁移改身份
|
||
"UPDATE example_candidate_cas SET attempt=9, revision=revision+1 WHERE run_id=%s",
|
||
# run_id 身份漂移
|
||
"UPDATE example_candidate_cas SET run_id=run_id || 'x' WHERE run_id=%s",
|
||
)
|
||
for sql in bad_updates:
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute(sql, (run_id,))
|
||
conn.commit()
|
||
raise AssertionError(f"触发器必须拒绝: {sql}")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
latest = store.latest(run_id)
|
||
assert (latest.state, latest.revision) == ("DRAFT", 1), "非法 UPDATE 不得改变链"
|
||
|
||
|
||
def test_start_next_monotonic_at_db_level() -> None:
|
||
store = PostgresCasStateStore(creator="unittest")
|
||
run_id = _new_run_id("startnext")
|
||
draft = store.create(run_id, attempt=1, candidate_version=1)
|
||
checking = store.transition(draft, "CHECKING")
|
||
rejected = store.transition(checking, "REJECTED")
|
||
assert rejected is not None and rejected.state == "REJECTED"
|
||
# 绕过存储直接开新轮但不递增 attempt/candidate_version,触发器必须拒绝
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute(
|
||
"UPDATE example_candidate_cas SET state='DRAFT', revision=revision+1 "
|
||
"WHERE run_id=%s AND state='REJECTED'", (run_id,))
|
||
conn.commit()
|
||
raise AssertionError("REJECTED 开新轮必须递增 attempt/candidate_version")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
assert store.latest(run_id).state == "REJECTED", "非法开新轮不得改变链"
|
||
|
||
|
||
def test_insert_must_start_draft_revision_one() -> None:
|
||
run_id = _new_run_id("insert-guard")
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute(
|
||
"INSERT INTO example_candidate_cas(run_id, attempt, candidate_version, state, revision, creator) "
|
||
"VALUES (%s,1,1,'CHECKING',1,'unittest')", (run_id,))
|
||
conn.commit()
|
||
raise AssertionError("初始行必须是 DRAFT")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
try:
|
||
with connect() as conn:
|
||
conn.execute(
|
||
"INSERT INTO example_candidate_cas(run_id, attempt, candidate_version, state, revision, creator) "
|
||
"VALUES (%s,1,1,'DRAFT',5,'unittest')", (run_id,))
|
||
conn.commit()
|
||
raise AssertionError("初始 revision 必须是 1")
|
||
except AssertionError:
|
||
raise
|
||
except RaiseException:
|
||
pass
|
||
|
||
|
||
def main() -> None:
|
||
tests = (
|
||
test_full_lifecycle_and_contention,
|
||
test_passed_is_terminal,
|
||
test_db_trigger_rejects_malformed_direct_updates,
|
||
test_start_next_monotonic_at_db_level,
|
||
test_insert_must_start_draft_revision_one,
|
||
)
|
||
try:
|
||
for test in tests:
|
||
test()
|
||
print(f"PASS: {test.__name__}")
|
||
print("PASS:PostgresCasStateStore 真实库集成测试全部通过")
|
||
finally:
|
||
_cleanup()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|