Files
codex-app-server/cloud/tests/test_skill_transplant_prep.py
T
host 6bceed484c code-to10 v5: solidify 7 provisional resolutions per user confirmation (2026-11-02)
- whitelist expansion (tr + exit builtin) confirmed, comment only
- B4 viewport wording: temp banner removed, wording untouched,
  change-list table stays in draft, not merged into body
- B3-tail (a)/(b): no content change (bullet still absent), recorded
  as decided; v2 record showed it unselected, no side picked here
- section 4: 6 templates/artifacts/ refs deleted per signed verdict;
  tripwire test updated 6 -> 0 to pin the new inventory
- CHANGELOG pointer line kept, no marker present
- workers audit landing migration confirmed, comment only
- verify-script gate evidence confirmed, gate hard untouched
- 2 temp stances NOT touched: actual-only quota, evidence binding
  not enforced / no anti-restamp
- behavior code unchanged; directional skill tests 39/39 OK
2026-11-03 02:49:26 +00:00

297 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Transplant preparation checks for #244 -> v0.3 / v0.4 / v0.5.
What this pins (see TRANSPLANT-PREP-244.md next to the skill):
- tools/check_transplant.py applies the graded transplant to fixture
texts: v03 drops the G7 / full-auto sentences, v04 drops the G7
sentences and the tighten-3 pre-rebuild confirmation paragraph but
keeps the full-auto sentences, v05 drops the whole QA section, its
checklist item and the confirmation paragraph, and renumbers the
appendix chapter to 11-auditor;
- check-transplanted passes on those drafts and fails when an
artifact reference survives or a preserved marker is dropped;
- apply fails loudly when a required anchor is missing;
- the real pack in this tree carries zero templates/artifacts/
reference lines after the user-signed v5 deletion, so the
decision-B inventory cannot drift silently.
Fixture texts are built from the script's own fragment constants, so
the tests exercise the real redaction strings, not copies of them.
The real-branch runs (against #199 / #205 / #207 / #244 texts) are
recorded in the verification summary; they cannot run in CI because
those branches are not in this tree.
"""
import importlib.util
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
SKILL_DIR = (REPO_ROOT / "cloud" / "skills" / "software-engineering")
SCRIPT = SKILL_DIR / "tools" / "check_transplant.py"
SKILL_MD = SKILL_DIR / "SKILL.md"
def _load_script():
spec = importlib.util.spec_from_file_location(
"check_transplant", str(SCRIPT))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
CT = _load_script()
def _source_fixture():
m = CT
parts = [
"---\nname: software-engineering\n---\n",
"## 角色线程矩阵\n",
"| 角色 | 线程数 | 负责目录 | 产出 |",
"| QA / 测试 | 1 | `tests/` | 测试用例 |",
"| 独立审计 | 1 | 只写审计记录 | 审计结论 |",
"| 验收 / 发布 | 协调员兼任 | `docs/release/` | 验收清单 |",
"## 失败、续跑与成本\n",
"- 成本控制:分阶段本身就是成本控制。",
"## 独立审计与自动推回\n",
"本节是审计加固第 3 步的技能机制,与代码 `cloud/audit_pushback.py`",
"(推回决策纯函数)双轨落地,只写文档不算落地。只补审计机制,",
"不改上面已有规则的语义:G5 缺陷回路、合并拆分、G6 用户验收",
"的原意都不变。" + m.AUDIT_WARNING,
"### 独立审计角色:不写代码,只独立重跑、抽查证据、单独签字\n",
"- 审计线程不写业务代码、不改待办、不给合并决定、不执行合并。",
"### 自动推回:审计不过关,系统自动推回,不等人工点头\n",
"- 推回上限与升级:推回修复最多三轮" + m.PUSHBACK_LIMIT_PAREN + "。",
"- 与已有回路的关系:G5 缺陷回路复用为修复执行通道,推回派生的修复子任务",
" 就按 G5 方式执行,只补“自动触发 + 合并发布阻塞”这两块缺失;",
m.G7_FULLAUTO_BLOCK,
"## 推倒重做:超过三次后的升级分支(不是并行回路)\n",
"- 与上一节自动推回的关系再写一遍以免混淆:自动推回(1–3 轮修补)",
" 在前、推倒重做(超过 3 次)在后,是同一链路的前后两段;",
" 与 G5 缺陷回路的关系是复用修复执行通道(重做任务本身仍按",
" 阶段派生、仍走 G5 回归复核)" + m.REBUILD_TAIL,
"## 三条收紧:分流、保留资产、审计的审计\n",
"- 收紧一:阈值不能死板,第三次失败先判根因。",
m.TIGHTEN3_NOTIFY,
"## QA 全链路:测试环节跑通各个和所有业务链路\n",
"本节是“独立审计与 QA”部分的补充验收标准,与自动推回、推倒重做、",
"三条收紧并列,不覆盖已有规则。" + m.QA_INTRO,
"### 每个客户端交付前自测:三项缺一不算自测完\n",
"- 单元测试 + 对照契约的集成测试 + 手工冒烟清单,三项缺一不算自测完。",
"## 诚实边界:原生客户端\n",
"- 在只有 Linux 主机的环境里,真原生 iOS 需要 Xcode。",
"## 校验(协调员每过一个门禁自查一次)\n",
"- 分阶段走了吗?G3.5 没过就开了 G4b / G4c 吗?",
"- 审计与推回走了吗?审计签字人是否独立于执行人、协调员批准人、",
" QA 测试人?安全签字是不是无条件通过?审计 fail 后有没有自动推回",
" 并阻塞合并/发布?模块失败超过 3 次后有没有按推倒重做分支处理",
" (先判根因、只推倒该模块、旧实现归档、避坑清单带进新包),",
" 而不是无限修补或悄悄放行?",
"- QA 全链路走了吗?每端自测三项齐了吗?PRD 验收标准全有对应用例吗?",
" agent 专属六项齐了吗?跨端矩阵覆盖全部已选端了吗,未测端写明原因了吗?",
" 未编译端是不是只写了“代码评审 + Web 核心验证”?QA 矩阵签字与",
" 独立审计签字两个签字位都在且不是同一人吗?推回包里失败链路齐了吗,",
" 重审重跑失败链路了吗?",
"有一项为否,就停在当前门禁,不进入下一阶段。",
"## 附录:角色模板(播种随附版)\n",
"下面是 `templates/roles/` 的随附全文。",
"---",
"## 附录角色:08-shared-core(来源 templates/roles/08-shared-core.md)\n",
"# 角色模板:共享核心\n",
"- 不在共享逻辑里写某端专属代码。",
"---",
"## 附录角色:09-auditor(独立审计,新增)\n",
"# 角色模板:独立审计(1 个线程)\n",
"你是本项目的独立审计线程。",
]
return "\n".join(parts) + "\n"
def _target_fixture(target_id):
markers = " ".join(CT.MARKERS[target_id])
last = ("## 附录角色:10-iteration(来源 templates/roles/10-iteration.md,v0.5 新增)"
if target_id == "v05"
else "## 附录角色:08-shared-core(来源 templates/roles/08-shared-core.md)")
parts = [
"# software-engineering:多客户端软件工程编排\n",
"## 角色线程矩阵\n",
"| 角色 | 线程数 | 负责目录 | 产出 |",
"| QA / 测试 | 1 | `tests/` | 测试用例 |",
"| 验收 / 发布 | 协调员兼任 | `docs/release/` | 验收清单 |",
"## 失败、续跑与成本\n",
"- 成本控制:分阶段本身就是成本控制。",
"## 诚实边界:原生客户端\n",
"- 在只有 Linux 主机的环境里,真原生 iOS 需要 Xcode。",
"## 校验(协调员每过一个门禁自查一次)\n",
"- 分阶段走了吗?G3.5 没过就开了 G4b / G4c 吗?",
"- 标记行:" + markers,
"| G1 | PRD 冻结 | PRD 按 `templates/artifacts/prd-outline.md` 写完,用户故事有验收标准 | 回产品线程改 |",
"问卷模板在 `templates/artifacts/intake-questions.md`。最关键的六问:",
"- 验收清单模板在 `templates/artifacts/acceptance-checklist.md`。",
"按 `templates/artifacts/prd-outline.md` 写一份 PRD,至少包含:",
" 协议模板见 `templates/artifacts/` 下的 agent 任务契约模板。",
"1. 按 `templates/artifacts/acceptance-checklist.md` 生成本项目的验收清单,",
"有一项为否,就停在当前门禁,不进入下一阶段。",
"## 附录:角色模板(播种随附版)\n",
last + "\n",
"# 角色模板:共享核心\n",
"- 不在共享逻辑里写某端专属代码。",
]
return "\n".join(parts) + "\n"
def _run_script(testcase, args, tmp):
src = Path(tmp.name) / "source.md"
tgt = Path(tmp.name) / "target.md"
src.write_text(_source_fixture(), encoding="utf-8")
tgt.write_text(_target_fixture(args["target_id"]), encoding="utf-8")
cmd = [sys.executable, str(SCRIPT), args["cmd"],
"--source244", str(src), "--target", str(tgt),
"--target-id", args["target_id"]]
if args["cmd"] == "apply":
out = Path(tmp.name) / "draft.md"
cmd += ["--out", str(out)]
if args["cmd"] == "check-transplanted":
out = Path(tmp.name) / "draft.md"
cmd += ["--transplanted", str(out)]
result = subprocess.run(cmd, capture_output=True, text=True)
return result, src, tgt, Path(tmp.name) / "draft.md"
class TransplantPrepTestCase(unittest.TestCase):
def test_apply_v03_drops_g7_and_fullauto(self):
with tempfile.TemporaryDirectory() as tmp:
tmp = type("T", (), {"name": tmp})()
result, _s, _t, draft = _run_script(
self, {"cmd": "apply", "target_id": "v03"}, tmp)
self.assertEqual(result.returncode, 0, result.stderr)
text = draft.read_text(encoding="utf-8")
self.assertEqual(text.count("| 独立审计 |"), 1)
self.assertNotIn("G7 热修复回路并行", text)
self.assertNotIn("全自动 CI 自动修复", text)
self.assertIn("验收失败导致的推倒要先通知用户", text)
self.assertIn("## QA 全链路", text)
self.assertNotIn("移植警告", text)
self.assertNotIn("templates/artifacts", text)
self.assertEqual(
text.count("## 附录角色:09-auditor(来源 "
"templates/roles/09-auditor.md)"), 1)
check = subprocess.run(
[sys.executable, str(SCRIPT), "check-transplanted",
"--source244", str(_s), "--target", str(_t),
"--target-id", "v03", "--transplanted", str(draft)],
capture_output=True, text=True)
self.assertEqual(check.returncode, 0, check.stderr)
def test_apply_v04_drops_g7_and_notify_keeps_fullauto(self):
with tempfile.TemporaryDirectory() as tmp:
tmp = type("T", (), {"name": tmp})()
result, _s, _t, draft = _run_script(
self, {"cmd": "apply", "target_id": "v04"}, tmp)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("STOPPED", result.stderr)
text = draft.read_text(encoding="utf-8")
self.assertNotIn("G7 热修复回路并行", text)
self.assertNotIn("验收失败导致的推倒要先通知用户", text)
self.assertIn("与全自动 CI 自动修复限", text)
self.assertIn("## QA 全链路", text)
self.assertNotIn("templates/artifacts", text)
check = subprocess.run(
[sys.executable, str(SCRIPT), "check-transplanted",
"--source244", str(_s), "--target", str(_t),
"--target-id", "v04", "--transplanted", str(draft)],
capture_output=True, text=True)
self.assertEqual(check.returncode, 0, check.stderr)
def test_apply_v05_drops_qa_and_notify_renumbers_11(self):
with tempfile.TemporaryDirectory() as tmp:
tmp = type("T", (), {"name": tmp})()
result, _s, _t, draft = _run_script(
self, {"cmd": "apply", "target_id": "v05"}, tmp)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("STOPPED", result.stderr)
text = draft.read_text(encoding="utf-8")
self.assertNotIn("## QA 全链路", text)
self.assertNotIn("- QA 全链路走了吗?", text)
self.assertNotIn("验收失败导致的推倒要先通知用户", text)
self.assertEqual(
text.count("## 附录角色:11-auditor(来源 "
"templates/roles/11-auditor.md)"), 1)
self.assertNotIn("09-auditor", text)
self.assertNotIn("templates/artifacts", text)
check = subprocess.run(
[sys.executable, str(SCRIPT), "check-transplanted",
"--source244", str(_s), "--target", str(_t),
"--target-id", "v05", "--transplanted", str(draft)],
capture_output=True, text=True)
self.assertEqual(check.returncode, 0, check.stderr)
def test_apply_missing_anchor_fails(self):
with tempfile.TemporaryDirectory() as tmp:
src = Path(tmp) / "source.md"
tgt = Path(tmp) / "target.md"
src.write_text(_source_fixture(), encoding="utf-8")
tgt.write_text(_target_fixture("v03").replace(
"| QA / 测试 |", "| QA |"), encoding="utf-8")
result = subprocess.run(
[sys.executable, str(SCRIPT), "apply",
"--source244", str(src), "--target", str(tgt),
"--target-id", "v03",
"--out", str(Path(tmp) / "draft.md")],
capture_output=True, text=True)
self.assertNotEqual(result.returncode, 0)
def test_check_rejects_surviving_artifact_ref(self):
with tempfile.TemporaryDirectory() as tmp:
tmp_o = type("T", (), {"name": tmp})()
result, src, tgt, draft = _run_script(
self, {"cmd": "apply", "target_id": "v03"}, tmp_o)
self.assertEqual(result.returncode, 0, result.stderr)
text = draft.read_text(encoding="utf-8")
draft.write_text(text + "\n见 `templates/artifacts/x.md`。\n",
encoding="utf-8")
check = subprocess.run(
[sys.executable, str(SCRIPT), "check-transplanted",
"--source244", str(src), "--target", str(tgt),
"--target-id", "v03", "--transplanted", str(draft)],
capture_output=True, text=True)
self.assertNotEqual(check.returncode, 0)
self.assertIn("templates/artifacts", check.stderr)
def test_check_rejects_dropped_marker(self):
with tempfile.TemporaryDirectory() as tmp:
tmp_o = type("T", (), {"name": tmp})()
result, src, tgt, draft = _run_script(
self, {"cmd": "apply", "target_id": "v03"}, tmp_o)
self.assertEqual(result.returncode, 0, result.stderr)
lines = draft.read_text(encoding="utf-8").split("\n")
kept = [l for l in lines if not l.startswith("- 标记行:")]
self.assertLess(len(kept), len(lines))
draft.write_text("\n".join(kept), encoding="utf-8")
check = subprocess.run(
[sys.executable, str(SCRIPT), "check-transplanted",
"--source244", str(src), "--target", str(tgt),
"--target-id", "v03", "--transplanted", str(draft)],
capture_output=True, text=True)
self.assertNotEqual(check.returncode, 0)
def test_real_pack_artifact_inventory_is_zero_after_signed_deletion(self):
# v5 用户定稿(2026-11-02):§4 的 6 处引用经用户签字删除,
# 本测试由“钉住 6 处”改为“钉住 0 处”,防止删除后悄悄回潮。
text = SKILL_MD.read_text(encoding="utf-8")
refs = [l for l in text.split("\n") if "templates/artifacts" in l]
self.assertEqual(len(refs), 0, refs)
self.assertIn("## 独立审计与自动推回", text)
# 集成基底章节表:审计章在 v0.5 表里编号为 11-auditor
#(00–08 + 09-ops + 10-iteration + 11-auditor),
# #258 原生的 09-auditor 编号已按章节表重定基。
self.assertIn("## 附录角色:11-auditor(来源 "
"templates/roles/11-auditor.md)", text)
if __name__ == "__main__":
unittest.main()