#!/usr/bin/env python3 """Verify a downloaded Clark operator-tool set without executing any tool.""" from __future__ import annotations import argparse import hashlib import json import sys from pathlib import Path SCHEMA = "kiwerb-clark-operator-tool-manifest-v1" TOP_FIELDS = { "schema_version", "generated_on", "scope", "tool_count", "tools", "operation", "claims", "manifest_sha256", } TOOL_FIELDS = {"id", "filename", "url", "bytes", "sha256", "purpose"} def digest(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def fail(message: str) -> None: raise ValueError(message) def verify(manifest_path: Path, directory: Path) -> dict: if manifest_path.is_symlink() or not manifest_path.is_file(): fail("manifest must be a regular file") if directory.is_symlink() or not directory.is_dir(): fail("tool directory must be a regular directory") raw = manifest_path.read_bytes() if len(raw) > 1_000_000: fail("manifest is too large") try: manifest = json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError) as error: fail(f"manifest is not valid UTF-8 JSON: {type(error).__name__}") if not isinstance(manifest, dict) or set(manifest) != TOP_FIELDS: fail("manifest fields do not match the supported schema") if manifest["schema_version"] != SCHEMA: fail("unsupported manifest schema") claimed = manifest["manifest_sha256"] if not isinstance(claimed, str) or len(claimed) != 64: fail("invalid manifest_sha256") body = {key: value for key, value in manifest.items() if key != "manifest_sha256"} canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() if digest(canonical) != claimed: fail("manifest self-hash mismatch") tools = manifest["tools"] if not isinstance(tools, list) or manifest["tool_count"] != len(tools) or not tools: fail("tool count mismatch") filenames: set[str] = set() tool_ids: set[str] = set() hashes: set[str] = set() checked = [] for entry in tools: if not isinstance(entry, dict) or set(entry) != TOOL_FIELDS: fail("tool entry fields do not match the supported schema") filename = entry["filename"] if not isinstance(filename, str) or Path(filename).name != filename or not filename.endswith(".py"): fail("tool filename must be a top-level Python basename") if filename in filenames or entry["id"] in tool_ids or entry["sha256"] in hashes: fail("tool filename, id and hash must be unique") if not isinstance(entry["bytes"], int) or isinstance(entry["bytes"], bool) or entry["bytes"] <= 0: fail("tool byte count must be a positive integer") if not isinstance(entry["sha256"], str) or len(entry["sha256"]) != 64: fail("tool sha256 must be a 64-character string") if entry["url"] != f"https://kiwerb.de/forschung/{filename}": fail("tool URL does not match the canonical public path") path = directory / filename if path.is_symlink() or not path.is_file(): fail(f"missing regular tool file: {filename}") data = path.read_bytes() if len(data) != entry["bytes"] or digest(data) != entry["sha256"]: fail(f"tool integrity mismatch: {filename}") filenames.add(filename) tool_ids.add(entry["id"]) hashes.add(entry["sha256"]) checked.append({"filename": filename, "bytes": len(data), "sha256": entry["sha256"]}) extra = sorted(path.name for path in directory.glob("*.py") if path.name not in filenames) if extra: fail("unlisted Python files present: " + ", ".join(extra)) return { "schema_version": "kiwerb-clark-operator-tool-verification-v1", "status": "verified_bytes_only_not_executed", "manifest_sha256": claimed, "tools_checked": len(checked), "network_requests": 0, "files_executed": 0, "scientific_validation_claimed": False, "tools": checked, } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("manifest", type=Path) parser.add_argument("directory", type=Path) args = parser.parse_args(argv) try: result = verify(args.manifest, args.directory) except (OSError, ValueError) as error: print(json.dumps({"status": "refused", "error": str(error)}, ensure_ascii=False), file=sys.stderr) return 2 print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())