← Files ProoflineARCHIVED FILE
skills/manage-evidence-decisions/scripts/proofline_package.py
22.2 KB · Oct 3, 2026 · 06:30 UTC
#!/usr/bin/env python3
"""Dependency-free verifier for Proofline Decision Evidence Packages."""
from __future__ import annotations
import argparse
import hashlib
import io
import json
import math
import stat
import sys
import uuid
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Any
SCHEMA = "proofline-decision-evidence-package-v1"
REVIEW_RECEIPT_SCHEMA = "proofline-decision-review-receipt-v1"
MAX_PACKAGE_BYTES = 32 * 1024 * 1024
MAX_ARCHIVE_BYTES = MAX_PACKAGE_BYTES + 64 * 1024
MAX_REVIEW_RECEIPT_BYTES = 1024 * 1024
HASH_CHARS = frozenset("0123456789abcdef")
REVIEW_RECEIPT_KEYS = {
"schema",
"review_id",
"workspace_id",
"decision_id",
"evidence_id",
"finding_fingerprint",
"cited_source_version_id",
"cited_content_sha256",
"current_source_version_id",
"current_content_sha256",
"anchor_state",
"policy_sha256",
"state",
"resolution",
"opened_at",
"updated_at",
"closed_at",
"dep_root_hash",
"receipt_hash",
}
class PackageError(RuntimeError):
def __init__(self, code: str) -> None:
self.code = code
super().__init__(code)
def fail(code: str) -> None:
raise PackageError(code)
def canonical_bytes(value: Any) -> bytes:
return json.dumps(
value, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def envelope(domain: str, value: Any) -> str:
prefix = f"proofline/{domain}/v1\0".encode()
return hashlib.sha256(prefix + canonical_bytes(value)).hexdigest()
def node_hash(kind: str, node: dict[str, Any]) -> str:
return envelope(
f"node/{kind}", {key: value for key, value in node.items() if key != "node_hash"}
)
def require_object(value: Any, keys: set[str], code: str) -> dict[str, Any]:
if not isinstance(value, dict) or set(value) != keys:
fail(code)
return value
def require_text(value: Any, code: str, *, nullable: bool = False) -> None:
if value is None and nullable:
return
if not isinstance(value, str) or not value:
fail(code)
def require_hash(value: Any, code: str) -> None:
if not isinstance(value, str) or len(value) != 64 or any(c not in HASH_CHARS for c in value):
fail(code)
def require_datetime(value: Any, code: str, *, nullable: bool = False) -> None:
if value is None and nullable:
return
if not isinstance(value, str):
fail(code)
try:
parsed = datetime.fromisoformat(value)
except ValueError:
fail(code)
if parsed.tzinfo is None:
fail(code)
def verify_review(document: Any) -> dict[str, Any]:
receipt = require_object(document, REVIEW_RECEIPT_KEYS, "receipt_shape_invalid")
if receipt["schema"] != REVIEW_RECEIPT_SCHEMA:
fail("schema_unsupported")
for key, code in (
("review_id", "review_id_invalid"),
("workspace_id", "workspace_id_invalid"),
("decision_id", "decision_id_invalid"),
("evidence_id", "evidence_id_invalid"),
("cited_source_version_id", "source_version_id_invalid"),
("current_source_version_id", "source_version_id_invalid"),
):
require_text(receipt[key], code)
try:
parsed = uuid.UUID(receipt[key])
except ValueError:
fail(code)
if str(parsed) != receipt[key]:
fail(code)
for key, code in (
("finding_fingerprint", "fingerprint_invalid"),
("cited_content_sha256", "content_hash_invalid"),
("current_content_sha256", "content_hash_invalid"),
("policy_sha256", "policy_hash_invalid"),
("dep_root_hash", "dep_root_hash_invalid"),
("receipt_hash", "receipt_hash_invalid"),
):
require_hash(receipt[key], code)
if receipt["anchor_state"] not in {"moved", "ambiguous", "changed", "deleted"}:
fail("anchor_state_invalid")
state = receipt["state"]
if state not in {"open", "acknowledged", "resolved", "waived", "superseded"}:
fail("review_state_invalid")
terminal = state in {"resolved", "waived", "superseded"}
resolution = receipt["resolution"]
if terminal:
if not isinstance(resolution, str) or not resolution.strip():
fail("resolution_invalid")
elif resolution is not None:
fail("resolution_invalid")
for key in ("opened_at", "updated_at"):
require_datetime(receipt[key], "timestamp_invalid")
require_datetime(receipt["closed_at"], "timestamp_invalid", nullable=True)
opened = datetime.fromisoformat(receipt["opened_at"])
updated = datetime.fromisoformat(receipt["updated_at"])
closed = (
datetime.fromisoformat(receipt["closed_at"]) if receipt["closed_at"] is not None else None
)
if opened > updated or (terminal and (closed is None or opened > closed or closed > updated)):
fail("timestamp_invalid")
if not terminal and closed is not None:
fail("timestamp_invalid")
body = {key: value for key, value in receipt.items() if key != "receipt_hash"}
expected = hashlib.sha256(b"proofline/review-receipt/v1\0" + canonical_bytes(body)).hexdigest()
if receipt["receipt_hash"] != expected:
fail("receipt_hash_mismatch")
return {
"valid": True,
"schema": REVIEW_RECEIPT_SCHEMA,
"review_id": receipt["review_id"],
"receipt_hash": receipt["receipt_hash"],
"dep_root_hash": receipt["dep_root_hash"],
}
def load_review(path: Path) -> dict[str, Any]:
try:
data = path.read_bytes()
if len(data) > MAX_REVIEW_RECEIPT_BYTES:
fail("receipt_too_large")
document = json.loads(data, object_pairs_hook=unique_object)
except PackageError:
raise
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
fail("receipt_unreadable")
verify_review(document)
return document
def line_number(content: str, offset: int) -> int:
return content.count("\n", 0, offset) + 1
def verify(document: Any) -> dict[str, Any]:
root = require_object(document, {"manifest", "payload"}, "package_shape_invalid")
manifest = require_object(
root["manifest"],
{"schema", "created_at", "app_version", "root_hash"},
"manifest_shape_invalid",
)
if manifest["schema"] != SCHEMA:
fail("schema_unsupported")
require_datetime(manifest["created_at"], "manifest_created_at_invalid")
require_text(manifest["app_version"], "manifest_app_version_invalid")
require_hash(manifest["root_hash"], "root_hash_invalid")
payload = require_object(
root["payload"],
{"source_version", "chunks", "citations", "transformation", "artifact", "review"},
"payload_shape_invalid",
)
source = require_object(
payload["source_version"],
{
"workspace_id",
"source_id",
"source_identity_sha256",
"source_version_id",
"version_number",
"content",
"content_sha256",
"content_length",
"parser_version",
"node_hash",
},
"source_node_invalid",
)
for key in ("workspace_id", "source_id", "source_version_id", "parser_version"):
require_text(source[key], "source_identity_invalid")
require_hash(source["source_identity_sha256"], "source_identity_invalid")
require_hash(source["content_sha256"], "source_content_hash_invalid")
require_hash(source["node_hash"], "source_node_hash_invalid")
if (
source["source_identity_sha256"]
!= hashlib.sha256(f"source:{source['source_id']}".encode()).hexdigest()
):
fail("source_identity_invalid")
content = source["content"]
if (
not isinstance(content, str)
or not isinstance(source["version_number"], int)
or isinstance(source["version_number"], bool)
or source["version_number"] < 1
or not isinstance(source["content_length"], int)
or isinstance(source["content_length"], bool)
or source["content_length"] != len(content)
):
fail("source_content_invalid")
if hashlib.sha256(content.encode()).hexdigest() != source["content_sha256"]:
fail("source_content_hash_mismatch")
if node_hash("source-version", source) != source["node_hash"]:
fail("source_node_hash_mismatch")
chunks = payload["chunks"]
if not isinstance(chunks, list) or not chunks:
fail("chunks_invalid")
chunk_hashes: list[str] = []
chunk_ids: set[str] = set()
ordinals: set[int] = set()
chunk_keys = {
"id",
"source_node_hash",
"source_id",
"source_version_id",
"ordinal",
"start_offset",
"end_offset",
"start_line",
"end_line",
"content",
"content_sha256",
"node_hash",
}
for chunk in chunks:
chunk = require_object(chunk, chunk_keys, "chunk_node_invalid")
start, end, ordinal = chunk["start_offset"], chunk["end_offset"], chunk["ordinal"]
require_text(chunk["id"], "chunk_identity_invalid")
require_hash(chunk["content_sha256"], "chunk_content_hash_invalid")
require_hash(chunk["node_hash"], "chunk_node_hash_invalid")
if (
chunk["id"] in chunk_ids
or chunk["node_hash"] in chunk_hashes
or ordinal in ordinals
or chunk["source_node_hash"] != source["node_hash"]
or chunk["source_id"] != source["source_id"]
or chunk["source_version_id"] != source["source_version_id"]
or not isinstance(ordinal, int)
or isinstance(ordinal, bool)
or ordinal < 0
or not isinstance(start, int)
or isinstance(start, bool)
or not isinstance(end, int)
or isinstance(end, bool)
or not 0 <= start < end <= len(content)
):
fail("chunk_reference_invalid")
exact = content[start:end]
if (
chunk["content"] != exact
or chunk["content_sha256"] != hashlib.sha256(exact.encode()).hexdigest()
or chunk["start_line"] != line_number(content, start)
or chunk["end_line"] != line_number(content, end - 1)
):
fail("chunk_span_invalid")
if node_hash("chunk", chunk) != chunk["node_hash"]:
fail("chunk_node_hash_mismatch")
chunk_ids.add(chunk["id"])
ordinals.add(ordinal)
chunk_hashes.append(chunk["node_hash"])
citations = payload["citations"]
if not isinstance(citations, list) or not citations:
fail("citations_invalid")
citation_hashes: list[str] = []
citation_ids: set[str] = set()
citation_keys = {
"id",
"source_node_hash",
"chunk_node_hashes",
"source_id",
"source_version_id",
"start_offset",
"end_offset",
"start_line",
"end_line",
"quote",
"quote_sha256",
"node_hash",
}
for citation in citations:
citation = require_object(citation, citation_keys, "citation_node_invalid")
start, end = citation["start_offset"], citation["end_offset"]
require_text(citation["id"], "citation_identity_invalid")
require_hash(citation["quote_sha256"], "citation_quote_hash_invalid")
require_hash(citation["node_hash"], "citation_node_hash_invalid")
expected_chunks = [
c["node_hash"] for c in chunks if c["start_offset"] < end and c["end_offset"] > start
]
if (
citation["id"] in citation_ids
or citation["node_hash"] in citation_hashes
or citation["source_node_hash"] != source["node_hash"]
or citation["chunk_node_hashes"] != expected_chunks
or not expected_chunks
or citation["source_id"] != source["source_id"]
or citation["source_version_id"] != source["source_version_id"]
or not isinstance(start, int)
or isinstance(start, bool)
or not isinstance(end, int)
or isinstance(end, bool)
or not 0 <= start < end <= len(content)
):
fail("citation_reference_invalid")
exact = content[start:end]
if (
citation["quote"] != exact
or citation["quote_sha256"] != hashlib.sha256(exact.encode()).hexdigest()
or citation["start_line"] != line_number(content, start)
or citation["end_line"] != line_number(content, end - 1)
):
fail("citation_span_invalid")
if node_hash("citation", citation) != citation["node_hash"]:
fail("citation_node_hash_mismatch")
citation_ids.add(citation["id"])
citation_hashes.append(citation["node_hash"])
transformation = require_object(
payload["transformation"],
{"method", "parser_version", "model_runs", "input_node_hashes", "node_hash"},
"transformation_node_invalid",
)
if transformation["method"] not in {"deterministic", "model"}:
fail("transformation_method_invalid")
if transformation["parser_version"] != source["parser_version"]:
fail("transformation_parser_invalid")
expected_inputs = [source["node_hash"], *chunk_hashes, *citation_hashes]
if transformation["input_node_hashes"] != expected_inputs:
fail("transformation_inputs_invalid")
if not isinstance(transformation["model_runs"], list) or (
(transformation["method"] == "model") != bool(transformation["model_runs"])
):
fail("model_run_lineage_invalid")
require_hash(transformation["node_hash"], "transformation_node_hash_invalid")
if node_hash("transformation", transformation) != transformation["node_hash"]:
fail("transformation_node_hash_mismatch")
artifact = require_object(
payload["artifact"],
{
"id",
"kind",
"title",
"statement",
"rationale",
"confidence",
"valid_from",
"valid_to",
"transformation_node_hash",
"citation_node_hashes",
"node_hash",
},
"artifact_node_invalid",
)
if artifact["kind"] not in {"decision", "assumption", "constraint", "alternative"}:
fail("artifact_content_invalid")
for key in ("id", "title", "statement"):
require_text(artifact[key], "artifact_content_invalid")
require_text(artifact["rationale"], "artifact_content_invalid", nullable=True)
confidence = artifact["confidence"]
if (
not isinstance(confidence, (int, float))
or isinstance(confidence, bool)
or not math.isfinite(confidence)
or not 0 <= confidence <= 1
):
fail("artifact_content_invalid")
require_datetime(artifact["valid_from"], "artifact_datetime_invalid", nullable=True)
require_datetime(artifact["valid_to"], "artifact_datetime_invalid", nullable=True)
require_hash(artifact["node_hash"], "artifact_node_hash_invalid")
if (
artifact["transformation_node_hash"] != transformation["node_hash"]
or artifact["citation_node_hashes"] != citation_hashes
):
fail("artifact_parents_invalid")
if node_hash("decision-artifact", artifact) != artifact["node_hash"]:
fail("artifact_node_hash_mismatch")
review = require_object(
payload["review"],
{"artifact_node_hash", "status", "created_at", "updated_at", "node_hash"},
"review_node_invalid",
)
if review["status"] not in {"candidate", "active", "accepted", "rejected", "obsolete"}:
fail("review_status_invalid")
require_datetime(review["created_at"], "review_datetime_invalid")
require_datetime(review["updated_at"], "review_datetime_invalid")
require_hash(review["node_hash"], "review_node_hash_invalid")
if review["artifact_node_hash"] != artifact["node_hash"]:
fail("review_parent_invalid")
if node_hash("review-state", review) != review["node_hash"]:
fail("review_node_hash_mismatch")
root_hash = envelope(
"package-root",
{"artifact_node_hash": artifact["node_hash"], "review_node_hash": review["node_hash"]},
)
if root_hash != manifest["root_hash"]:
fail("root_hash_mismatch")
return {
"valid": True,
"schema": SCHEMA,
"root_hash": root_hash,
"artifact_id": artifact["id"],
"citation_count": len(citations),
}
def unique_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
fail("duplicate_json_key")
result[key] = value
return result
def load(path: Path) -> dict[str, Any]:
try:
data = path.read_bytes()
if len(data) > MAX_ARCHIVE_BYTES:
fail("package_too_large")
if data.startswith(b"PK"):
with zipfile.ZipFile(io.BytesIO(data), "r") as archive:
entries = archive.infolist()
if len(entries) != 1:
fail("archive_entry_count_invalid")
entry = entries[0]
if (
entry.filename != "evidence.json"
or entry.is_dir()
or "\\" in entry.filename
or stat.S_ISLNK(entry.external_attr >> 16)
):
fail("archive_entry_invalid")
if entry.flag_bits & 1:
fail("archive_encrypted")
if entry.compress_type != zipfile.ZIP_STORED:
fail("archive_compression_unsupported")
if entry.file_size > MAX_PACKAGE_BYTES or entry.file_size != entry.compress_size:
fail("archive_entry_too_large")
document = json.loads(archive.read(entry), object_pairs_hook=unique_object)
else:
if len(data) > MAX_PACKAGE_BYTES:
fail("package_too_large")
document = json.loads(data, object_pairs_hook=unique_object)
except PackageError:
raise
except (OSError, UnicodeDecodeError, json.JSONDecodeError, zipfile.BadZipFile):
fail("package_unreadable")
verify(document)
return document
def explanation(document: dict[str, Any]) -> dict[str, Any]:
report = verify(document)
payload = document["payload"]
return {
**report,
"artifact": {
key: payload["artifact"][key]
for key in (
"id",
"kind",
"title",
"statement",
"rationale",
"confidence",
"valid_from",
"valid_to",
)
},
"review": {key: payload["review"][key] for key in ("status", "created_at", "updated_at")},
"source": {
key: payload["source_version"][key]
for key in (
"source_id",
"source_version_id",
"version_number",
"content_sha256",
"node_hash",
)
},
"citations": [
{
key: item[key]
for key in (
"id",
"start_offset",
"end_offset",
"start_line",
"end_line",
"quote_sha256",
"node_hash",
)
}
for item in payload["citations"]
],
}
def changed_fields(before: dict[str, Any], after: dict[str, Any], excluded: set[str]) -> list[str]:
return sorted(
key
for key in set(before) | set(after)
if key not in excluded and before.get(key) != after.get(key)
)
def difference(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]:
left, right = verify(before), verify(after)
a, b = before["payload"], after["payload"]
left_chunks = {x["node_hash"] for x in a["chunks"]}
right_chunks = {x["node_hash"] for x in b["chunks"]}
left_cites = {x["node_hash"] for x in a["citations"]}
right_cites = {x["node_hash"] for x in b["citations"]}
return {
"schema": "proofline-decision-evidence-package-diff-v1",
"same_root": left["root_hash"] == right["root_hash"],
"before_root_hash": left["root_hash"],
"after_root_hash": right["root_hash"],
"same_artifact_id": left["artifact_id"] == right["artifact_id"],
"before_artifact_id": left["artifact_id"],
"after_artifact_id": right["artifact_id"],
"source_changed_fields": changed_fields(
a["source_version"], b["source_version"], {"content", "node_hash"}
),
"chunks_added": sorted(right_chunks - left_chunks),
"chunks_removed": sorted(left_chunks - right_chunks),
"citations_added": sorted(right_cites - left_cites),
"citations_removed": sorted(left_cites - right_cites),
"transformation_changed": a["transformation"]["node_hash"]
!= b["transformation"]["node_hash"],
"artifact_changed_fields": changed_fields(
a["artifact"],
b["artifact"],
{"node_hash", "transformation_node_hash", "citation_node_hashes"},
),
"review_changed_fields": changed_fields(
a["review"],
b["review"],
{"node_hash", "artifact_node_hash", "created_at", "updated_at"},
),
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest="command", required=True)
for name in ("verify", "explain", "verify-review"):
item = commands.add_parser(name)
item.add_argument("path", type=Path)
item = commands.add_parser("diff")
item.add_argument("before", type=Path)
item.add_argument("after", type=Path)
args = parser.parse_args()
try:
if args.command == "verify":
result = verify(load(args.path))
elif args.command == "explain":
result = explanation(load(args.path))
elif args.command == "verify-review":
result = verify_review(load_review(args.path))
else:
result = difference(load(args.before), load(args.after))
except PackageError as exc:
print(json.dumps({"valid": False, "error": exc.code}), file=sys.stderr)
return 2
print(json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: 6969274e69ef803f431bdd67f3c9d372a50a93f97c1a050b8c1c3f2464e5a2b6