Files
yao-meta-skill/scripts/adaptation_patch_safety.py
YAO 31ce04c655 Split meta skill CLI and review gates
Merge the beta-ready Yao Meta Skill architecture, report, evidence gate, and release-boundary updates.\n\nRelease boundary: beta/public testing is allowed; formal world-class, fully reviewed, or superiority claims remain blocked until the pending evidence gates are accepted.
2026-06-17 18:43:02 +08:00

77 lines
2.6 KiB
Python

"""Patch target and baseline hash checks for approval-gated adaptation."""
import hashlib
from pathlib import Path
from typing import Any
SCRIPT_INTERFACE = "internal-module"
SCRIPT_INTERFACE_REASON = "Shared adaptation patch target parsing and target-file baseline verification helpers."
BLOCKED_PATH_PARTS = {".git", "__pycache__", ".pytest_cache", "dist"}
ABSENT_FILE_SHA256 = "__absent__"
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def target_file_sha256(skill_dir: Path, target_files: list[str]) -> dict[str, str]:
observed: dict[str, str] = {}
for target in target_files:
path = skill_dir / target
observed[target] = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256
return observed
def normalize_patch_path(raw: str) -> str | None:
token = raw.strip().split("\t", 1)[0].split(" ", 1)[0]
if token == "/dev/null":
return None
if token.startswith("a/") or token.startswith("b/"):
token = token[2:]
path = Path(token)
if path.is_absolute() or ".." in path.parts or any(part in BLOCKED_PATH_PARTS for part in path.parts):
raise ValueError(f"Unsafe patch path: {raw}")
if not token or token == ".":
raise ValueError(f"Empty patch path: {raw}")
return token
def patch_target_files(patch_text: str) -> list[str]:
targets: set[str] = set()
for line in patch_text.splitlines():
if line.startswith("--- ") or line.startswith("+++ "):
raw = line[4:].strip()
path = normalize_patch_path(raw)
if path:
targets.add(path)
return sorted(targets)
def validate_target_file_sha256(
skill_dir: Path,
target_files: list[str],
expected_sha256: dict[str, Any],
) -> tuple[list[str], dict[str, str]]:
failures: list[str] = []
observed: dict[str, str] = {}
for target in target_files:
path = skill_dir / target
expected = str(expected_sha256.get(target, ""))
if not expected:
failures.append(f"Approval target_file_sha256 missing target: {target}")
continue
if path.exists() and not path.is_file():
failures.append(f"Patch target is not a file: {target}")
continue
current = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256
observed[target] = current
if current != expected:
failures.append(f"Target file baseline sha256 does not match approval ledger: {target}")
return failures, observed