Files
codex-app-server/cloud/tests/test_skill_appendix_sync.py
host e921db3472 audit: independent audit role, auto-pushback, rebuild branch, three tightenings, QA full-chain
Independent audit (no code, independent re-run, separate signature;
CONDITIONAL never passes; deviation needs due todo) and automatic
pushback (fail -> derived fix subtask, merge/release blocked,
package with per-item evidence, full re-audit, 3-round limit with
a separate CI auto-fix counter, ledger hash-chain interface for
track 4, gate-block interface for track 1). Over 3 failures per
module per contract version: rebuild only that module from the
frozen contract (old implementation archived, avoidance list),
contract/design root cause -> G2 re-freeze escalation instead.
Tightenings: 3rd-failure triage (structural early rebuild /
early refreeze, near-complete one targeted round), rebuild keeps
tests and passed assets with an old-test first gate, audit-of-
audit sampling/correction and G6 user-only acceptance plus
acceptance-rebuild user confirmation. QA full chain: self-test
three parts, PRD coverage, agent six, cross-client matrix with
honest uncompiled verdicts, G5 gates, impact-scoped regression,
dual QA/audit signatures, failed-link re-runs in re-audit.
Skill text is purely additive (+459/-0) against main v0.2 text;
port this section into v0.3/v0.4/v0.5 texts on merge, never
overwrite them with this file.
2026-11-03 02:49:25 +00:00

