init: company-haness 설계
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run one verifier and append a typed, context-bound evidence receipt.
|
||||
|
||||
Claude Code's generic PostToolUse response does not always expose a process exit
|
||||
code. This sanctioned runner owns the subprocess, so exit status, assertion
|
||||
status, command argv, output hashes, workflow/session/agent context, and subject
|
||||
are recorded together. It never invokes a shell.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import _workspace as W # noqa: E402
|
||||
|
||||
CATEGORIES = (
|
||||
"acceptance-criteria", "test", "security", "privacy", "data-quality",
|
||||
"reliability", "release-readiness",
|
||||
)
|
||||
TRIVIAL_EXECUTABLES = {"true", "false", "echo", "printf", "ls", "cat", "grep", "pwd"}
|
||||
|
||||
|
||||
def _now():
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
def _sha(value):
|
||||
return hashlib.sha256(value).hexdigest()
|
||||
|
||||
|
||||
def _safe_cwd(raw):
|
||||
root = os.path.realpath(W.work_root())
|
||||
candidate = raw if os.path.isabs(raw) else os.path.join(root, raw)
|
||||
candidate = os.path.realpath(candidate)
|
||||
if os.path.commonpath([root, candidate]) != root or not os.path.isdir(candidate):
|
||||
raise ValueError(f"--cwd must be an existing directory inside workspace: {raw}")
|
||||
return candidate
|
||||
|
||||
|
||||
def _append_receipt(receipt):
|
||||
evidence_dir = W.evidence_dir()
|
||||
os.makedirs(evidence_dir, exist_ok=True)
|
||||
path = os.path.join(evidence_dir, "ledger.jsonl")
|
||||
with open(path, "a", encoding="utf-8") as fh:
|
||||
try:
|
||||
import fcntl
|
||||
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
||||
except Exception:
|
||||
pass
|
||||
fh.write(json.dumps(receipt, ensure_ascii=False) + "\n")
|
||||
fh.flush()
|
||||
os.fsync(fh.fileno())
|
||||
try:
|
||||
import fcntl
|
||||
fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(description="execute a verifier and mint a typed receipt")
|
||||
parser.add_argument("--workflow", required=True)
|
||||
parser.add_argument("--agent", required=True)
|
||||
parser.add_argument("--session", required=True)
|
||||
parser.add_argument("--category", choices=CATEGORIES, required=True)
|
||||
parser.add_argument("--subject", required=True,
|
||||
help="specific criterion/component being verified")
|
||||
parser.add_argument("--source-revision-sha256")
|
||||
parser.add_argument("--cwd", default=".")
|
||||
parser.add_argument("command", nargs=argparse.REMAINDER)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
args = build_parser().parse_args(argv)
|
||||
command = list(args.command)
|
||||
if command and command[0] == "--":
|
||||
command = command[1:]
|
||||
if not command:
|
||||
sys.stderr.write("[verify_run] verifier command required after --\n")
|
||||
return 2
|
||||
executable = os.path.basename(command[0]).lower()
|
||||
if executable in TRIVIAL_EXECUTABLES:
|
||||
sys.stderr.write(f"[verify_run] trivial command cannot prove verification: {executable}\n")
|
||||
return 2
|
||||
if executable in {"sh", "bash", "zsh", "fish"}:
|
||||
sys.stderr.write("[verify_run] shell interpreters are forbidden; pass verifier argv directly\n")
|
||||
return 2
|
||||
if args.source_revision_sha256 and (
|
||||
len(args.source_revision_sha256) != 64
|
||||
or any(ch not in "0123456789abcdef" for ch in args.source_revision_sha256.lower())):
|
||||
sys.stderr.write("[verify_run] --source-revision-sha256 must be 64-hex\n")
|
||||
return 2
|
||||
try:
|
||||
cwd = _safe_cwd(args.cwd)
|
||||
except Exception as exc:
|
||||
sys.stderr.write(f"[verify_run] {exc}\n")
|
||||
return 2
|
||||
|
||||
started_at = _now()
|
||||
started = time.monotonic()
|
||||
try:
|
||||
completed = subprocess.run(command, cwd=cwd, capture_output=True, check=False)
|
||||
exit_code = completed.returncode
|
||||
stdout = completed.stdout or b""
|
||||
stderr = completed.stderr or b""
|
||||
except OSError as exc:
|
||||
exit_code, stdout, stderr = 127, b"", str(exc).encode("utf-8", "replace")
|
||||
finished_at = _now()
|
||||
receipt_id = f"vr-{int(time.time())}-{uuid.uuid4().hex[:12]}"
|
||||
receipt = {
|
||||
"receipt_id": receipt_id,
|
||||
"tool_use_id": receipt_id,
|
||||
"receipt_type": "verification-run",
|
||||
"tool_name": "VerifyRun",
|
||||
"workflow_id": args.workflow,
|
||||
"session_id": args.session,
|
||||
"agent_id": args.agent,
|
||||
"verification_category": args.category,
|
||||
"verification_subject": args.subject,
|
||||
"assertion_status": "passed" if exit_code == 0 else "failed",
|
||||
"exit_code": exit_code,
|
||||
"command_argv": command,
|
||||
"command_argv_sha256": _sha(
|
||||
json.dumps(command, ensure_ascii=False, separators=(",", ":")).encode("utf-8")),
|
||||
"cwd": cwd,
|
||||
"started_at": started_at,
|
||||
"ts": finished_at,
|
||||
"duration_ms": round((time.monotonic() - started) * 1000),
|
||||
"stdout_sha256": _sha(stdout),
|
||||
"stderr_sha256": _sha(stderr),
|
||||
}
|
||||
if args.source_revision_sha256:
|
||||
receipt["source_revision_sha256"] = args.source_revision_sha256.lower()
|
||||
try:
|
||||
_append_receipt(receipt)
|
||||
except Exception as exc:
|
||||
sys.stderr.write(f"[verify_run] receipt append failed: {exc}\n")
|
||||
return 125
|
||||
|
||||
if stdout:
|
||||
sys.stdout.buffer.write(stdout)
|
||||
if not stdout.endswith(b"\n"):
|
||||
sys.stdout.buffer.write(b"\n")
|
||||
if stderr:
|
||||
sys.stderr.buffer.write(stderr)
|
||||
if not stderr.endswith(b"\n"):
|
||||
sys.stderr.buffer.write(b"\n")
|
||||
sys.stderr.write(
|
||||
f"[verify_run] receipt-id={receipt_id} status={receipt['assertion_status']} "
|
||||
f"exit={exit_code} category={args.category} subject={args.subject}\n")
|
||||
return exit_code if 0 <= exit_code <= 124 else 124
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user