← Files AgentProofARCHIVED FILE

skills/agentproof/scripts/agentproof.py

8.93 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

#!/usr/bin/env python3
"""Fixed-request launcher for the AgentProof Codex skill."""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import re
import stat
import sys
import traceback
from pathlib import Path
from typing import Any

# Keep the installed plugin tree immutable even if a host omits Python's -B flag.
sys.dont_write_bytecode = True

from agentproof_runtime.capture import (
    CodexCaptureError,
    capture_codex_session,
    resolve_repository_root,
)
from agentproof_runtime.receipt import ReceiptLoadError, load_canonical_receipt
from agentproof_runtime.repository import (
    RepositoryVerifyError,
    verify_repository_state,
)


REQUEST_PATH = Path(".agentproof/request.json")
RECEIPT_PATH = Path(".agentproof/agent-session.json")
MAX_REQUEST_BYTES = 128 * 1024
MAX_PROMPT_BYTES = 64 * 1024
MODEL_RE = re.compile(r"^[A-Za-z0-9._:/+-]{1,128}$")
LIMITS = [
    "cooperative_operator_side_capture",
    "observed_events_may_be_incomplete",
    "integrity_does_not_establish_truth_or_quality",
    "no_identity_compliance_or_economic_authority",
    "no_independent_public_timestamp",
]


class RequestError(RuntimeError):
    pass


def _read_fd_limited(fd: int, limit: int) -> bytes:
    chunks: list[bytes] = []
    remaining = limit + 1
    while remaining:
        chunk = os.read(fd, min(64 * 1024, remaining))
        if not chunk:
            break
        chunks.append(chunk)
        remaining -= len(chunk)
    return b"".join(chunks)


def _require_git_root() -> Path:
    current = Path.cwd().resolve()
    root = resolve_repository_root(current)
    if current != root:
        raise RequestError("run_from_git_repository_root")
    return root


def _ensure_private_control_directory(repo_root: Path) -> Path:
    control = repo_root / ".agentproof"
    try:
        info = control.lstat()
    except FileNotFoundError:
        control.mkdir(mode=0o700)
        info = control.lstat()
    if stat.S_ISLNK(info.st_mode) or not stat.S_ISDIR(info.st_mode):
        raise RequestError("agentproof_control_directory_unsafe")
    if hasattr(os, "getuid") and info.st_uid != os.getuid():
        raise RequestError("agentproof_control_directory_wrong_owner")
    control.chmod(0o700)
    return control


def _require_fixed_request_argument(value: str) -> Path:
    if value != REQUEST_PATH.as_posix():
        raise RequestError("request_path_must_be_.agentproof/request.json")
    return REQUEST_PATH


