init: llm-wiki-haness 하네스 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:21:35 +09:00
parent 42bf3db4fd
commit 6c53ded9cb
2436 changed files with 194486 additions and 1 deletions
+518
View File
@@ -0,0 +1,518 @@
#!/usr/bin/env python3
"""Atomically commit one candidate document and all generated reverse views."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import sys
import tempfile
from typing import Any, Callable, Mapping
from fs_transaction import replace_many, stage_repository
import contract_projection
import layout_check
import moc_indexer
import proof_manifest
import quality_gate
import semantic_audit
import semantic_certificate
import semantic_surface_extractor
import typed_contract_check
import vault_migrate
SCHEMA_VERSION = "document-commit/v1"
RESULT_SCHEMA = "document-commit-result/v1"
DEFAULT_ROOT = Path(__file__).resolve().parents[2]
HEX_SHA256 = proof_manifest.HEX_SHA256
QualityRunner = Callable[..., dict[str, Any]]
class DocumentCommitError(ValueError):
def __init__(self, code: str, message: str, location: str = "") -> None:
self.code = code
self.location = location
super().__init__(message)
class PreparedChanges(dict[Path, bytes]):
"""Final bytes plus optimistic preconditions for concurrent-edit detection."""
def __init__(self, values: Mapping[Path, bytes], expected: Mapping[Path, str | None]) -> None:
super().__init__(values)
self.expected = dict(expected)
def _projection_quality_result(staged_root: Path) -> dict[str, Any]:
updates, result = contract_projection.build_updates(staged_root)
current = result.get("status") == "CURRENT" and not updates
return {
"schema_version": contract_projection.RESULT_SCHEMA,
"status": "PASS" if current else "FAIL",
"findings": result.get("findings", []),
}
def _repo_path(root: Path, value: Any, location: str, *, must_exist: bool) -> Path:
if not isinstance(value, str) or not value or "\\" in value:
raise DocumentCommitError("INVALID_PATH", "path must be a non-empty repo-relative POSIX path", location)
candidate = Path(value)
if candidate.is_absolute():
raise DocumentCommitError("PATH_OUTSIDE_REPO", "absolute path is not allowed", location)
resolved = (root / candidate).resolve()
try:
resolved.relative_to(root)
except ValueError as exc:
raise DocumentCommitError("PATH_OUTSIDE_REPO", "path escapes repository root", location) from exc
if must_exist and not resolved.is_file():
raise DocumentCommitError("FILE_NOT_FOUND", "file does not exist", location)
return resolved
def _sha256(value: Any, location: str) -> str:
if not isinstance(value, str) or not HEX_SHA256.fullmatch(value):
raise DocumentCommitError("INVALID_SHA256", "expected 64 lowercase hexadecimal characters", location)
return value
def _plan_sha256(
root: Path,
changes: "PreparedChanges",
*,
candidate_hash: str,
proof_hash: str,
target: Path,
authority: Mapping[str, Any],
) -> str:
document = {
"schema_version": "document-commit-plan/v1",
"candidate_sha256": candidate_hash,
"proof_manifest_sha256": proof_hash,
"target": target.relative_to(root).as_posix(),
"active_layout": {
"authority": authority["authority"],
"manifest_sha256": authority["manifest_sha256"],
"mode": authority["mode"],
"write_roots": authority["write_roots"],
},
"writes": [
{
"path": path.relative_to(root).as_posix(),
"sha256": hashlib.sha256(changes[path]).hexdigest(),
"expected_sha256": changes.expected[path],
}
for path in sorted(changes)
],
}
payload = (json.dumps(document, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + "\n").encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def _request_parts(root: Path, request: Any) -> tuple[Path, str, Path, bool, Path, str]:
required = {"schema_version", "candidate", "target", "proof_manifest"}
if not isinstance(request, dict) or not required.issubset(request) or set(request) - required - {"semantic_audit"}:
raise DocumentCommitError("INVALID_REQUEST", "request has missing or unknown top-level fields")
if request.get("schema_version") != SCHEMA_VERSION:
raise DocumentCommitError("REQUEST_SCHEMA_MISMATCH", f"expected {SCHEMA_VERSION}")
candidate = request.get("candidate")
target = request.get("target")
proof = request.get("proof_manifest")
if not isinstance(candidate, dict) or set(candidate) != {"path", "sha256"}:
raise DocumentCommitError("INVALID_CANDIDATE", "candidate must contain path and sha256", "candidate")
if not isinstance(target, dict) or set(target) != {"path", "must_not_exist"}:
raise DocumentCommitError("INVALID_TARGET", "target must contain path and must_not_exist", "target")
if not isinstance(proof, dict) or set(proof) != {"path", "sha256"}:
raise DocumentCommitError("INVALID_PROOF_MANIFEST", "proof_manifest must contain path and sha256", "proof_manifest")
candidate_path = _repo_path(root, candidate.get("path"), "candidate.path", must_exist=True)
candidate_hash = _sha256(candidate.get("sha256"), "candidate.sha256")
target_path = _repo_path(root, target.get("path"), "target.path", must_exist=False)
if target_path.suffix != ".md" or target_path == candidate_path:
raise DocumentCommitError("INVALID_TARGET", "target must be a distinct Markdown path", "target.path")
if not isinstance(target.get("must_not_exist"), bool):
raise DocumentCommitError("INVALID_TARGET", "must_not_exist must be boolean", "target.must_not_exist")
proof_path = _repo_path(root, proof.get("path"), "proof_manifest.path", must_exist=True)
proof_hash = _sha256(proof.get("sha256"), "proof_manifest.sha256")
return candidate_path, candidate_hash, target_path, target["must_not_exist"], proof_path, proof_hash
def _semantic_parts(root: Path, request: Mapping[str, Any], run_root: Path | None) -> tuple[Path, str, Path, str] | None:
value = request.get("semantic_audit")
if value is None:
return None
if not isinstance(value, dict) or set(value) != {"request", "result"}:
raise DocumentCommitError("INVALID_SEMANTIC_AUDIT", "semantic_audit must contain request and result", "semantic_audit")
parsed: list[Path | str] = []
for key in ("request", "result"):
reference = value.get(key)
if not isinstance(reference, dict) or set(reference) not in ({"path", "sha256"}, {"namespace", "path", "sha256"}):
raise DocumentCommitError("INVALID_SEMANTIC_AUDIT", f"semantic_audit.{key} must contain namespace/path/sha256", f"semantic_audit.{key}")
namespace = reference.get("namespace", "repo")
if namespace not in {"repo", "run"}:
raise DocumentCommitError("INVALID_SEMANTIC_AUDIT", "semantic audit namespace must be repo or run", f"semantic_audit.{key}.namespace")
source_root = root if namespace == "repo" else run_root
if source_root is None:
raise DocumentCommitError("SEMANTIC_RUN_ROOT_REQUIRED", "run namespace requires semantic_run_root", f"semantic_audit.{key}.namespace")
parsed.extend((
_repo_path(source_root, reference.get("path"), f"semantic_audit.{key}.path", must_exist=True),
_sha256(reference.get("sha256"), f"semantic_audit.{key}.sha256"),
))
return parsed[0], parsed[1], parsed[2], parsed[3] # type: ignore[return-value]
def _verify_hash(path: Path, expected: str, code: str, location: str) -> bytes:
content = path.read_bytes()
observed = hashlib.sha256(content).hexdigest()
if observed != expected:
raise DocumentCommitError(code, f"expected {expected}, observed {observed}", location)
return content
def _verify_proof(path: Path, expected_hash: str, root: Path, profiles_path: Path) -> dict[str, Any]:
content = _verify_hash(path, expected_hash, "PROOF_MANIFEST_HASH_MISMATCH", "proof_manifest.sha256")
try:
manifest = json.loads(content.decode("utf-8"))
except (UnicodeError, json.JSONDecodeError) as exc:
raise DocumentCommitError("INVALID_PROOF_MANIFEST", str(exc), "proof_manifest.path") from exc
verification = manifest.get("verification") if isinstance(manifest, dict) else None
if not isinstance(verification, dict) or verification.get("status") != "PASS" or verification.get("fail_count") != 0:
raise DocumentCommitError("PROOF_NOT_PASS", "persisted proof manifest must have PASS and fail_count=0")
profiles = proof_manifest.load_allowed_profiles(profiles_path.resolve(strict=True))
try:
verified = proof_manifest.verify_manifest(manifest, root, profiles)
except proof_manifest.ManifestValidationError as exc:
codes = ",".join(sorted({issue["code"] for issue in exc.issues}))
raise DocumentCommitError("PROOF_REVALIDATION_FAILED", codes, "proof_manifest.path") from exc
if verified["verification"]["fail_count"] != 0:
raise DocumentCommitError("PROOF_NOT_PASS", "failed proofs block document completion")
return verified
def _stage_repository(root: Path, destination: Path) -> None:
stage_repository(root, destination)
def prepare(
root: Path,
request: Any,
*,
profiles_path: Path = proof_manifest.DEFAULT_PROFILES,
relations_path: Path = moc_indexer.DEFAULT_RELATIONS,
layout_path: Path = layout_check.DEFAULT_MANIFEST,
quality_runner: QualityRunner = quality_gate.run,
semantic_run_root: Path | None = None,
) -> tuple[PreparedChanges, dict[str, Any], set[Path]]:
"""Prepare verified final bytes without changing the repository."""
root = root.resolve(strict=True)
candidate, candidate_hash, target, must_not_exist, proof_path, proof_hash = _request_parts(root, request)
# 정체성 판정(TARGET_EXISTS 위치 표기·subject 대조·staging·touched/report)은 심링크를
# 따라가기 *전* 논리 경로로 한다 — canonical cutover 에서 target 을 resolve() 하면
# vault 철자가 되어 인증서의 logical(raw/…) subject 와 영구 mismatch 였다
# (branch_contract_check 가 고친 pre-resolve 규율의 쌍둥이, 2026-07-23 실측).
# resolve() 된 `target` 은 실제 바이트가 쓰일 목적지로만 쓴다.
logical_target = layout_check._lexical_absolute(root / Path(str(request["target"]["path"])))
logical_rel = logical_target.relative_to(root).as_posix()
resolved_run_root = semantic_run_root.resolve(strict=True) if semantic_run_root is not None else None
semantic_parts = _semantic_parts(root, request, resolved_run_root)
try:
authority = layout_check.resolve_authority(root, layout_path)
enforcement_targets = [target]
if (
authority["mode"] == "canonical"
and not logical_target.exists()
and not logical_target.is_symlink()
and vault_migrate._is_legacy_content_path(root, logical_target, layout_path)
):
# 신규 legacy 문서 — 목적지·호환 심링크·manifest 는 expand 의 planner 가
# 계산·검증한다(정본은 그 단계에서 계속 write-root 집행 대상).
enforcement_targets = []
layout_check.enforce_write_paths(root, enforcement_targets, authority)
except layout_check.LayoutContractError as exc:
raise DocumentCommitError(exc.code, str(exc), exc.location) from exc
if must_not_exist and target.exists():
raise DocumentCommitError("TARGET_EXISTS", "target already exists", logical_rel)
candidate_bytes = _verify_hash(candidate, candidate_hash, "CANDIDATE_HASH_MISMATCH", "candidate.sha256")
verified_proof = _verify_proof(proof_path, proof_hash, root, profiles_path)
with tempfile.TemporaryDirectory(prefix="document-commit-stage-") as directory:
stage = Path(directory) / "repo"
stage.mkdir()
_stage_repository(root, stage)
staged_target = stage / logical_rel
staged_target.parent.mkdir(parents=True, exist_ok=True)
target_original_hash = hashlib.sha256(staged_target.read_bytes()).hexdigest() if staged_target.is_file() else None
staged_target.write_bytes(candidate_bytes)
moc_updates, moc_stats = moc_indexer.build_updates(stage, relations_path)
moc_original_hashes = {
path.relative_to(stage).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest()
for path in moc_updates
}
for path, text in moc_updates.items():
path.write_text(text, encoding="utf-8")
remaining, _ = moc_indexer.build_updates(stage, relations_path)
if remaining:
raise DocumentCommitError("MOC_NOT_CONVERGED", "relation indexer did not converge")
projection_updates, projection_result = contract_projection.build_updates(stage)
if projection_result["status"] == "FAIL":
codes = ",".join(sorted({str(item.get("code", "UNKNOWN")) for item in projection_result["findings"]}))
raise DocumentCommitError("TYPED_CONTRACT_FAILED", codes)
projection_original_hashes = {
path.relative_to(stage).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest()
for path in projection_updates
}
for path, text in projection_updates.items():
path.write_text(text, encoding="utf-8")
projection_remaining, projection_after = contract_projection.build_updates(stage)
if projection_remaining or projection_after["status"] != "CURRENT":
raise DocumentCommitError("CONTRACT_PROJECTION_NOT_CONVERGED", "typed projections did not converge")
staged_policy = stage / semantic_surface_extractor.DEFAULT_POLICY
target_frontmatter = semantic_surface_extractor.parse_frontmatter(staged_target.read_text(encoding="utf-8"))
semantic_required = False
if str(target_frontmatter.get("source_type", "")) in {"project-note", "branch-note"}:
if not staged_policy.is_file():
raise DocumentCommitError("SEMANTIC_POLICY_SOURCE_MISSING", "design-bearing document requires semantic surface policy")
policy = semantic_surface_extractor.load_policy(stage)
semantic_required = semantic_surface_extractor.is_required(target_frontmatter, policy)
certificate_stage_path: Path | None = None
certificate_root_path: Path | None = None
certificate_original_hash: str | None = None
certificate_document: dict[str, Any] | None = None
if semantic_parts is not None:
audit_request_path, audit_request_hash, audit_result_path, audit_result_hash = semantic_parts
request_bytes = _verify_hash(audit_request_path, audit_request_hash, "SEMANTIC_AUDIT_REQUEST_HASH_MISMATCH", "semantic_audit.request.sha256")
result_bytes = _verify_hash(audit_result_path, audit_result_hash, "SEMANTIC_AUDIT_RESULT_HASH_MISMATCH", "semantic_audit.result.sha256")
try:
audit_request = json.loads(request_bytes.decode("utf-8"))
audit_result = json.loads(result_bytes.decode("utf-8"))
validated_audit = semantic_audit.validate_result(
stage,
audit_request,
audit_result,
profiles_path=profiles_path,
run_root=resolved_run_root,
)
certificate_stage_path, certificate_bytes, certificate_document = semantic_certificate.prepare_certificate(
stage,
validated_audit,
audit_request=audit_request,
audit_result=audit_result,
run_root=resolved_run_root,
)
except (
UnicodeError,
json.JSONDecodeError,
semantic_audit.SemanticAuditError,
semantic_certificate.SemanticCertificateError,
proof_manifest.ManifestValidationError,
) as exc:
raise DocumentCommitError("SEMANTIC_AUDIT_FAILED", str(exc), "semantic_audit") from exc
if certificate_document["subject"] != logical_rel:
raise DocumentCommitError("SEMANTIC_AUDIT_SUBJECT_MISMATCH", "semantic audit subject must equal target path", "semantic_audit")
if certificate_document["verdict"] != "PASS":
raise DocumentCommitError("SEMANTIC_BLOCKING_VERDICT", "semantic audit contains blocking or readiness-blocking findings", "semantic_audit")
certificate_stage_path.parent.mkdir(parents=True, exist_ok=True)
certificate_stage_path.write_bytes(certificate_bytes)
certificate_root_path = root / certificate_stage_path.relative_to(stage)
certificate_original_hash = hashlib.sha256(certificate_root_path.read_bytes()).hexdigest() if certificate_root_path.is_file() else None
elif semantic_required:
raise DocumentCommitError("SEMANTIC_CERTIFICATE_MISSING", "required design-bearing candidate has no semantic audit", logical_rel)
touched_rel = {logical_rel}
touched_rel.update(path.relative_to(stage).as_posix() for path in moc_updates)
touched_rel.update(path.relative_to(stage).as_posix() for path in projection_updates)
extensions = [
quality_gate.QualityExtension(
"typed-contract",
lambda staged: typed_contract_check.check(
staged,
include_projection=False,
),
),
quality_gate.QualityExtension(
"contract-projection",
_projection_quality_result,
),
]
if certificate_stage_path is not None:
extensions.append(
quality_gate.QualityExtension(
"semantic-certificate",
lambda staged: semantic_certificate.quality_extension(staged, [staged_target]),
)
)
try:
gate = quality_runner(
stage,
sorted(touched_rel),
structure_paths=[logical_rel],
template_root=root,
include_graph=True,
require_moc_convergence=True,
extensions=extensions,
)
except quality_gate.QualityGateError as exc:
raise DocumentCommitError("QUALITY_GATE_ERROR", str(exc)) from exc
if not isinstance(gate, dict) or gate.get("schema_version") != "quality-gate-result/v1":
raise DocumentCommitError("QUALITY_GATE_ERROR", "unexpected quality gate result schema")
if gate.get("status") != "PASS":
codes = ",".join(sorted({str(item.get("code", "UNKNOWN")) for item in gate.get("findings", [])}))
raise DocumentCommitError("QUALITY_GATE_FAILED", codes)
raw_changes = {root / rel: (stage / rel).read_bytes() for rel in sorted(touched_rel)}
expected_before_expansion = {
root / rel: (
target_original_hash
if rel == logical_rel
else moc_original_hashes.get(rel)
if rel in moc_original_hashes
else projection_original_hashes.get(rel)
)
for rel in sorted(touched_rel)
}
try:
expanded, authority = vault_migrate.expand_authoritative_changes(
root,
raw_changes,
layout_path=layout_path,
relations_path=relations_path,
)
except vault_migrate.MigrationError as exc:
raise DocumentCommitError(exc.code, str(exc), exc.location) from exc
if certificate_stage_path is not None and certificate_root_path is not None:
expanded[certificate_root_path] = certificate_stage_path.read_bytes()
expected_before_expansion[certificate_root_path] = certificate_original_hash
expected = {
path: expected_before_expansion.get(
path,
hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None,
)
for path in expanded
}
changes = PreparedChanges(expanded, expected)
forbidden = {path for path, digest in changes.expected.items() if digest is None} if must_not_exist else set()
plan_sha256 = _plan_sha256(
root,
changes,
candidate_hash=candidate_hash,
proof_hash=proof_hash,
target=root / logical_rel,
authority=authority,
)
result = {
"target": logical_rel,
"candidate_sha256": candidate_hash,
"proof_manifest": proof_path.relative_to(root).as_posix(),
"proof_manifest_sha256": proof_hash,
"proof_count": verified_proof["verification"]["proof_count"],
"semantic_certificate": (
certificate_root_path.relative_to(root).as_posix()
if certificate_root_path is not None
else None
),
"relation_edges": moc_stats["canonical_edges"],
"changed_paths": [path.relative_to(root).as_posix() for path in sorted(changes)],
"active_layout": {
"mode": authority["mode"],
"authority": authority["authority"],
"write_roots": authority["write_roots"],
"manifest_sha256": authority["manifest_sha256"],
},
"plan_sha256": plan_sha256,
}
return changes, result, forbidden
def commit(changes: PreparedChanges, forbidden: set[Path]) -> None:
"""Check snapshot preconditions, then perform exactly one multi-file replace."""
for path, expected_hash in changes.expected.items():
if expected_hash is None:
if path.exists():
raise DocumentCommitError("CONCURRENT_MODIFICATION", "new target appeared after staging", path.as_posix())
continue
if not path.is_file():
raise DocumentCommitError("CONCURRENT_MODIFICATION", "existing target disappeared after staging", path.as_posix())
observed = hashlib.sha256(path.read_bytes()).hexdigest()
if observed != expected_hash:
raise DocumentCommitError(
"CONCURRENT_MODIFICATION",
f"expected pre-commit hash {expected_hash}, observed {observed}",
path.as_posix(),
)
replace_many(changes, must_not_exist=forbidden)
def _failure(exc: Exception) -> dict[str, Any]:
return {
"schema_version": RESULT_SCHEMA,
"status": "FAIL",
"error": {
"code": getattr(exc, "code", "IO_ERROR"),
"location": getattr(exc, "location", ""),
"message": str(exc),
},
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("request", type=Path)
parser.add_argument("--root", type=Path, default=DEFAULT_ROOT)
parser.add_argument("--profiles", type=Path, default=proof_manifest.DEFAULT_PROFILES)
parser.add_argument("--relations", type=Path, default=moc_indexer.DEFAULT_RELATIONS)
parser.add_argument("--layout", type=Path, default=layout_check.DEFAULT_MANIFEST)
parser.add_argument("--semantic-run-root", type=Path)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--dry-run", action="store_true")
mode.add_argument("--apply", action="store_true")
parser.add_argument("--expected-plan-sha256")
args = parser.parse_args(argv)
try:
root = args.root.resolve(strict=True)
request = json.loads(args.request.read_text(encoding="utf-8"))
changes, result, forbidden = prepare(
root,
request,
profiles_path=args.profiles,
relations_path=args.relations,
layout_path=args.layout,
semantic_run_root=args.semantic_run_root,
)
if args.apply:
if args.expected_plan_sha256 is not None:
expected = args.expected_plan_sha256
if not HEX_SHA256.fullmatch(expected):
raise DocumentCommitError("INVALID_PLAN_SHA256", "expected plan hash must be 64 lowercase hex characters")
if expected != result["plan_sha256"]:
raise DocumentCommitError(
"PLAN_HASH_MISMATCH",
f"expected {expected}, current plan is {result['plan_sha256']}",
)
commit(changes, forbidden)
json.dump(
{"schema_version": RESULT_SCHEMA, "status": "APPLIED" if args.apply else "DRY_RUN", **result},
sys.stdout,
ensure_ascii=False,
indent=2,
sort_keys=True,
)
sys.stdout.write("\n")
return 0
except (DocumentCommitError, proof_manifest.ManifestValidationError) as exc:
json.dump(_failure(exc), sys.stdout, ensure_ascii=False, indent=2, sort_keys=True)
sys.stdout.write("\n")
return 1
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
json.dump(_failure(exc), sys.stdout, ensure_ascii=False, indent=2, sort_keys=True)
sys.stdout.write("\n")
return 2
if __name__ == "__main__":
raise SystemExit(main())