feat: ship production local assurance viewer in core

This commit is contained in:
thanhnv
2026-07-28 23:09:22 +07:00
parent 3cc897c5bb
commit 9c9efb1043
21 changed files with 2544 additions and 136 deletions
+303 -102
View File
@@ -1,102 +1,183 @@
#!/usr/bin/env python3
"""Read-only CASAN report discovery for Core installations.
Core owns the trace/evidence source of truth but not the Control Plane UI. This
helper exposes the latest materialized receipt and opens an enrolled dashboard
without generating HTML on the prompt hot path.
"""
"""CASAN Core report, export and Local Assurance Viewer commands."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import re
import secrets
import subprocess
import sys
import time
from urllib.error import URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
import webbrowser
from urllib.parse import quote
from local_report import (
SAFE_ID,
h6_report,
read_json,
report_html,
run_report,
)
def find_root(start):
current = os.path.abspath(start)
while current != os.path.dirname(current):
if os.path.isfile(os.path.join(current, ".casan", "config.json")):
return current
current = os.path.dirname(current)
return os.path.abspath(start)
def find_root(start: str) -> Path:
current = Path(start).expanduser().resolve()
for candidate in (current, *current.parents):
if (candidate / ".casan" / "config.json").is_file():
return candidate
return current
def read_json(path):
def latest_receipt(root: Path) -> dict:
return read_json(root / ".specify" / "state" / "latest-run.json")
def require_trace(value, receipt) -> str:
trace_id = str(value or receipt.get("trace_id") or "")
if not SAFE_ID.fullmatch(trace_id):
raise ValueError("no safe trace id was supplied and no latest run exists")
return trace_id
def viewer_state_path(root: Path) -> Path:
return root / ".specify" / "state" / "local-viewer.json"
def viewer_log_path(root: Path) -> Path:
return root / ".specify" / "state" / "local-viewer.log"
def load_viewer_state(root: Path) -> dict:
state = read_json(viewer_state_path(root))
if state.get("project_root") != str(root.resolve()):
return {}
if not isinstance(state.get("port"), int) or not isinstance(state.get("token"), str):
return {}
return state
def health(state: dict, timeout: float = 0.7) -> dict:
if not state:
return {}
request = Request(
f"http://127.0.0.1:{state['port']}/healthz",
headers={"X-CASAN-Viewer-Token": state["token"]},
)
try:
with open(path, "r", encoding="utf-8") as handle:
value = json.load(handle)
return value if isinstance(value, dict) else {}
except (OSError, ValueError):
with urlopen(request, timeout=timeout) as response:
payload = json.loads(response.read().decode("utf-8"))
return payload if response.status == 200 and isinstance(payload, dict) else {}
except (OSError, URLError, ValueError):
return {}
def dashboard_base(root):
configured = os.environ.get("CASAN_DASHBOARD_URL")
if not configured:
control_plane = read_json(
os.path.join(root, ".casan", "config.json")
).get("control_plane")
if isinstance(control_plane, dict):
configured = control_plane.get("dashboard_url")
base = str(configured or "").strip().rstrip("/")
return base if re.match(r"^https?://[a-zA-Z0-9]", base) else None
def ensure_viewer(root: Path, port: int = 0) -> dict:
existing = load_viewer_state(root)
if health(existing):
return existing
state_path = viewer_state_path(root)
state_path.parent.mkdir(parents=True, exist_ok=True)
lock_path = state_path.with_suffix(".lock")
acquired = False
for _attempt in range(70):
try:
lock_path.mkdir()
acquired = True
break
except FileExistsError:
state = load_viewer_state(root)
if health(state):
return state
time.sleep(0.1)
if not acquired:
try:
if time.time() - lock_path.stat().st_mtime > 15:
lock_path.rmdir()
lock_path.mkdir()
acquired = True
except OSError:
pass
if not acquired:
raise RuntimeError("another viewer start is still in progress")
try:
return _start_viewer(root, port, state_path)
finally:
try:
lock_path.rmdir()
except OSError:
pass
def latest_receipt(root):
return read_json(
os.path.join(root, ".specify", "state", "latest-run.json")
)
def _start_viewer(root: Path, port: int, state_path: Path) -> dict:
existing = load_viewer_state(root)
if health(existing):
return existing
try:
state_path.unlink()
except OSError:
pass
log_path = viewer_log_path(root)
try:
if log_path.stat().st_size > 1024 * 1024:
rotated = log_path.with_suffix(".log.1")
try:
rotated.unlink()
except OSError:
pass
os.replace(log_path, rotated)
except OSError:
pass
token = secrets.token_urlsafe(32)
server = Path(__file__).with_name("local_viewer.py")
with log_path.open("ab", buffering=0) as log:
popen_options = {
"cwd": str(root),
"stdin": subprocess.DEVNULL,
"stdout": log,
"stderr": subprocess.STDOUT,
"close_fds": True,
}
if os.name == "nt":
popen_options["creationflags"] = (
getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
| getattr(subprocess, "DETACHED_PROCESS", 0)
)
else:
popen_options["start_new_session"] = True
subprocess.Popen(
[
sys.executable, str(server),
"--root", str(root),
"--port", str(port),
"--token", token,
"--state-file", str(state_path),
],
**popen_options,
)
for _attempt in range(50):
time.sleep(0.1)
state = load_viewer_state(root)
if state.get("token") == token and health(state):
return state
raise RuntimeError(f"viewer did not become ready; inspect {log_path}")
def require_trace(value, receipt):
trace_id = value or receipt.get("trace_id")
if not trace_id or not re.match(r"^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$", str(trace_id)):
raise ValueError("no safe trace id was supplied and no latest run exists")
return str(trace_id)
def viewer_url(state: dict, trace_id: str | None = None, view: str | None = None) -> str:
query = {"token": state["token"]}
if trace_id:
query.update({"view": "runs", "trace": trace_id})
elif view:
query["view"] = view
return f"http://127.0.0.1:{state['port']}/?{urlencode(query)}"
def report_url(root, trace_id):
base = dashboard_base(root)
if not base:
return None
return "%s/runs?trace=%s" % (base, quote(trace_id, safe=""))
def export_url(root, trace_id, export_format):
base = dashboard_base(root)
if not base:
return None
return "%s/api/v1/reports/run/%s/export?format=%s" % (
base,
quote(trace_id, safe=""),
export_format,
)
def print_receipt(receipt):
if not receipt:
print("CASAN_REPORT_NONE — no finalized prompt receipt exists yet")
return 1
print("CASAN %s" % ("CERTIFIED" if receipt.get("certified") else "NON-CERTIFIED"))
print("trace_id=%s" % (receipt.get("trace_id") or "unknown"))
print("project_id=%s" % (receipt.get("project_id") or "unknown"))
print("h6_quality=%s" % (receipt.get("telemetry_quality") or "unknown"))
print("duration_ms=%s tool_calls=%s failures=%s" % (
receipt.get("duration_ms"),
receipt.get("tool_calls", 0),
receipt.get("failures", 0),
))
if receipt.get("report_url"):
print("report_url=%s" % receipt["report_url"])
return 0
def open_or_print(url, no_open):
def open_or_print(url: str, no_open: bool) -> int:
print(url)
if no_open:
return 0
@@ -106,52 +187,172 @@ def open_or_print(url, no_open):
return 0
def main(argv=None):
parser = argparse.ArgumentParser(description="CASAN Core report discovery")
parser.add_argument("--root", default=os.environ.get("CASAN_APP_ROOT") or os.getcwd())
commands = parser.add_subparsers(dest="command", required=True)
def print_receipt(receipt: dict, root: Path) -> int:
if not receipt:
print("CASAN_REPORT_NONE — no finalized prompt receipt exists yet")
print("next=complete a prompt, then run `casan view`")
return 1
certified = receipt.get("certified") is True
print("CASAN %s" % ("CERTIFIED" if certified else "NON-CERTIFIED"))
print("trace_id=%s" % (receipt.get("trace_id") or "unknown"))
print("project_id=%s" % (receipt.get("project_id") or "unknown"))
print("h6_quality=%s" % (receipt.get("telemetry_quality") or "unknown"))
print("duration_ms=%s tool_calls=%s failures=%s" % (
receipt.get("duration_ms"),
receipt.get("tool_calls", 0),
receipt.get("failures", 0),
))
print("view_command=casan view %s" % (receipt.get("trace_id") or ""))
active_viewer = load_viewer_state(root)
if health(active_viewer):
print("local_report_url=%s" % viewer_url(
active_viewer, str(receipt.get("trace_id") or "") or None))
if receipt.get("report_url"):
print("central_report_url=%s" % receipt["report_url"])
return 0
def export_report(root: Path, args, receipt: dict) -> int:
if args.h6:
report = h6_report(
root,
project=args.project,
run=args.trace_id,
from_date=args.from_date,
to_date=args.to_date,
limit=args.limit,
)
stem = report["report_id"].lower()
kind = "h6"
else:
trace_id = require_trace(args.trace_id, receipt)
report = run_report(root, trace_id)
if report["verdict"] == "not_found":
print("CASAN_REPORT_NOT_FOUND — no evidence exists for that trace", file=sys.stderr)
return 2
stem = re.sub(r"[^a-zA-Z0-9._-]", "-", report["report_id"]).lower()
kind = "run"
destination = Path(args.output or f"{stem}.{args.format}").expanduser()
if not destination.is_absolute():
destination = Path.cwd() / destination
destination.parent.mkdir(parents=True, exist_ok=True)
if args.format == "json":
body = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
else:
body = report_html(report, kind)
destination.write_text(body, encoding="utf-8")
print(str(destination.resolve()))
return 0
def viewer_command(root: Path, action: str, port: int, no_open: bool) -> int:
if action in {"start", "open"}:
try:
state = ensure_viewer(root, port)
except RuntimeError as error:
print(f"CASAN_VIEW_START_FAILED — {error}", file=sys.stderr)
return 3
return open_or_print(viewer_url(state), no_open or action == "start")
state = load_viewer_state(root)
status = health(state)
if action == "status":
if not status:
print("CASAN_VIEW_STOPPED")
return 1
print(json.dumps({
"status": "running",
"pid": state.get("pid"),
"url": viewer_url(state),
"project_id": status.get("project_id"),
"mode": "local_core",
}, indent=2))
return 0
if action == "stop":
if not status:
try:
viewer_state_path(root).unlink()
except OSError:
pass
print("CASAN_VIEW_ALREADY_STOPPED")
return 0
request = Request(
f"http://127.0.0.1:{state['port']}/__shutdown",
headers={"X-CASAN-Viewer-Token": state["token"]},
)
try:
urlopen(request, timeout=1).read()
except (OSError, URLError):
print("CASAN_VIEW_STOP_FAILED", file=sys.stderr)
return 3
for _attempt in range(30):
time.sleep(0.1)
if not health(state, timeout=0.1):
print("CASAN_VIEW_STOPPED")
return 0
print("CASAN_VIEW_STOP_PENDING")
return 0
return 64
def parser() -> argparse.ArgumentParser:
value = argparse.ArgumentParser(description=__doc__)
value.add_argument("--root", default=os.environ.get("CASAN_APP_ROOT") or os.getcwd())
commands = value.add_subparsers(dest="command", required=True)
latest = commands.add_parser("latest", help="show the latest finalized prompt receipt")
latest.add_argument("--json", action="store_true")
view = commands.add_parser("view", help="open a trace in the enrolled Control Plane")
view = commands.add_parser("view", help="open a trace in the local Core viewer")
view.add_argument("trace_id", nargs="?")
view.add_argument("--no-open", action="store_true", help="print the URL without opening a browser")
view.add_argument("--no-open", action="store_true")
view.add_argument("--port", type=int, default=0)
export = commands.add_parser("export", help="download a trace evidence snapshot on demand")
export = commands.add_parser("export", help="write a self-contained report on demand")
export.add_argument("trace_id", nargs="?")
export.add_argument("--h6", action="store_true", help="export the H6 dossier instead of a run dossier")
export.add_argument("--format", choices=["html", "json"], default="html")
export.add_argument("--no-open", action="store_true", help="print the URL without opening a browser")
export.add_argument("--output", "-o")
export.add_argument("--project")
export.add_argument("--from", dest="from_date")
export.add_argument("--to", dest="to_date")
export.add_argument("--limit", type=int, default=50)
args = parser.parse_args(argv)
viewer = commands.add_parser("viewer", help="manage the local Core viewer")
viewer.add_argument("action", choices=["start", "status", "stop", "open"])
viewer.add_argument("--port", type=int, default=0)
viewer.add_argument("--no-open", action="store_true")
return value
def main(argv=None) -> int:
args = parser().parse_args(argv)
root = find_root(args.root)
receipt = latest_receipt(root)
if args.command == "latest":
if args.json:
print(json.dumps(receipt, ensure_ascii=False, indent=2))
return 0 if receipt else 1
return print_receipt(receipt)
return print_receipt(receipt, root)
if args.command == "viewer":
return viewer_command(root, args.action, args.port, args.no_open)
if args.command == "export":
try:
return export_report(root, args, receipt)
except ValueError as error:
print(f"CASAN_REPORT_INVALID — {error}", file=sys.stderr)
return 2
try:
trace_id = require_trace(args.trace_id, receipt)
if args.trace_id and not SAFE_ID.fullmatch(str(args.trace_id)):
raise ValueError("unsafe trace id")
trace_id = str(args.trace_id or receipt.get("trace_id") or "") or None
state = ensure_viewer(root, args.port)
except ValueError as error:
print("CASAN_REPORT_NOT_FOUND — %s" % error, file=sys.stderr)
print(f"CASAN_REPORT_NOT_FOUND — {error}", file=sys.stderr)
return 2
url = (
report_url(root, trace_id)
if args.command == "view"
else export_url(root, trace_id, args.format)
)
if not url:
print(
"CASAN_CONTROL_PLANE_NOT_ENROLLED — run `casan init "
"--dashboard-url https://your-casan.example` or set CASAN_DASHBOARD_URL",
file=sys.stderr,
)
except RuntimeError as error:
print(f"CASAN_VIEW_START_FAILED — {error}", file=sys.stderr)
return 3
return open_or_print(url, args.no_open)
return open_or_print(viewer_url(state, trace_id), args.no_open)
if __name__ == "__main__":