refactor: unify impl operation gates and correction verification

This commit is contained in:
2026-10-13 16:35:52 +00:00
parent 4e9c46bba2
commit bd73c97a97
24 changed files with 1992 additions and 682 deletions
@@ -0,0 +1,17 @@
"""Read only the explicitly required workflow reference for canonical impl skills."""
from pathlib import Path
NAMES = {"gitea-issue-implementation", "gitea-issue-rework"}
REQUIRED = "Required reference: [workflow](references/workflow.md)"
def read_contract(path):
path = Path(path)
text = path.read_text(encoding="utf-8")
if path.name == "SKILL.md" and path.parent.name in NAMES:
if REQUIRED not in text:
raise AssertionError("impl skill does not require its canonical workflow reference")
reference = path.parent / "references/workflow.md"
if reference.is_symlink() or not reference.is_file():
raise AssertionError("canonical workflow reference missing or symlinked")
text += "\n" + reference.read_text(encoding="utf-8")
return text
@@ -1,5 +1,6 @@
"""Executable consumer declaration contract wiring."""
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
import unittest
ROOT = Path(__file__).resolve().parents[3]
@@ -22,7 +23,7 @@ class DelegationAdoptionContractTests(unittest.TestCase):
'review/skills/gitea-code-review',
)
for path in paths:
text = (ROOT / 'harness/agents' / path / 'SKILL.md').read_text()
text = read_contract(ROOT / 'harness/agents' / path / 'SKILL.md')
with self.subTest(path=path):
self.assertIn('require_adoption', text)
self.assertIn('mode', text)
@@ -1,5 +1,6 @@
"""Procedural consumer wiring is not proof of live agent execution."""
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
import unittest
ROOT = Path(__file__).resolve().parents[2]
@@ -15,7 +16,7 @@ class DelegationEvidenceContractTests(unittest.TestCase):
def test_consumers_require_gate_before_adoption(self):
for path in ROLES:
with self.subTest(path=path):
text = (ROOT / path).read_text()
text = read_contract(ROOT / path)
for required in ['## Delegation evidence acceptance gate',
'delegation_evidence.py', '위임 결과 채택 이전',
'main이 독립적으로 고정한 context',
@@ -7,6 +7,7 @@ import subprocess
import tempfile
import unittest
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
ROOT = Path(__file__).resolve().parents[3]
POLICY = ROOT / "harness/operations/repository-cache.md"
@@ -38,7 +39,7 @@ class GitCredentialRecipeTests(unittest.TestCase):
self.assertIn(required, section)
policy = POLICY.read_text(encoding="utf-8")
self.assertIn("Lock 파일과 임시 credential helper는 실행 role 소유와 mode `0600`을 요구한다", policy)
delivery = REWORK.read_text(encoding="utf-8").split("### 7. 기존 PR branch 전달", 1)[1].split("###", 1)[0]
delivery = read_contract(REWORK).split("### 7. 기존 PR branch 전달", 1)[1].split("###", 1)[0]
self.assertIn("#non-file-git-credential-helper", delivery)
self.assertNotIn("필요한 temporary askpass helper", delivery)
@@ -1,6 +1,7 @@
"""Contract wiring for the read-only inventory source-binding preflight."""
import unittest
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
ROOT = Path(__file__).resolve().parents[2]
@@ -15,7 +16,7 @@ class InventoryBindingDocumentationTests(unittest.TestCase):
]
for path in paths:
with self.subTest(path=path):
text = (ROOT / path).read_text()
text = read_contract(ROOT / path)
self.assertIn('test_inventory_binding.py', text)
self.assertIn('check-tests 이전', text)
self.assertIn('독립적으로 고정한 revision과 stage', text)
@@ -7,6 +7,7 @@ import importlib.util
import json
import os
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
import subprocess
import sys
import tempfile
@@ -19,7 +20,7 @@ HEADING = '### Conditional pytest evidence recipe'
class PytestEvidenceRecipeTests(unittest.TestCase):
def recipe(self):
text = SKILL.read_text()
text = read_contract(SKILL)
self.assertIn(HEADING, text, 'conditional pytest recipe is missing')
section = text.split(HEADING, 1)[1].split('\n### ', 1)[0]
return section, json.loads(section.split('```json\n', 1)[1].split('\n```', 1)[0])
@@ -3,6 +3,7 @@ import subprocess
import sys
import unittest
from pathlib import Path
from harness.tests.common.impl_contract_docs import read_contract
ROOT = Path(__file__).resolve().parents[3]
SCRIPT = ROOT / "harness/agents/impl/skills/gitea-issue-rework/scripts/evidence_manifest.py"
@@ -29,7 +30,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
self.assertIn("성공 기록을 실패로 덮어쓰지 않는다", text)
for path in SKILLS.values():
with self.subTest(path=path.name):
role = path.read_text(encoding="utf-8")
role = read_contract(path)
gate = role.split("## Managed-lock broker gate", 1)[1].split("\n## ", 1)[0]
self.assertIn("broker-generated", gate)
self.assertIn("ready JSON", gate)
@@ -38,11 +39,11 @@ class VerificationContractDocumentationTests(unittest.TestCase):
def test_role_runtime_startup_precedes_and_does_not_replace_admission(self):
roles = [path for path in (ROOT / 'harness/agents').rglob('SKILL.md')
if 'broker-generated runtime' in path.read_text()]
if 'broker-generated runtime' in read_contract(path)]
self.assertEqual(len(roles), 6)
for path in roles:
with self.subTest(role=path.parent.name):
gate = path.read_text().split('## Managed-lock broker gate', 1)[1].split('\n## ', 1)[0]
gate = read_contract(path).split('## Managed-lock broker gate', 1)[1].split('\n## ', 1)[0]
self.assertIn('ready 이후 각 workflow의 진입 검증·승인 조건을 확인한다', gate)
self.assertIn('Runtime 생성은 approval이나 worktree admission을 대신하지 않는다', gate)
self.assertIn('ready JSON의 `handle`/`socket`', gate)
@@ -55,7 +56,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
self.assertIn("PYTHONDONTWRITEBYTECODE", plan["command_env_allowlist"])
def test_full_suite_tracked_background_reconciles_before_retry(self):
text = SKILLS['rework'].read_text()
text = read_contract(SKILLS['rework'])
heading = '### Tracked bounded background evidence execution'
self.assertIn(heading, text)
section = text.split(heading, 1)[1].split('\n### ', 1)[0]
@@ -78,7 +79,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
self.assertGreater(command["timeout_seconds"], 0)
self.assertLessEqual(command["timeout_seconds"], 3600)
for path in (CACHE, *SKILLS.values()):
text = path.read_text()
text = read_contract(path)
for token in ("evidence-manifest/v3", "timeout_seconds", "process group",
"validate-archived-v2", "TDD RED", "자동 재실행"):
with self.subTest(path=str(path), token=token):
@@ -92,7 +93,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
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")
text = read_contract(path)
self.assertIn(CANONICAL, text)
self.assertIn("evidence_gap", text)
self.assertIn("[REDACTED]", text)
@@ -100,7 +101,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
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]
section = read_contract(path).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", " ")
@@ -135,7 +136,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
def test_prepare_contract_binds_root_and_forbids_recursive_parent_creation(self):
for path in (CACHE, SKILLS["implementation"], SKILLS["rework"]):
with self.subTest(path=path.name):
text = path.read_text(encoding="utf-8")
text = read_contract(path)
self.assertIn("exact harness root", text)
self.assertIn("Path.mkdir(parents=True, mode=0o700)", text)
self.assertIn("자동 chmod", text)
@@ -163,7 +164,7 @@ class VerificationContractDocumentationTests(unittest.TestCase):
guard = text.split('### Shared workflow guard gates', 1)[1].split('Issue별 검증 실행 기록의 정본', 1)[0]
self.assertNotIn('or verification root', guard)
for path in (SKILLS['implementation'], SKILLS['rework']):
self.assertNotIn('outside product checkout and verification root', path.read_text())
self.assertNotIn('outside product checkout and verification root', read_contract(path))
self.assertIn('verification/<run-id>', guard)
def test_workflow_guard_readonly_scope_does_not_ban_approved_worker_edits(self):
@@ -173,13 +174,13 @@ class VerificationContractDocumentationTests(unittest.TestCase):
self.assertIn('쓰기 worker는 승인된 변경 allowlist', guard)
def test_workflow_guard_snapshot_protects_scope_before_delegation(self):
implementation = SKILLS["implementation"].read_text()
implementation = read_contract(SKILLS["implementation"])
self.assertIn("workflow_guard.py snapshot", implementation)
self.assertIn("compare-source --repo-root", implementation)
self.assertIn("immutable protected scope", implementation)
def test_workflow_guard_inventory_is_checked_before_adopting_results(self):
rework = SKILLS["rework"].read_text()
rework = read_contract(SKILLS["rework"])
self.assertIn("workflow_guard.py check-tests", rework)
self.assertIn("test inventory", rework)
self.assertIn("RED/GREEN records must be named", rework)
@@ -215,13 +216,13 @@ class VerificationContractDocumentationTests(unittest.TestCase):
masked = 'authorization: ' + '*' * 3 + ' verification'
helper = ROOT / 'harness/lib/workflow_guard.py'
for path in (helper, CACHE):
text = path.read_text()
text = read_contract(path)
with self.subTest(path=path.name):
self.assertEqual(text.count(declaration), 1)
self.assertNotIn(masked, text)
def test_rework_no_longer_excludes_repo_evidence_from_clean_tree(self):
text = SKILLS["rework"].read_text()
text = read_contract(SKILLS["rework"])
self.assertNotIn("plan과 manifest를 제외한 clean tree", text)
self.assertIn("새 evidence는 checkout 밖", text)
self.assertIn("archived v1 read-only", text)