mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
590 lines
23 KiB
Python
590 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify the active proof pointer and every byte frozen by its descriptor."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from datetime import datetime
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
import sys
|
|
from typing import Any
|
|
|
|
EPOCH_ONE_REQUIRED_COMPONENT_ROLES = {
|
|
"validator-contract",
|
|
"validator-implementation",
|
|
"validator-cli",
|
|
"verdict-schema",
|
|
"rpi-report-schema",
|
|
"subject-manifest-schema",
|
|
"scope-index-schema",
|
|
"effect-receipt-schema",
|
|
"check-receipt-schema",
|
|
}
|
|
|
|
|
|
def load_bootstrap_module(repository: Path):
|
|
path = repository / "scripts" / "bootstrap-proof-transition.py"
|
|
specification = importlib.util.spec_from_file_location(
|
|
"agentops_bootstrap_proof_transition", path
|
|
)
|
|
if specification is None or specification.loader is None:
|
|
raise RuntimeError(f"cannot load bootstrap recorder: {path}")
|
|
module = importlib.util.module_from_spec(specification)
|
|
specification.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def load_kernel_module(path: Path):
|
|
specification = importlib.util.spec_from_file_location(
|
|
"agentops_kernel_v3_proof_check", path
|
|
)
|
|
if specification is None or specification.loader is None:
|
|
raise RuntimeError(f"cannot load epoch-1 proof reader: {path}")
|
|
module = importlib.util.module_from_spec(specification)
|
|
specification.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def repository_ref(module: Any, repository: Path, raw: Any, label: str) -> Path:
|
|
if (
|
|
not isinstance(raw, str)
|
|
or not raw
|
|
or raw == "."
|
|
or "\\" in raw
|
|
or raw.startswith("/")
|
|
or raw.startswith("//")
|
|
or raw.endswith("/")
|
|
or (len(raw) >= 2 and raw[0].isalpha() and raw[1] == ":")
|
|
or any(part in {"", ".", ".."} for part in raw.split("/"))
|
|
or any(ord(character) < 32 or ord(character) == 127 for character in raw)
|
|
):
|
|
raise module.TransitionError(f"{label} is not a canonical repository ref")
|
|
target = repository / raw
|
|
cursor = repository
|
|
for part in raw.split("/"):
|
|
cursor = cursor / part
|
|
if cursor.is_symlink():
|
|
raise module.TransitionError(f"{label} traverses a symlink")
|
|
try:
|
|
target.resolve(strict=False).relative_to(repository.resolve())
|
|
except ValueError as error:
|
|
raise module.TransitionError(f"{label} escapes the repository") from error
|
|
return target
|
|
|
|
|
|
def exact_artifact(module: Any, path: Path, label: str) -> dict[str, Any]:
|
|
try:
|
|
information = path.lstat()
|
|
executable = bool(
|
|
information.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
)
|
|
if path.is_symlink():
|
|
kind = "symlink"
|
|
payload = os.readlink(path).encode("utf-8")
|
|
elif path.is_file():
|
|
kind = "file"
|
|
payload = path.read_bytes()
|
|
else:
|
|
raise module.TransitionError(f"{label} is not a file or symlink")
|
|
except OSError as error:
|
|
raise module.TransitionError(f"{label} is unavailable: {error}") from error
|
|
return {
|
|
"kind": kind,
|
|
"digest": hashlib.sha256(payload).hexdigest(),
|
|
"executable": executable,
|
|
"mode": mode(path),
|
|
}
|
|
|
|
|
|
def check_epoch_one(
|
|
repository: Path,
|
|
active_path: Path,
|
|
active: dict[str, Any],
|
|
module: Any,
|
|
) -> dict[str, Any]:
|
|
if set(active) != module.ACTIVE_KEYS:
|
|
raise module.TransitionError("active proof pointer has invalid fields")
|
|
if active["schema_version"] != "proof-contract-active.v1" or active["epoch"] != 1:
|
|
raise module.TransitionError("epoch-1 active pointer identity is invalid")
|
|
for field in ("contract_digest", "activation_transition_digest"):
|
|
if not module.valid_digest(active[field]):
|
|
raise module.TransitionError(f"active {field} must be SHA-256")
|
|
descriptor_path = repository_ref(
|
|
module, repository, active["contract_ref"], "active contract_ref"
|
|
)
|
|
transition_path = repository_ref(
|
|
module,
|
|
repository,
|
|
active["activation_transition_ref"],
|
|
"active activation_transition_ref",
|
|
)
|
|
descriptor_bytes = descriptor_path.read_bytes()
|
|
if hashlib.sha256(descriptor_bytes).hexdigest() != active["contract_digest"]:
|
|
raise module.TransitionError("active descriptor bytes do not match pointer")
|
|
transition_bytes = transition_path.read_bytes()
|
|
if hashlib.sha256(transition_bytes).hexdigest() != active[
|
|
"activation_transition_digest"
|
|
]:
|
|
raise module.TransitionError("active transition bytes do not match pointer")
|
|
if transition_path.name != f"{active['activation_transition_digest']}.json":
|
|
raise module.TransitionError("active transition filename is not content-addressed")
|
|
|
|
descriptor = module.load_object(descriptor_path)
|
|
module.validate_descriptor(descriptor, expected_epoch=1)
|
|
if type(descriptor.get("epoch")) is not int:
|
|
raise module.TransitionError("epoch-1 descriptor epoch must be an integer")
|
|
roles: dict[str, dict[str, Any]] = {}
|
|
subject_bindings: list[tuple[str, dict[str, Any], dict[str, Any]]] = []
|
|
for index, component in enumerate(descriptor["components"]):
|
|
role, reference = module.validate_component(component, index)
|
|
if role in roles:
|
|
raise module.TransitionError("epoch-1 descriptor has duplicate roles")
|
|
path = repository_ref(
|
|
module, repository, reference, f"component ref for {role}"
|
|
)
|
|
observed = exact_artifact(module, path, f"component {role}")
|
|
if (
|
|
observed["digest"] != component["digest"]
|
|
or observed["mode"] != component["mode"]
|
|
):
|
|
raise module.TransitionError(
|
|
f"frozen component bytes or mode changed: {role}"
|
|
)
|
|
roles[role] = component
|
|
subject_bindings.append((role, component, observed))
|
|
missing = EPOCH_ONE_REQUIRED_COMPONENT_ROLES - set(roles)
|
|
if missing:
|
|
raise module.TransitionError(
|
|
"epoch-1 descriptor lacks required roles: " + ", ".join(sorted(missing))
|
|
)
|
|
|
|
corpus = descriptor["qualification_corpus"]
|
|
corpus_path = repository_ref(
|
|
module, repository, corpus["ref"], "qualification corpus ref"
|
|
)
|
|
if module.tree_digest(corpus_path) != corpus["digest"]:
|
|
raise module.TransitionError("frozen qualification corpus digest changed")
|
|
recorder = descriptor["transition_recorder"]
|
|
recorder_path = repository_ref(
|
|
module, repository, recorder["ref"], "transition recorder ref"
|
|
)
|
|
recorder_observed = exact_artifact(
|
|
module, recorder_path, "transition recorder"
|
|
)
|
|
if (
|
|
recorder_observed["digest"] != recorder["digest"]
|
|
or recorder_observed["mode"] != recorder["mode"]
|
|
):
|
|
raise module.TransitionError("frozen transition recorder bytes or mode changed")
|
|
subject_bindings.append(("transition-recorder", recorder, recorder_observed))
|
|
|
|
transition = module.load_object(transition_path)
|
|
if transition_bytes != module.canonical_bytes(transition) + b"\n":
|
|
raise module.TransitionError("active transition bytes are not canonical")
|
|
required_transition = {
|
|
"schema_version",
|
|
"prior",
|
|
"candidate",
|
|
"qualification_verdict",
|
|
"validator_identity",
|
|
"activated_at",
|
|
}
|
|
if (
|
|
set(transition) != required_transition
|
|
or transition["schema_version"] != "proof-contract-transition.v1"
|
|
):
|
|
raise module.TransitionError("active transition has invalid fields")
|
|
prior = transition["prior"]
|
|
candidate = transition["candidate"]
|
|
verdict_binding = transition["qualification_verdict"]
|
|
if not isinstance(prior, dict) or set(prior) != {
|
|
"epoch",
|
|
"contract_ref",
|
|
"contract_digest",
|
|
"activation_transition_digest",
|
|
}:
|
|
raise module.TransitionError("active transition prior has invalid fields")
|
|
if not isinstance(candidate, dict) or set(candidate) != {
|
|
"epoch",
|
|
"contract_ref",
|
|
"contract_digest",
|
|
"subject_manifest_ref",
|
|
"subject_manifest_digest",
|
|
"qualification_corpus_ref",
|
|
"qualification_corpus_digest",
|
|
}:
|
|
raise module.TransitionError("active transition candidate has invalid fields")
|
|
if not isinstance(verdict_binding, dict) or set(verdict_binding) != {
|
|
"ref",
|
|
"digest",
|
|
}:
|
|
raise module.TransitionError(
|
|
"active transition qualification verdict has invalid fields"
|
|
)
|
|
if (
|
|
type(candidate["epoch"]) is not int
|
|
or candidate["epoch"] != 1
|
|
or candidate["contract_ref"] != active["contract_ref"]
|
|
or candidate["contract_digest"] != active["contract_digest"]
|
|
or candidate["subject_manifest_digest"]
|
|
!= descriptor["qualification_subject_manifest_digest"]
|
|
or candidate["qualification_corpus_ref"] != corpus["ref"]
|
|
or candidate["qualification_corpus_digest"] != corpus["digest"]
|
|
):
|
|
raise module.TransitionError(
|
|
"active proof transition does not bind the active descriptor"
|
|
)
|
|
if (
|
|
not isinstance(transition["validator_identity"], str)
|
|
or not transition["validator_identity"]
|
|
):
|
|
raise module.TransitionError("active transition validator identity is invalid")
|
|
try:
|
|
activated = datetime.fromisoformat(
|
|
transition["activated_at"].replace("Z", "+00:00")
|
|
)
|
|
except (AttributeError, ValueError) as error:
|
|
raise module.TransitionError(
|
|
"active transition activated_at is invalid"
|
|
) from error
|
|
if activated.tzinfo is None:
|
|
raise module.TransitionError("active transition activated_at lacks timezone")
|
|
|
|
prior_path = repository_ref(
|
|
module, repository, prior["contract_ref"], "prior contract_ref"
|
|
)
|
|
if (
|
|
type(prior["epoch"]) is not int
|
|
or prior["epoch"] != 0
|
|
or prior["activation_transition_digest"] is not None
|
|
or not module.valid_digest(prior["contract_digest"])
|
|
or hashlib.sha256(prior_path.read_bytes()).hexdigest()
|
|
!= prior["contract_digest"]
|
|
):
|
|
raise module.TransitionError("active transition prior binding is invalid")
|
|
prior_descriptor = module.load_object(prior_path)
|
|
if (
|
|
set(prior_descriptor) != module.DESCRIPTOR_KEYS
|
|
or prior_descriptor.get("schema_version") != "proof-contract.v1"
|
|
or type(prior_descriptor.get("epoch")) is not int
|
|
or prior_descriptor.get("epoch") != 0
|
|
or prior_descriptor.get("qualification_subject_manifest_digest") is not None
|
|
):
|
|
raise module.TransitionError(
|
|
"active transition prior descriptor is invalid"
|
|
)
|
|
prior_roles: set[str] = set()
|
|
for index, component in enumerate(prior_descriptor.get("components", [])):
|
|
role, reference = module.validate_component(component, index)
|
|
repository_ref(
|
|
module, repository, reference, f"prior component ref for {role}"
|
|
)
|
|
if role in prior_roles:
|
|
raise module.TransitionError(
|
|
"active transition prior descriptor duplicates roles"
|
|
)
|
|
prior_roles.add(role)
|
|
if module.REQUIRED_COMPONENT_ROLES - prior_roles:
|
|
raise module.TransitionError(
|
|
"active transition prior descriptor lacks required roles"
|
|
)
|
|
prior_corpus = prior_descriptor.get("qualification_corpus")
|
|
prior_recorder = prior_descriptor.get("transition_recorder")
|
|
if (
|
|
not isinstance(prior_corpus, dict)
|
|
or set(prior_corpus) != {"algorithm", "ref", "digest"}
|
|
or prior_corpus.get("algorithm") != "sha256-tree-v1"
|
|
or not module.valid_digest(prior_corpus.get("digest"))
|
|
or not isinstance(prior_recorder, dict)
|
|
or set(prior_recorder) != {"ref", "digest", "mode"}
|
|
):
|
|
raise module.TransitionError(
|
|
"active transition prior proof surfaces are invalid"
|
|
)
|
|
repository_ref(
|
|
module, repository, prior_corpus["ref"], "prior qualification corpus ref"
|
|
)
|
|
repository_ref(
|
|
module, repository, prior_recorder["ref"], "prior transition recorder ref"
|
|
)
|
|
|
|
subject_path = repository_ref(
|
|
module,
|
|
repository,
|
|
candidate["subject_manifest_ref"],
|
|
"qualification subject ref",
|
|
)
|
|
subject = module.load_object(subject_path)
|
|
subject_digest = module.manifest_digest(subject)
|
|
if subject_digest != candidate["subject_manifest_digest"]:
|
|
raise module.TransitionError("qualification subject binding changed")
|
|
entries = subject.get("entries")
|
|
if not isinstance(entries, list):
|
|
raise module.TransitionError("qualification subject entries are invalid")
|
|
by_path = {
|
|
entry.get("path"): entry
|
|
for entry in entries
|
|
if isinstance(entry, dict) and isinstance(entry.get("path"), str)
|
|
}
|
|
if len(by_path) != len(entries):
|
|
raise module.TransitionError(
|
|
"qualification subject entries are invalid or duplicated"
|
|
)
|
|
for role, binding, observed in subject_bindings:
|
|
entry = by_path.get(binding["ref"])
|
|
if (
|
|
entry is None
|
|
or entry.get("kind") != observed["kind"]
|
|
or entry.get("digest") != observed["digest"]
|
|
or entry.get("executable") is not observed["executable"]
|
|
):
|
|
raise module.TransitionError(
|
|
f"qualification subject does not bind component: {role}"
|
|
)
|
|
|
|
verdict_path = repository_ref(
|
|
module, repository, verdict_binding["ref"], "qualification verdict ref"
|
|
)
|
|
verdict = module.load_object(verdict_path)
|
|
verdict_digest = module.verdict_digest(verdict)
|
|
if (
|
|
verdict_digest != verdict_binding["digest"]
|
|
or verdict_path.name != f"{verdict_digest}.json"
|
|
or verdict_path.read_bytes() != module.canonical_bytes(verdict) + b"\n"
|
|
or verdict.get("subject_manifest_digest") != subject_digest
|
|
):
|
|
raise module.TransitionError("qualification verdict binding is invalid")
|
|
|
|
validator_binding = roles["validator-implementation"]
|
|
validator_path = repository_ref(
|
|
module,
|
|
repository,
|
|
validator_binding["ref"],
|
|
"validator implementation ref",
|
|
)
|
|
kernel = load_kernel_module(validator_path)
|
|
identity = kernel.load_active_proof(repository)
|
|
expected_identity = {
|
|
"epoch": 1,
|
|
"contract_ref": active["contract_ref"],
|
|
"contract_digest": active["contract_digest"],
|
|
"activation_transition_digest": active["activation_transition_digest"],
|
|
}
|
|
if identity != expected_identity:
|
|
raise module.TransitionError(
|
|
"descriptor-bound validator disagrees with standalone proof check"
|
|
)
|
|
return {
|
|
"result": "PASS",
|
|
"epoch": 1,
|
|
"contract_digest": active["contract_digest"],
|
|
"activation_transition_digest": active["activation_transition_digest"],
|
|
"component_count": len(roles),
|
|
"qualification_corpus_digest": corpus["digest"],
|
|
"bootstrap_replacement_count": len(
|
|
list(
|
|
(
|
|
repository
|
|
/ "docs/contracts/proof-contracts/bootstrap-root-replacements"
|
|
).glob("*.json")
|
|
)
|
|
),
|
|
}
|
|
|
|
|
|
def mode(path: Path) -> str:
|
|
return f"{stat.S_IMODE(path.lstat().st_mode):04o}"
|
|
|
|
|
|
def check(repository: Path, active_path: Path) -> dict[str, Any]:
|
|
module = load_bootstrap_module(repository)
|
|
active = module.load_object(active_path)
|
|
epoch = active.get("epoch")
|
|
if type(epoch) is not int or epoch < 0:
|
|
raise module.TransitionError(
|
|
"active proof pointer epoch must be a nonnegative integer"
|
|
)
|
|
if epoch == 1:
|
|
expected_active = (
|
|
repository / "docs/contracts/proof-contracts/active.json"
|
|
).resolve()
|
|
if active_path.resolve() != expected_active:
|
|
raise module.TransitionError(
|
|
"epoch-1 checker requires the canonical active pointer"
|
|
)
|
|
return check_epoch_one(repository, active_path, active, module)
|
|
if epoch != 0:
|
|
raise module.TransitionError(
|
|
f"unsupported active proof epoch: {epoch}; checker supports 0 and 1"
|
|
)
|
|
module.validate_active(active)
|
|
descriptor_path = repository / active["contract_ref"]
|
|
descriptor = module.load_object(descriptor_path)
|
|
if descriptor.get("epoch") != active["epoch"]:
|
|
raise module.TransitionError("active pointer and descriptor epochs differ")
|
|
expected_descriptor_digest = hashlib.sha256(descriptor_path.read_bytes()).hexdigest()
|
|
if active["contract_digest"] != expected_descriptor_digest:
|
|
raise module.TransitionError("active descriptor bytes do not match pointer")
|
|
|
|
component_roles: list[str] = []
|
|
for index, component in enumerate(descriptor.get("components", [])):
|
|
role, _ = module.validate_component(component, index)
|
|
component_roles.append(role)
|
|
component_path = repository / component["ref"]
|
|
if hashlib.sha256(component_path.read_bytes()).hexdigest() != component["digest"]:
|
|
raise module.TransitionError(f"frozen component digest changed: {role}")
|
|
if mode(component_path) != component["mode"]:
|
|
raise module.TransitionError(f"frozen component mode changed: {role}")
|
|
if len(component_roles) != len(set(component_roles)):
|
|
raise module.TransitionError("frozen descriptor has duplicate component roles")
|
|
missing = sorted(module.REQUIRED_COMPONENT_ROLES - set(component_roles))
|
|
if missing:
|
|
raise module.TransitionError(
|
|
"frozen descriptor lacks required roles: " + ", ".join(missing)
|
|
)
|
|
|
|
corpus = descriptor.get("qualification_corpus")
|
|
if not isinstance(corpus, dict):
|
|
raise module.TransitionError("frozen descriptor lacks qualification corpus")
|
|
corpus_path = repository / corpus["ref"]
|
|
if module.tree_digest(corpus_path) != corpus.get("digest"):
|
|
raise module.TransitionError("frozen qualification corpus digest changed")
|
|
|
|
recorder = descriptor.get("transition_recorder")
|
|
if not isinstance(recorder, dict):
|
|
raise module.TransitionError("frozen descriptor lacks transition recorder")
|
|
recorder_path = repository / recorder["ref"]
|
|
if hashlib.sha256(recorder_path.read_bytes()).hexdigest() != recorder.get("digest"):
|
|
raise module.TransitionError("frozen transition recorder digest changed")
|
|
if mode(recorder_path) != recorder.get("mode"):
|
|
raise module.TransitionError("frozen transition recorder mode changed")
|
|
|
|
if active["epoch"] == 0 and descriptor.get(
|
|
"qualification_subject_manifest_digest"
|
|
) is not None:
|
|
raise module.TransitionError(
|
|
"bootstrap descriptor must not claim a qualification subject"
|
|
)
|
|
replacements = sorted(
|
|
(
|
|
repository
|
|
/ "docs"
|
|
/ "contracts"
|
|
/ "proof-contracts"
|
|
/ "bootstrap-root-replacements"
|
|
).glob("*.json")
|
|
)
|
|
if active["contract_ref"].startswith(
|
|
"docs/contracts/proof-contracts/epoch-0b/"
|
|
):
|
|
if len(replacements) != 1:
|
|
raise module.TransitionError(
|
|
"corrected bootstrap root requires exactly one replacement record"
|
|
)
|
|
replacement = module.load_object(replacements[0])
|
|
required = {
|
|
"schema_version",
|
|
"rejected_contract_ref",
|
|
"rejected_contract_digest",
|
|
"rejecting_verdict_ref",
|
|
"rejecting_verdict_digest",
|
|
"replacement_contract_ref",
|
|
"replacement_contract_digest",
|
|
"authority",
|
|
"reason",
|
|
"replaced_at",
|
|
}
|
|
if set(replacement) != required or replacement["schema_version"] != (
|
|
"proof-bootstrap-root-replacement.v1"
|
|
):
|
|
raise module.TransitionError(
|
|
"bootstrap root replacement record has invalid fields"
|
|
)
|
|
rejected = repository / replacement["rejected_contract_ref"]
|
|
if (
|
|
hashlib.sha256(rejected.read_bytes()).hexdigest()
|
|
!= replacement["rejected_contract_digest"]
|
|
):
|
|
raise module.TransitionError("rejected bootstrap descriptor changed")
|
|
verdict_path = repository / replacement["rejecting_verdict_ref"]
|
|
verdict = module.load_object(verdict_path)
|
|
if (
|
|
verdict.get("artifact_digest")
|
|
!= replacement["rejecting_verdict_digest"]
|
|
or verdict.get("verdict") != "FAIL"
|
|
):
|
|
raise module.TransitionError(
|
|
"bootstrap replacement is not bound to the rejecting FAIL"
|
|
)
|
|
unsigned = {
|
|
key: value
|
|
for key, value in verdict.items()
|
|
if key != "artifact_digest"
|
|
}
|
|
if (
|
|
hashlib.sha256(module.canonical_bytes(unsigned)).hexdigest()
|
|
!= verdict["artifact_digest"]
|
|
):
|
|
raise module.TransitionError("rejecting verdict digest changed")
|
|
if (
|
|
verdict_path.name != f"{verdict['artifact_digest']}.json"
|
|
or verdict_path.read_bytes() != module.canonical_bytes(verdict) + b"\n"
|
|
):
|
|
raise module.TransitionError(
|
|
"rejecting verdict bytes or filename changed"
|
|
)
|
|
if (
|
|
replacement["replacement_contract_ref"] != active["contract_ref"]
|
|
or replacement["replacement_contract_digest"]
|
|
!= active["contract_digest"]
|
|
):
|
|
raise module.TransitionError(
|
|
"bootstrap replacement does not bind the active pointer"
|
|
)
|
|
if not replacement["authority"] or not replacement["reason"]:
|
|
raise module.TransitionError(
|
|
"bootstrap replacement lacks authority or reason"
|
|
)
|
|
return {
|
|
"result": "PASS",
|
|
"epoch": active["epoch"],
|
|
"contract_digest": active["contract_digest"],
|
|
"component_count": len(component_roles),
|
|
"qualification_corpus_digest": corpus["digest"],
|
|
"bootstrap_replacement_count": len(replacements),
|
|
}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--repository", default=".")
|
|
parser.add_argument(
|
|
"--active-pointer",
|
|
default="docs/contracts/proof-contracts/active.json",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
repository = Path(args.repository).resolve()
|
|
active_path = Path(args.active_pointer)
|
|
if not active_path.is_absolute():
|
|
active_path = repository / active_path
|
|
try:
|
|
result = check(repository, active_path)
|
|
except (OSError, RuntimeError, ValueError) as error:
|
|
print(f"check-proof-contract: FAIL — {error}", file=sys.stderr)
|
|
return 1
|
|
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|