feat: implement numbered patch-stack apply/verify enforcement (DGR-028)
Split the range-loader patch into single-concern patches 0002-0005 (loader, filtered state report, boundary I/O endpoint guard, worker range-report hook), add UPSTREAM-ASSUMPTIONS.json describing each patch's assumptions, and enforce control-plane/license boundary checks plus first-incompatible- patch reporting in scripts/llama_cpp_dependency.py apply/reverse/verify. 7 passed in tests/test_llama_cpp_dependency.py; SHA256SUMS verified against all five patches; focused native CTest (test-meshnet-range-ownership 1/1) recorded in evidence README (build/ dir not present in this environment to independently reverify).
This commit is contained in:
@@ -3,6 +3,13 @@
|
||||
|
||||
This tool deliberately owns only a source dependency boundary. It never
|
||||
downloads a model, invokes inference, or interprets generated text.
|
||||
|
||||
DGR-028 adds the numbered patch-stack contract: the ordered series applies,
|
||||
checks, and reverses deterministically against the exact manifest pin, each
|
||||
patch's recorded upstream file/API assumptions are enforced before it is
|
||||
attempted, the first incompatible patch is named on failure, and the stack is
|
||||
refused if it carries license/attribution damage or Meshnet control-plane
|
||||
(routing, billing, relay, authentication) code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -61,6 +68,11 @@ def _run(*args: str, cwd: pathlib.Path | None = None) -> str:
|
||||
return completed.stdout.strip()
|
||||
|
||||
|
||||
def _git(source: pathlib.Path, *args: str) -> str:
|
||||
"""Run Git against the materialized upstream checkout."""
|
||||
return _run("git", "-C", str(source), *args)
|
||||
|
||||
|
||||
def _load_lock() -> dict[str, Any]:
|
||||
try:
|
||||
lock = json.loads(LOCK_PATH.read_text())
|
||||
@@ -113,8 +125,171 @@ def _patches(lock: dict[str, Any]) -> list[pathlib.Path]:
|
||||
return patches
|
||||
|
||||
|
||||
def _git(source: pathlib.Path, *args: str) -> str:
|
||||
return _run("git", "-C", str(source), *args)
|
||||
def _parse_patch_files(patch: pathlib.Path) -> dict[str, tuple[str | None, str | None]]:
|
||||
"""Parse one patch into {path: (before-short, after-short)}.
|
||||
|
||||
Short object IDs come from the patch's ``index`` lines; an all-zero side
|
||||
means the file is created (``before is None``) or deleted (``after is
|
||||
None``). Patch order is preserved.
|
||||
"""
|
||||
files: dict[str, tuple[str | None, str | None]] = {}
|
||||
current: str | None = None
|
||||
for line in patch.read_text().splitlines():
|
||||
header = re.match(r"^diff --git a/(.+) b/(.+)$", line)
|
||||
if header:
|
||||
if header.group(1) != header.group(2):
|
||||
raise DependencyError(f"{patch.name}: rename/copy diffs are unsupported: {line}")
|
||||
current = header.group(1)
|
||||
files[current] = (None, None)
|
||||
continue
|
||||
index = re.match(r"^index ([0-9a-f]{7,40})\.\.([0-9a-f]{7,40})(?:\s|$)", line)
|
||||
if index and current is not None:
|
||||
before, after = index.group(1), index.group(2)
|
||||
files[current] = (
|
||||
None if set(before) == {"0"} else before,
|
||||
None if set(after) == {"0"} else after,
|
||||
)
|
||||
if not files:
|
||||
raise DependencyError(f"{patch.name}: no file diffs found")
|
||||
return files
|
||||
|
||||
|
||||
_ASSUMPTIONS_DEFAULT = "patches/UPSTREAM-ASSUMPTIONS.json"
|
||||
|
||||
|
||||
def _assumptions(lock: dict[str, Any], patches: list[pathlib.Path]) -> dict[str, Any]:
|
||||
"""Load and validate the recorded upstream file/API assumptions.
|
||||
|
||||
The record must cover exactly the ordered series, each recorded file must
|
||||
match the patch's parsed diff headers, and each recorded full object ID
|
||||
must agree with the patch's abbreviated ``index`` IDs. A stale or edited
|
||||
record is a fail-closed error, never a warning.
|
||||
"""
|
||||
configured = lock.get("patch_assumptions", _ASSUMPTIONS_DEFAULT)
|
||||
if not isinstance(configured, str) or not configured:
|
||||
raise DependencyError("patch_assumptions must name a manifest-relative JSON path")
|
||||
relative = pathlib.Path(configured)
|
||||
if relative.is_absolute() or ".." in relative.parts:
|
||||
raise DependencyError("patch_assumptions must stay inside the repository manifest tree")
|
||||
candidate = (LLAMA_DIR / relative).absolute()
|
||||
try:
|
||||
candidate.relative_to(LLAMA_DIR.absolute())
|
||||
except ValueError as error:
|
||||
raise DependencyError("patch_assumptions must live under packages/node/native/llama") from error
|
||||
if not candidate.is_file():
|
||||
raise DependencyError(f"recorded upstream assumptions are missing: {candidate}")
|
||||
try:
|
||||
doc = json.loads(candidate.read_text())
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise DependencyError(f"invalid upstream assumptions: {candidate}: {error}") from error
|
||||
if not isinstance(doc, dict) or doc.get("schema_version") != 1:
|
||||
raise DependencyError("upstream assumptions must declare schema_version 1")
|
||||
if doc.get("upstream_commit") != lock["commit"]:
|
||||
raise DependencyError("upstream assumptions disagree with the locked commit")
|
||||
recorded = doc.get("patches")
|
||||
if not isinstance(recorded, dict):
|
||||
raise DependencyError("upstream assumptions must record a patches object")
|
||||
names = [patch.name for patch in patches]
|
||||
if list(recorded.keys()) != names:
|
||||
raise DependencyError(
|
||||
f"upstream assumptions do not cover exactly the ordered series: "
|
||||
f"expected {names}, got {list(recorded.keys())}"
|
||||
)
|
||||
for patch in patches:
|
||||
entry = recorded[patch.name]
|
||||
if not isinstance(entry.get("concern"), str) or not entry["concern"]:
|
||||
raise DependencyError(f"{patch.name}: assumptions are missing the scoped concern")
|
||||
symbols = entry.get("api_assumptions")
|
||||
if not isinstance(symbols, list) or not symbols or not all(isinstance(s, str) and s for s in symbols):
|
||||
raise DependencyError(f"{patch.name}: assumptions must record upstream file/API assumptions")
|
||||
files = entry.get("files")
|
||||
if not isinstance(files, dict):
|
||||
raise DependencyError(f"{patch.name}: assumptions must record a files object")
|
||||
parsed = _parse_patch_files(patch)
|
||||
if list(files.keys()) != list(parsed.keys()):
|
||||
raise DependencyError(
|
||||
f"{patch.name}: recorded files {list(files.keys())} disagree with the patch bytes {list(parsed.keys())}"
|
||||
)
|
||||
for path, (before_short, after_short) in parsed.items():
|
||||
blobs = files[path]
|
||||
if not isinstance(blobs, dict):
|
||||
raise DependencyError(f"{patch.name}: recorded blobs for {path} must be an object")
|
||||
for side, short in (("before", before_short), ("after", after_short)):
|
||||
value = blobs.get(side)
|
||||
if short is None:
|
||||
if value is not None:
|
||||
raise DependencyError(f"{patch.name}: {path} {side} must be null for a created/deleted file")
|
||||
elif not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{40}", value) or not value.startswith(short):
|
||||
raise DependencyError(
|
||||
f"{patch.name}: recorded {side} blob for {path} does not match the patch index ID {short}"
|
||||
)
|
||||
return doc
|
||||
|
||||
|
||||
# Control-plane vocabulary that must never enter the upstream patch stack:
|
||||
# Meshnet routing, billing, relay, authentication, and transport semantics are
|
||||
# backend-agnostic and live outside the llama.cpp fork boundary (ADR-0024).
|
||||
_CONTROL_PLANE_TERMS = re.compile(
|
||||
r"\b(tracker|routing|route session|grpc|billing|wallet|relay|telemetry|"
|
||||
r"auth|authentication|load balanc\w*)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
_LICENSE_PATH = re.compile(r"(^|/)(license|copying|notice)(\..*)?$", re.IGNORECASE)
|
||||
|
||||
_LICENSE_TEXT = re.compile(
|
||||
r"copyright|permission is hereby granted|mit license|apache license|gnu general public",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _verify_patch_stack_boundaries(patches: list[pathlib.Path]) -> None:
|
||||
"""Refuse license/attribution damage or control-plane code in the stack."""
|
||||
for patch in patches:
|
||||
for line in patch.read_text().splitlines():
|
||||
header = re.match(r"^diff --git a/(.+) b/(.+)$", line)
|
||||
if header and (_LICENSE_PATH.search(header.group(1)) or _LICENSE_PATH.search(header.group(2))):
|
||||
raise DependencyError(f"{patch.name}: license/attribution files may not be patched: {line}")
|
||||
if line.startswith("-") and not line.startswith("---") and _LICENSE_TEXT.search(line[1:]):
|
||||
raise DependencyError(f"{patch.name}: removing license or attribution text is refused: {line}")
|
||||
body = patch.read_text()
|
||||
match = _CONTROL_PLANE_TERMS.search(body)
|
||||
if match:
|
||||
raise DependencyError(
|
||||
f"{patch.name}: Meshnet control-plane term {match.group(0)!r} must not enter the patch stack"
|
||||
)
|
||||
|
||||
|
||||
def _index_blob(source: pathlib.Path, path: str) -> str | None:
|
||||
"""Return the staged blob object ID for path, or None when absent."""
|
||||
records = _git(source, "ls-files", "-s", "-z", "--", path).split("\0")
|
||||
entries = [record for record in records if record]
|
||||
if not entries:
|
||||
return None
|
||||
if len(entries) != 1:
|
||||
raise DependencyError(f"unmerged index entry blocks patch verification: {path}")
|
||||
metadata, _ = entries[0].split("\t", 1)
|
||||
mode, blob, stage = metadata.split()
|
||||
if stage != "0":
|
||||
raise DependencyError(f"unmerged index entry blocks patch verification: {path}")
|
||||
return blob
|
||||
|
||||
|
||||
def _check_assumption_blobs(
|
||||
source: pathlib.Path,
|
||||
patch: pathlib.Path,
|
||||
files: dict[str, Any],
|
||||
side: str,
|
||||
) -> None:
|
||||
"""Fail on the first recorded pre-/post-image blob the index disagrees with."""
|
||||
for path, blobs in files.items():
|
||||
expected = blobs[side]
|
||||
actual = _index_blob(source, path)
|
||||
if actual != expected:
|
||||
raise DependencyError(
|
||||
f"first incompatible patch: {patch.name}: recorded {side} blob for {path} is "
|
||||
f"{expected}, found {actual}"
|
||||
)
|
||||
|
||||
|
||||
def _verify_tracked_content(source: pathlib.Path, lock: dict[str, Any]) -> None:
|
||||
@@ -235,16 +410,36 @@ def fetch(workspace: pathlib.Path) -> pathlib.Path:
|
||||
def apply(source: pathlib.Path) -> None:
|
||||
lock = _load_lock()
|
||||
patches = _patches(lock)
|
||||
assumptions = _assumptions(lock, patches)
|
||||
_verify_patch_stack_boundaries(patches)
|
||||
_verify_source(source, lock, require_clean=True)
|
||||
for patch in patches:
|
||||
files = assumptions["patches"][patch.name]["files"]
|
||||
_check_assumption_blobs(source, patch, files, "before")
|
||||
_git(source, "apply", "--check", str(patch))
|
||||
_git(source, "apply", "--index", str(patch))
|
||||
_check_assumption_blobs(source, patch, files, "after")
|
||||
_verify_patched_source(source, lock)
|
||||
|
||||
|
||||
def reverse(source: pathlib.Path) -> None:
|
||||
"""Reverse the complete verified stack and recover the exact clean pin."""
|
||||
lock = _load_lock()
|
||||
patches = _patches(lock)
|
||||
assumptions = _assumptions(lock, patches)
|
||||
_verify_patch_stack_boundaries(patches)
|
||||
_verify_source(source, lock, require_clean=False)
|
||||
_verify_patched_source(source, lock)
|
||||
for patch in reversed(patches):
|
||||
files = assumptions["patches"][patch.name]["files"]
|
||||
_check_assumption_blobs(source, patch, files, "after")
|
||||
_git(source, "apply", "--reverse", "--check", str(patch))
|
||||
_git(source, "apply", "--reverse", "--index", str(patch))
|
||||
_check_assumption_blobs(source, patch, files, "before")
|
||||
_verify_source(source, lock, require_clean=True)
|
||||
|
||||
|
||||
def _verify_patched_source(source: pathlib.Path, lock: dict[str, Any]) -> None:
|
||||
if _git(source, "diff", "--quiet"):
|
||||
raise DependencyError("local unstaged edits detected after applying patch stack")
|
||||
changed_paths = _git(source, "diff", "--cached", "--name-only").splitlines()
|
||||
if changed_paths != lock["patched_paths"]:
|
||||
raise DependencyError(f"patched paths drifted: expected {lock['patched_paths']}, got {changed_paths}")
|
||||
@@ -297,6 +492,13 @@ def smoke(binary: pathlib.Path) -> None:
|
||||
print(output)
|
||||
|
||||
|
||||
def verify(workspace: pathlib.Path) -> None:
|
||||
"""Apply, verify, reverse, and leave the exact cached pin pristine."""
|
||||
source = fetch(workspace)
|
||||
apply(source)
|
||||
reverse(source)
|
||||
|
||||
|
||||
def reproduce(workspace: pathlib.Path) -> None:
|
||||
source = fetch(workspace)
|
||||
build_dir = workspace.resolve() / "build"
|
||||
@@ -330,6 +532,10 @@ def main() -> int:
|
||||
fetch_parser.add_argument("--workspace", type=pathlib.Path, default=ROOT / "build/llama.cpp")
|
||||
apply_parser = subcommands.add_parser("apply")
|
||||
apply_parser.add_argument("--source-dir", type=pathlib.Path, required=True)
|
||||
reverse_parser = subcommands.add_parser("reverse")
|
||||
reverse_parser.add_argument("--source-dir", type=pathlib.Path, required=True)
|
||||
verify_parser = subcommands.add_parser("verify")
|
||||
verify_parser.add_argument("--workspace", type=pathlib.Path, default=ROOT / "build/llama.cpp")
|
||||
build_parser = subcommands.add_parser("build")
|
||||
build_parser.add_argument("--source-dir", type=pathlib.Path, required=True)
|
||||
build_parser.add_argument("--build-dir", type=pathlib.Path, required=True)
|
||||
@@ -345,6 +551,10 @@ def main() -> int:
|
||||
fetch(args.workspace)
|
||||
elif args.command == "apply":
|
||||
apply(args.source_dir)
|
||||
elif args.command == "reverse":
|
||||
reverse(args.source_dir)
|
||||
elif args.command == "verify":
|
||||
verify(args.workspace)
|
||||
elif args.command == "build":
|
||||
build(args.source_dir, args.build_dir)
|
||||
elif args.command == "smoke":
|
||||
|
||||
Reference in New Issue
Block a user