mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
61 lines
3.6 KiB
Python
61 lines
3.6 KiB
Python
"""Read-only campaign readiness. No case selection, database launch or IPC cleanup."""
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import platform
|
|
import shutil
|
|
import subprocess
|
|
|
|
|
|
def command(arguments):
|
|
result = subprocess.run(arguments, capture_output=True, text=True, timeout=10)
|
|
return result.returncode, result.stdout
|
|
|
|
|
|
def inspect(root):
|
|
root = pathlib.Path(root).resolve()
|
|
ipc = {"available": False, "semaphore_headroom": None, "shared_memory_headroom": None}
|
|
if platform.system() == "Darwin":
|
|
status, output = command(["ipcs", "-s", "-a"])
|
|
status_m, output_m = command(["ipcs", "-m", "-a"])
|
|
status_limits, output_limits = command(["sysctl", "-n", "kern.sysv.semmns", "kern.sysv.shmmni"])
|
|
if status == status_m == status_limits == 0:
|
|
semaphores = [line.split() for line in output.splitlines() if line.startswith("s ")]
|
|
segments = [line for line in output_m.splitlines() if line.startswith("m ")]
|
|
limits = [int(value) for value in output_limits.split()]
|
|
consumed = sum(int(row[8]) for row in semaphores)
|
|
ipc = {"available": True, "semaphore_sets": len(semaphores), "semaphores_consumed": consumed,
|
|
"semaphore_limit": limits[0], "semaphore_headroom": limits[0] - consumed,
|
|
"shared_memory_segments": len(segments), "shared_memory_limit": limits[1],
|
|
"shared_memory_headroom": limits[1] - len(segments)}
|
|
runner = root / "packages/paperclip-runner/runner/target/debug/paperclip-runnerd"
|
|
tools = {name: bool(shutil.which(name)) for name in ["node", "pnpm", "python3", "codex"]}
|
|
credentials = {name: bool(os.getenv(name)) for name in ["CODEX_AUTH_JSON", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "DAYTONA_API_KEY"]}
|
|
status, revision = command(["git", "-C", str(root), "rev-parse", "HEAD"])
|
|
status_dirty, dirty = command(["git", "-C", str(root), "status", "--porcelain", "--untracked-files=no"])
|
|
# A guard against known exhaustion, not proof that a database will start.
|
|
ipc_guard = ipc["available"] and ipc["semaphore_headroom"] >= 34 and ipc["shared_memory_headroom"] >= 2
|
|
return {"version": 1, "measuredAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
"scope": "read-only host preflight; no selected-case attempt", "platform": platform.system(),
|
|
"sourceRevision": revision.strip() if status == 0 else None,
|
|
"trackedSourceClean": not dirty.strip() if status_dirty == 0 else None,
|
|
"tools": tools, "runnerExecutable": os.access(runner, os.X_OK),
|
|
"credentialsPresent": credentials,
|
|
"privateCodexAuthFilePresent": (pathlib.Path.home() / ".codex/auth.json").is_file(),
|
|
"localEnvironmentFilePresent": (root / ".env.runner-e2e.local").is_file(),
|
|
"immutableDaytonaImageConfigured": "@sha256:" in os.getenv("PAPERCLIP_E2E_DAYTONA_IMAGE", ""),
|
|
"ipc": ipc, "singleInstanceIpcGuardPassed": ipc_guard,
|
|
"ipcGuardLimits": {"minimumFreeSemaphores": 34, "minimumFreeSharedMemorySlots": 2},
|
|
"limitations": ["Credential existence does not establish account validity.",
|
|
"IPC headroom does not establish database or browser health.",
|
|
"No external connected integration is qualified by this inspection."]}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--checkout", default=str(pathlib.Path(__file__).resolve().parents[2]))
|
|
arguments = parser.parse_args()
|
|
print(json.dumps(inspect(arguments.checkout), indent=2))
|