init: llm-wiki-haness 하네스 설계
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
RUNTIME = Path(__file__).resolve().parents[1] / "runtime"
|
||||
if str(RUNTIME) not in sys.path:
|
||||
sys.path.insert(0, str(RUNTIME))
|
||||
|
||||
import semantic_candidate_builder as builder # noqa: E402
|
||||
import semantic_surface_extractor as extractor # noqa: E402
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
class SemanticCandidateBuilderTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tempdir = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.tempdir.name)
|
||||
(self.root / "harness/source").mkdir(parents=True)
|
||||
shutil.copyfile(REPO_ROOT / builder.DEFAULT_ONTOLOGY, self.root / builder.DEFAULT_ONTOLOGY)
|
||||
shutil.copyfile(REPO_ROOT / extractor.DEFAULT_POLICY, self.root / extractor.DEFAULT_POLICY)
|
||||
(self.root / "raw/branch-notes").mkdir(parents=True)
|
||||
self.path = self.root / "raw/branch-notes/feature-candidates.md"
|
||||
body_lines = [
|
||||
"stage-7 owns mapper",
|
||||
"stage-7 produces bundle",
|
||||
"stage-7 consumes input",
|
||||
"build-gate requires `vite build` FE-GATE-BUILD-001@2",
|
||||
"build-gate forbids `vite build` FE-GATE-BUILD-001@2",
|
||||
"component returns model",
|
||||
"component validates model",
|
||||
]
|
||||
self.path.write_text(
|
||||
"---\n"
|
||||
"title: candidate fixture\n"
|
||||
"source_type: branch-note\n"
|
||||
"status: verified\n"
|
||||
"semantic_surface_exclusions:\n"
|
||||
" - branch-contract-packet|fixture\n"
|
||||
" - scope|fixture\n"
|
||||
" - decision-evidence|fixture\n"
|
||||
" - edge-failure-dependency|fixture\n"
|
||||
" - claims-to-verify|fixture\n"
|
||||
"---\n\n"
|
||||
"<!-- section-id: implementation -->\n"
|
||||
"## 구현\n"
|
||||
+ "\n".join(body_lines)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
policy = extractor.load_policy(self.root)
|
||||
self.extraction = extractor.extract_document(self.root, self.path, policy)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.tempdir.cleanup()
|
||||
|
||||
def _assertion_result(self) -> dict[str, object]:
|
||||
surface = self.extraction["surfaces"][0]
|
||||
lines = self.path.read_text(encoding="utf-8").splitlines()
|
||||
quote_lines = {line: number for number, line in enumerate(lines, 1) if number >= surface["line_start"]}
|
||||
specs = [
|
||||
("A1", "stage-7", "owns", "mapper", "must", "stage-7 owns mapper"),
|
||||
("A2", "stage-7", "produces", "bundle", "must", "stage-7 produces bundle"),
|
||||
("A3", "consumer", "consumes", "bundle", "must", "stage-7 consumes input"),
|
||||
("A4", "build-gate", "requires", "vite build", "must", "build-gate requires `vite build` FE-GATE-BUILD-001@2"),
|
||||
("A5", "build-gate", "forbids", "vite build", "must_not", "build-gate forbids `vite build` FE-GATE-BUILD-001@2"),
|
||||
("A6", "component", "returns", "model", "must", "component returns model"),
|
||||
("A7", "component", "validates", "model", "must", "component validates model"),
|
||||
]
|
||||
assertions = []
|
||||
for identifier, subject, predicate, obj, modality, quote in specs:
|
||||
line = quote_lines[quote]
|
||||
assertions.append({
|
||||
"assertion_id": identifier,
|
||||
"source_surface": surface["surface_id"],
|
||||
"subject": subject,
|
||||
"predicate": predicate,
|
||||
"object": obj,
|
||||
"condition": "normal-path",
|
||||
"modality": modality,
|
||||
"scope": "branch",
|
||||
"quote": quote,
|
||||
"line_start": line,
|
||||
"line_end": line,
|
||||
})
|
||||
return {
|
||||
"schema_version": builder.ASSERTION_SCHEMA,
|
||||
"subject": self.extraction["path"],
|
||||
"mode": "local",
|
||||
"surface_manifest_sha256": hashlib.sha256(extractor.canonical_json_bytes(self.extraction)).hexdigest(),
|
||||
"assertions": assertions,
|
||||
}
|
||||
|
||||
def test_exact_ontology_and_seven_candidate_rules(self) -> None:
|
||||
result = builder.build(self.root, self.extraction, self._assertion_result())
|
||||
observed = {rule for item in result["candidates"] for rule in item["rule_ids"]}
|
||||
self.assertTrue({"C1", "C2", "C3", "C4", "C5", "C6", "C7"}.issubset(observed))
|
||||
self.assertEqual(result["coverage"]["processed_surfaces"], result["coverage"]["eligible_surfaces"])
|
||||
|
||||
def test_unknown_predicate_and_unverified_quote_fail(self) -> None:
|
||||
assertions = self._assertion_result()
|
||||
assertions["assertions"][0]["predicate"] = "owner"
|
||||
with self.assertRaises(builder.SemanticCandidateError):
|
||||
builder.build(self.root, self.extraction, assertions)
|
||||
assertions = self._assertion_result()
|
||||
assertions["assertions"][0]["quote"] = "not the source bytes"
|
||||
with self.assertRaises(builder.SemanticCandidateError):
|
||||
builder.build(self.root, self.extraction, assertions)
|
||||
|
||||
def test_surface_manifest_binding_blocks_stale_assertions(self) -> None:
|
||||
assertions = self._assertion_result()
|
||||
assertions["surface_manifest_sha256"] = "0" * 64
|
||||
with self.assertRaises(builder.SemanticCandidateError):
|
||||
builder.build(self.root, self.extraction, assertions)
|
||||
|
||||
def test_unquoted_path_literal_does_not_raise_stop_iteration(self) -> None:
|
||||
assertions = self._assertion_result()
|
||||
first = assertions["assertions"][0]
|
||||
line = first["line_start"]
|
||||
document_lines = self.path.read_text(encoding="utf-8").splitlines()
|
||||
document_lines[line - 1] = "stage-7 owns mapper at src/main/example"
|
||||
self.path.write_text("\n".join(document_lines) + "\n", encoding="utf-8")
|
||||
policy = extractor.load_policy(self.root)
|
||||
extraction = extractor.extract_document(self.root, self.path, policy)
|
||||
first["quote"] = document_lines[line - 1]
|
||||
assertions["surface_manifest_sha256"] = hashlib.sha256(
|
||||
extractor.canonical_json_bytes(extraction)
|
||||
).hexdigest()
|
||||
|
||||
result = builder.build(self.root, extraction, assertions)
|
||||
self.assertEqual(
|
||||
result["coverage"]["processed_surfaces"],
|
||||
result["coverage"]["eligible_surfaces"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user