harness: Issue 검증 증빙을 checkout 밖에 보관
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "harness/agents/impl/skills/gitea-issue-rework/scripts/evidence_manifest.py"
|
||||
CACHE = ROOT / "harness/operations/repository-cache.md"
|
||||
SKILLS = {
|
||||
"implementation": ROOT / "harness/agents/impl/skills/gitea-issue-implementation/SKILL.md",
|
||||
"rework": ROOT / "harness/agents/impl/skills/gitea-issue-rework/SKILL.md",
|
||||
"review": ROOT / "harness/agents/spec/skills/gitea-implementation-spec-review/SKILL.md",
|
||||
}
|
||||
CANONICAL = "<harness-root>/repositories/<owner>/<repo>/worktrees/issue-<number>/verification/<run-id>"
|
||||
|
||||
|
||||
class VerificationContractDocumentationTests(unittest.TestCase):
|
||||
def test_evidence_plan_disables_python_bytecode_in_clean_checkout(self):
|
||||
import json
|
||||
template = ROOT / "harness/agents/impl/skills/gitea-issue-rework/templates/evidence-plan.json"
|
||||
plan = json.loads(template.read_text())
|
||||
self.assertIn("-B", plan["commands"][0]["argv"])
|
||||
self.assertIn("PYTHONDONTWRITEBYTECODE", plan["command_env_allowlist"])
|
||||
|
||||
def test_all_roles_reference_canonical_external_artifacts(self):
|
||||
for path in (CACHE, *SKILLS.values()):
|
||||
with self.subTest(path=path.name):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
self.assertIn(CANONICAL, text)
|
||||
self.assertIn("evidence_gap", text)
|
||||
self.assertIn("[REDACTED]", text)
|
||||
self.assertIn("evidence-manifest/v2", text)
|
||||
|
||||
def test_examples_match_real_helper_cli_not_unsupported_options(self):
|
||||
for path in (SKILLS["implementation"], SKILLS["rework"]):
|
||||
section = path.read_text().split("## Canonical external verification evidence", 1)[1]
|
||||
match = re.search(r"```sh\n(.*?)```", section, re.S)
|
||||
assert match is not None
|
||||
block = match.group(1).replace("\\\n", " ")
|
||||
commands = [line for line in block.splitlines() if line.startswith("python3 ")]
|
||||
self.assertEqual(len(commands), 2)
|
||||
for op, command in zip(("run", "validate"), commands):
|
||||
with self.subTest(skill=path.parent.name, op=op):
|
||||
help_result = subprocess.run([sys.executable, str(SCRIPT), op, "--help"], text=True, capture_output=True)
|
||||
self.assertEqual(help_result.returncode, 0)
|
||||
supported = set(re.findall(r"--[a-z-]+", help_result.stdout))
|
||||
declared = set(re.findall(r"--[a-z-]+", command))
|
||||
required = {"--contract-path", "--repo-root", "--artifact-root"} | ({"--plan", "--output"} if op == "run" else {"--manifest"})
|
||||
self.assertEqual(declared, required)
|
||||
self.assertTrue(declared <= supported)
|
||||
self.assertIn("exact current HEAD", section)
|
||||
self.assertIn("validate-legacy", section)
|
||||
self.assertIn("exclusion 없음", section)
|
||||
|
||||
def test_checkout_cleanup_preserves_verification_without_recursive_delete(self):
|
||||
text = CACHE.read_text()
|
||||
cleanup = text.split("## Worktree 실패 처리와 제거", 1)[1].split("## Credential 안전", 1)[0]
|
||||
self.assertIn("`verification/`이 남으면 Issue directory를 보존한다", cleanup)
|
||||
self.assertIn("Issue directory가 남는 것은 checkout 제거 실패가 아니다", cleanup)
|
||||
self.assertIn("digest가 제거 전과 같음", cleanup)
|
||||
self.assertIn("Artifact 폐기는 별도의 exact 경로 승인", text)
|
||||
self.assertIn("mode `0700`", text)
|
||||
self.assertIn("mode `0600`", text)
|
||||
|
||||
def test_spec_review_uses_immutable_source_and_not_moving_checkout(self):
|
||||
section = SKILLS["review"].read_text().split("## External Issue verification evidence boundary", 1)[1]
|
||||
self.assertIn("제품 테스트를 실행하지 않", section)
|
||||
self.assertIn("moving implementation checkout", section)
|
||||
self.assertIn("authoritative blob evidence", section)
|
||||
self.assertIn("trusted `harness/.runtime`", section)
|
||||
self.assertIn("Producer evidence와 reviewer 실행을 구분", section)
|
||||
|
||||
def test_rework_no_longer_excludes_repo_evidence_from_clean_tree(self):
|
||||
text = SKILLS["rework"].read_text()
|
||||
self.assertNotIn("plan과 manifest를 제외한 clean tree", text)
|
||||
self.assertIn("새 evidence는 checkout 밖", text)
|
||||
self.assertIn("archived v1 read-only", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user