init: llm-wiki-haness 하네스 설계
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
RUNTIME = REPO_ROOT / "harness/runtime"
|
||||
sys.path.insert(0, str(RUNTIME))
|
||||
|
||||
import branch_contract_check # noqa: E402
|
||||
import branch_from_project # noqa: E402
|
||||
|
||||
|
||||
PROJECT = """---
|
||||
title: sample
|
||||
source_type: project-note
|
||||
id: sample-project
|
||||
project_revision: 3
|
||||
---
|
||||
# Sample
|
||||
<!-- section-id: project-decisions -->
|
||||
## 결정
|
||||
| Decision ID | Revision | Domain | Decision Summary | Status | Owner | Evidence |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `DEC-SAMPLE-PROJECT-API-001` | 2 | api | canonical summary | active | project | C1 |
|
||||
<!-- section-id: project-work-items -->
|
||||
## 작업
|
||||
| Work Item ID | branch slug | 완료 조건 (측정가능) | Applies Decisions | Dependencies | Status |
|
||||
|---|---|---|---|---|---|
|
||||
| `WI-SAMPLE-PROJECT-001` | `feature-sample-api-contract-runtime` | 검사가 통과한다 | `DEC-SAMPLE-PROJECT-API-001@2` | - | `planned` |
|
||||
## 묶음
|
||||
"""
|
||||
|
||||
|
||||
class BranchContractCheckTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(temporary.cleanup)
|
||||
self.root = Path(temporary.name)
|
||||
for path in (
|
||||
"raw/project-notes",
|
||||
"raw/branch-notes",
|
||||
"harness/source",
|
||||
"harness/adapters",
|
||||
"harness/runtime",
|
||||
"harness/tests",
|
||||
"vault/10-projects",
|
||||
):
|
||||
(self.root / path).mkdir(parents=True, exist_ok=True)
|
||||
(self.root / "raw/project-notes/sample-project.md").write_text(PROJECT, encoding="utf-8")
|
||||
(self.root / "harness/source/vault-layout.json").write_text(
|
||||
json.dumps({
|
||||
"schema_version": "vault-layout/v1",
|
||||
"mode": "compatibility",
|
||||
"vault_root": "vault",
|
||||
"canonical_mapping": {
|
||||
"schema_version": "project-first-paths/v1",
|
||||
"project_relation": "branch-to-project",
|
||||
"project_member_pattern": "{vault_root}/{area}/{project}/{category}/{relative_path}",
|
||||
"default_pattern": "{vault_root}/{area}/{category}/{relative_path}",
|
||||
},
|
||||
"write_roots": {"compatibility": ["raw", "wiki"], "shadow": ["raw", "wiki"], "canonical": ["vault"]},
|
||||
"migration_manifest": {"schema_version": "vault-migration/v1", "entries": []},
|
||||
"rollback_mapping": {"schema_version": "vault-rollback/v1", "entries": []},
|
||||
"areas": {"10-projects": ["raw/project-notes", "raw/branch-notes"]},
|
||||
}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
changes, result = branch_from_project.prepare(
|
||||
self.root, "sample-project", "WI-SAMPLE-PROJECT-001"
|
||||
)
|
||||
for path, content in changes.items():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(content)
|
||||
self.branch = self.root / result["target"]
|
||||
|
||||
def test_result_exposes_contract_and_generated_hashes(self) -> None:
|
||||
result = branch_contract_check.validate(self.root, self.branch)
|
||||
self.assertEqual(result["status"], "PASS", result)
|
||||
self.assertEqual(result["project"], "sample-project")
|
||||
self.assertEqual(result["work_item"], "WI-SAMPLE-PROJECT-001")
|
||||
self.assertEqual(result["project_revision"], 3)
|
||||
self.assertEqual(result["inherits"], ["DEC-SAMPLE-PROJECT-API-001@2"])
|
||||
self.assertEqual(result["editable_sections"], ["branch-parent", "branch-goal", "branch-scope"])
|
||||
self.assertEqual(
|
||||
result["generated_hashes"]["declared_sha256"],
|
||||
result["generated_hashes"]["observed_sha256"],
|
||||
)
|
||||
|
||||
def test_failed_candidate_postflight_leaves_target_unchanged(self) -> None:
|
||||
original = self.branch.read_bytes()
|
||||
candidate = self.root / "candidate.md"
|
||||
candidate.write_text(
|
||||
original.decode("utf-8").replace("canonical summary", "tampered summary", 1),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = branch_contract_check.validate_candidate(
|
||||
self.root, self.branch, candidate, postflight=True
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "FAIL")
|
||||
self.assertIn("GENERATED_REGION_DRIFT", {item["code"] for item in result["findings"]})
|
||||
self.assertEqual(self.branch.read_bytes(), original)
|
||||
self.assertTrue(result["staged"])
|
||||
|
||||
def test_override_mismatch_exposes_legacy_and_alias_codes(self) -> None:
|
||||
candidate = self.root / "candidate.md"
|
||||
candidate.write_text(
|
||||
self.branch.read_text(encoding="utf-8").replace(
|
||||
"overrides: []", "overrides: [DEC-SAMPLE-PROJECT-API-001@2]", 1
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = branch_contract_check.validate_candidate(self.root, self.branch, candidate)
|
||||
codes = {item["code"] for item in result["findings"]}
|
||||
self.assertIn("OVERRIDE_APPROVAL_MISMATCH", codes)
|
||||
self.assertIn("UNDECLARED_OVERRIDE", codes)
|
||||
self.assertEqual(
|
||||
result["failure_code_aliases"]["OVERRIDE_APPROVAL_MISMATCH"],
|
||||
["UNDECLARED_OVERRIDE"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user