def _read_and_consume_request(repo_root: Path, relative_path: Path) -> dict[str, Any]:
    path = repo_root / relative_path
    flags = os.O_RDONLY
    if hasattr(os, "O_NOFOLLOW"):
        flags |= os.O_NOFOLLOW
    try:
        fd = os.open(path, flags)
    except OSError as exc:
        raise RequestError("request_not_safely_readable") from exc

    try:
        info = os.fstat(fd)
        if not stat.S_ISREG(info.st_mode):
            raise RequestError("request_must_be_regular_file")
        if info.st_nlink != 1:
            raise RequestError("request_hardlink_forbidden")
        if hasattr(os, "getuid") and info.st_uid != os.getuid():
            raise RequestError("request_wrong_owner")
        if info.st_size > MAX_REQUEST_BYTES:
            raise RequestError("request_too_large")
        raw = _read_fd_limited(fd, MAX_REQUEST_BYTES)
        if len(raw) > MAX_REQUEST_BYTES:
            raise RequestError("request_too_large")
        identity = (info.st_dev, info.st_ino)
    finally:
        os.close(fd)

    try:
        current = path.lstat()
    except FileNotFoundError as exc:
        raise RequestError("request_changed_during_read") from exc
    if (
        stat.S_ISLNK(current.st_mode)
        or not stat.S_ISREG(current.st_mode)
        or current.st_nlink != 1
        or (current.st_dev, current.st_ino) != identity
    ):
        raise RequestError("request_changed_during_read")
    path.unlink()

    try:
        payload = json.loads(raw.decode("utf-8"))
    except (UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise RequestError("request_not_valid_utf8_json") from exc
    if not isinstance(payload, dict):
        raise RequestError("request_must_be_object")
    return payload


def _validate_request(payload: dict[str, Any]) -> dict[str, Any]:
    action = payload.get("action")
    if action == "capture":
        expected = {"action", "model", "prompt", "sandbox"}
        if set(payload) != expected:
            raise RequestError("capture_request_fields_invalid")
        model = payload.get("model")
        prompt = payload.get("prompt")
        sandbox = payload.get("sandbox")
        if not isinstance(model, str) or MODEL_RE.fullmatch(model) is None:
            raise RequestError("model_identifier_invalid")
        if (
            not isinstance(prompt, str)
            or not prompt.strip()
            or len(prompt.encode("utf-8")) > MAX_PROMPT_BYTES
        ):
            raise RequestError("prompt_invalid")
        if sandbox not in {"read-only", "workspace-write"}:
            raise RequestError("sandbox_invalid")
        return payload
    if action == "verify":
        if set(payload) != {"action"}:
            raise RequestError("verify_request_fields_invalid")
        return payload
    raise RequestError("action_unsupported")


def _require_new_output(path: Path, code: str) -> None:
    try:
        path.lstat()
    except FileNotFoundError:
        return
    raise RequestError(code)


def _require_receipt(path: Path) -> None:
    try:
        info = path.lstat()
    except FileNotFoundError as exc:
        raise RequestError("receipt_missing") from exc
    if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
        raise RequestError("receipt_must_be_regular_non_symlink")
    if info.st_nlink != 1:
        raise RequestError("receipt_hardlink_forbidden")
    if hasattr(os, "getuid") and info.st_uid != os.getuid():
        raise RequestError("receipt_wrong_owner")


def _capture(repo_root: Path, request: dict[str, Any]) -> dict[str, Any]:
    receipt_path = repo_root / RECEIPT_PATH
    _require_new_output(receipt_path, "receipt_already_exists")
    receipt = capture_codex_session(
        repo=repo_root,
        prompt=request["prompt"],
        output_path=receipt_path,
        model=request["model"],
        sandbox=request["sandbox"],
    )
    return {
        "action": "capture",
        "result": "RECEIPT_CREATED",
        "receipt": RECEIPT_PATH.as_posix(),
        "session_id": receipt["session_id"],
        "event_count": receipt["event_count"],
        "chain_head_sha256": receipt["chain_head_sha256"],
        "limits": LIMITS,
    }


def _verify(repo_root: Path) -> tuple[dict[str, Any], bool]:
    receipt_path = repo_root / RECEIPT_PATH
    _require_receipt(receipt_path)
    receipt, _ = load_canonical_receipt(receipt_path)
    repository = verify_repository_state(receipt, repo_root)
    result = {
        "action": "verify",
        "result": repository["result"],
        "receipt_valid": True,
        "repository": {
            key: value
            for key, value in repository.items()
            if key != "repository_root"
        },
        "limits": LIMITS,
    }
    return result, bool(repository["valid"])


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="agentproof")
    parser.add_argument("--request", required=True)
    return parser


def _emit_error(error: str, **details: Any) -> int:
    print(
        json.dumps(
            {
                "result": "ERROR",
                "error": error,
                **details,
                "limits": LIMITS,
            },
            sort_keys=True,
        ),
        file=sys.stderr,
    )
    return 1


def _traceback_sha256(exc: Exception) -> str:
    rendered = "".join(
        traceback.TracebackException(
            type(exc),
            exc,
            exc.__traceback__,
            capture_locals=False,
        ).format()
    ).encode("utf-8")
    return hashlib.sha256(rendered).hexdigest()


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    try:
        repo_root = _require_git_root()
        _ensure_private_control_directory(repo_root)
        request_path = _require_fixed_request_argument(args.request)
        request = _validate_request(_read_and_consume_request(repo_root, request_path))
        if request["action"] == "capture":
            result = _capture(repo_root, request)
            status = 0
        else:
            result, valid = _verify(repo_root)
            status = 0 if valid else 1
        print(json.dumps(result, ensure_ascii=False, sort_keys=True))
        return status
    except (
        CodexCaptureError,
        ReceiptLoadError,
        RepositoryVerifyError,
        RequestError,
    ) as exc:
        return _emit_error(str(exc))
    except Exception as exc:
        return _emit_error(
            "unexpected_runtime_error",
            exception_type=type(exc).__name__,
            traceback_sha256=_traceback_sha256(exc),
        )


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 9f615bdbc1bae10c53ac3db41ea7cdc843794cc8ad04a54cd34e44319bb4d905