222 lines
9.9 KiB
Python
Raw Permalink 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.
"""Body<->appendix sync for the software-engineering skill (C-line).
Scope guard: this file READS cloud/skills/software-engineering/SKILL.md
and asserts structure; it never edits the skill text. If any assertion
here fails, the fix is a skill-content decision (edit SKILL.md through
its own track), not a silent edit by this test.
Audit finding on origin/main (2026-10-31): there was NO bidirectional
test at all. The only appendix coverage was test_skill_seed_worker.py
asserting two chapters (00-coordinator, 08-shared-core) exist in the
seeded file -- a dropped middle chapter (01..07) would pass CI silently.
The bidirectional assertion previously discussed on the #209 track is
unmerged, so main does not have it; this file supplies it on the main
base (skill text v0.2, 9 appendix chapters 00..08;
integrated text is v0.5, 11 appendix chapters 00..10).
What "bidirectional" means here, concretely (all derived from SKILL.md
itself, no new semantics invented):
- body names the appendix as the authority AND the appendix intro
claims itself as the authority (either side missing -> fail naming
the missing side);
- body names exactly three appendix chapters for the GitHub guarantee
(coordinator, client, acceptance) AND each named chapter carries
the rule keywords (each chapter's keyword set is tailored to what
that chapter actually says -- e.g. 07-acceptance carries force-push /
merge-gate / PR-number rules but no literal "GitHub" word, so the
test must not demand that word there);
- body role-matrix role names == appendix role-title names as sets
(order differs: matrix order vs numeric 00..10 order, so set, not
list, comparison), and appendix ids are exactly 00..10 in order.
CI: .github/workflows/host-verify.yml and release.yml both run
`python3 -m unittest discover -s cloud/tests -v`, so this test_*.py
file runs in CI automatically with no workflow edit.
Only software-engineering has an appendix (grep: the other four
skills contain zero "附录"); appendix checks are therefore scoped to
this one skill and must NOT be generalised to the other skills.
"""
import re
import sys
import tempfile
import unittest
from pathlib import Path
try:
from cloud.skill_seed import ensure_codex_home_skills
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.skill_seed import ensure_codex_home_skills
REPO_ROOT = Path(__file__).resolve().parents[2]
SKILL_PATH = (REPO_ROOT / "cloud" / "skills" / "software-engineering"
/ "SKILL.md")
EXPECTED_IDS = ["00-coordinator", "01-product-manager", "02-architect",
"03-designer", "04-client-developer", "05-backend",
"06-qa", "07-acceptance", "08-shared-core",
"09-ops", "10-iteration",
"11-auditor"]
# Chapter -> keywords that chapter must carry for the GitHub guarantee
# the body claims is "同样写进了附录的协调员、客户端、验收章节".
# 07 has no literal "GitHub" word (its rules live under
# "按端发布与回滚(验收)"); demanding it would be a false failure.
GITHUB_CHAPTER_KEYWORDS = {
"00-coordinator": ["GitHub 保证", "force-push", "合并门"],
"04-client-developer": ["GitHub 交付", "force-push", "PR 号"],
"07-acceptance": ["force-push", "合并门", "PR 号"],
}
CHAPTER_RE = re.compile(
r"^## 附录角色:(\d{2}-[a-z0-9-]+)"
r"(来源 templates/roles/(\d{2}-[a-z0-9-]+\.md))",
flags=re.M)
TITLE_RE = re.compile(r"^# 角色模板:(.+)$", flags=re.M)
def _read_skill():
assert SKILL_PATH.is_file(), (
"正文侧缺失:技能源文件不存在:%s" % SKILL_PATH)
return SKILL_PATH.read_text(encoding="utf-8")
def _split(text):
marker = "## 附录:角色模板"
assert marker in text, "正文侧缺失:找不到附录引言节 %r" % marker
body, appendix = text.split(marker, 1)
return body, appendix
def _chapters(appendix):
matches = list(CHAPTER_RE.finditer(appendix))
chapters = {}
for i, m in enumerate(matches):
end = matches[i + 1].start() if i + 1 < len(matches) else len(appendix)
chapters[m.group(1)] = appendix[m.start():end]
return chapters, matches
def _base_role_name(title):
# "后端 / API(可选,1 个线程)" -> "后端 / API";
# "后端 / API(如有)" (matrix cell) -> "后端 / API".
return re.sub(r"([^)]*)\s*$", "", title.strip())
class AppendixSyncTestCase(unittest.TestCase):
def test_appendix_has_all_nine_chapters_in_order(self):
text = _read_skill()
_body, appendix = _split(text)
chapters, matches = _chapters(appendix)
found = [m.group(1) for m in matches]
self.assertEqual(
found, EXPECTED_IDS,
"附录侧缺失/错序:附录章节 id 应为 %s,实际为 %s"
% (EXPECTED_IDS, found))
for cid in EXPECTED_IDS:
sec = chapters[cid]
src = [m.group(2) for m in matches if m.group(1) == cid][0]
self.assertEqual(
src, cid + ".md",
"附录侧不一致:章节 %s 的来源注应为 %s,实际为 %s"
% (cid, cid + ".md", src))
title = TITLE_RE.search(sec)
self.assertIsNotNone(
title,
"附录侧缺失:章节 %s 内找不到“# 角色模板:”标题行" % cid)
# Smallest real chapter is ~600 chars; 300 catches a
# dropped/emptied chapter without being brittle.
self.assertGreater(
len(sec), 300,
"附录侧缺失/被掏空:章节 %s 仅 %d 字" % (cid, len(sec)))
def test_body_role_matrix_matches_appendix_titles(self):
text = _read_skill()
body, appendix = _split(text)
chapters, _m = _chapters(appendix)
appendix_roles = set()
for cid, sec in chapters.items():
title = TITLE_RE.search(sec)
self.assertIsNotNone(title, "附录侧缺失:%s 无标题" % cid)
appendix_roles.add(_base_role_name(title.group(1)))
matrix_roles = set()
# Only the role-matrix section: other body tables (gate table
# with G0..G6, etc.) must not leak into the role set.
matrix_section = body.split("## 角色线程矩阵", 1)[1]
matrix_section = matrix_section.split("\n## ", 1)[0]
for line in matrix_section.splitlines():
if not line.startswith("| "):
continue
cells = [c.strip() for c in line.strip().strip("|").split("|")]
if not cells or cells[0] in ("角色", "------"):
continue
matrix_roles.add(_base_role_name(cells[0]))
self.assertEqual(
matrix_roles, appendix_roles,
"双向不一致:正文角色矩阵缺 %s;附录缺角色 %s"
% (sorted(appendix_roles - matrix_roles),
sorted(matrix_roles - appendix_roles)))
def test_authority_statement_is_bidirectional(self):
text = _read_skill()
body, appendix = _split(text)
for phrase in ("角色模板的权威来源是本文件末尾的附录",
"不一致时以附录为准"):
self.assertIn(
phrase, body,
"正文侧缺失:正文未声明“%s”(附录单方面有权威也无法核对)"
% phrase)
intro = appendix.split("## 附录角色:", 1)[0]
for phrase in ("以本附录为准", "核对附录与 `templates/roles/` 一致"):
self.assertIn(
phrase, intro,
"附录侧缺失:附录引言未声明“%s”(正文单方面指认附录也无法核对)"
% phrase)
def test_github_guarantee_in_body_and_named_appendix_chapters(self):
text = _read_skill()
body, appendix = _split(text)
chapters, _m = _chapters(appendix)
for phrase in ("同样写进了附录的协调员、客户端、验收章节",
"正文与附录不一致就是不合格"):
self.assertIn(
phrase, body,
"正文侧缺失:正文 GitHub 保证节未声明“%s”" % phrase)
for cid, keywords in GITHUB_CHAPTER_KEYWORDS.items():
self.assertIn(
cid, chapters, "附录侧缺失:正文点名的章节 %s 不在附录中" % cid)
for kw in keywords:
self.assertIn(
kw, chapters[cid],
"附录侧缺失:章节 %s 缺少规则关键词 %r"
"(正文 GitHub 保证节声称该规则已写进本章)"
% (cid, kw))
def test_seeded_copy_carries_full_appendix_byte_identical(self):
# Delivery-channel check: the seeded tenant copy must carry
# the whole appendix, not just the two chapters the old test
# checked. Byte-identity is the strongest form: skill_seed
# copies SKILL.md verbatim, so any appendix edit in the pack
# reaches the tenant only through this exact file.
with tempfile.TemporaryDirectory() as tmp:
out = ensure_codex_home_skills(tmp)
self.assertIn("software-engineering", out["skills_copied"])
seeded = Path(tmp) / "skills" / "software-engineering" \
/ "SKILL.md"
self.assertTrue(seeded.is_file(), "播种侧缺失:未生成租户 SKILL.md")
seeded_text = seeded.read_text(encoding="utf-8")
self.assertEqual(
seeded_text, _read_skill(),
"播种侧不一致:租户 SKILL.md 与源包字节不一致(附录可能被截断)")
_body, appendix = _split(seeded_text)
chapters, _m = _chapters(appendix)
self.assertEqual(
sorted(chapters), sorted(EXPECTED_IDS),
"播种侧缺失:租户副本附录章节不全:%s" % sorted(chapters))
if __name__ == "__main__":
unittest.main()