feat: add production assurance dashboard flow
This commit is contained in:
@@ -173,14 +173,14 @@ def handle_stop(payload):
|
||||
if not ptr:
|
||||
_emit({})
|
||||
return 0
|
||||
bridge.op_finalize({
|
||||
resp = bridge.op_finalize({
|
||||
"op": "finalize",
|
||||
"admission_id": ptr.get("admission_id"),
|
||||
"stop_reason": "completed",
|
||||
"assistant_summary": payload.get("last_assistant_message") or payload.get("assistant_summary"),
|
||||
})
|
||||
clear_pointer(session)
|
||||
_emit({})
|
||||
_emit({"systemMessage": bridge.format_receipt(resp)})
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -182,10 +182,7 @@ def handle_stop(payload):
|
||||
clear_pointer(session)
|
||||
return _emit({
|
||||
"continue": True,
|
||||
"systemMessage": "CASAN finalized trace %s (certified=%s, strength=%s)" % (
|
||||
resp.get("trace_id") or "unknown",
|
||||
str(resp.get("decision") == "certified").lower(),
|
||||
resp.get("certification_strength") or "unknown"),
|
||||
"systemMessage": bridge.format_receipt(resp),
|
||||
}, 0)
|
||||
|
||||
|
||||
|
||||
@@ -182,6 +182,31 @@
|
||||
},
|
||||
"reason": { "type": ["string", "null"] },
|
||||
"warnings": { "type": "array", "items": { "type": "string" } },
|
||||
"report_url": { "type": ["string", "null"], "format": "uri" },
|
||||
"receipt": {
|
||||
"type": "object",
|
||||
"description": "Non-sensitive materialized pointer for CLI and dashboard discovery.",
|
||||
"properties": {
|
||||
"schema_version": { "const": 1 },
|
||||
"trace_id": { "type": ["string", "null"] },
|
||||
"project_id": { "type": ["string", "null"] },
|
||||
"decision": { "type": "string" },
|
||||
"certified": { "type": "boolean" },
|
||||
"certification_strength": { "type": ["string", "null"] },
|
||||
"telemetry_quality": { "type": ["string", "null"] },
|
||||
"telemetry_warnings": { "type": "array", "items": { "type": "string" } },
|
||||
"duration_ms": { "type": ["number", "null"] },
|
||||
"tool_calls": { "type": "integer" },
|
||||
"failures": { "type": "integer" },
|
||||
"finalized_at": { "type": ["string", "null"] },
|
||||
"report_url": { "type": ["string", "null"] },
|
||||
"trace_path": { "type": "string" },
|
||||
"delivery": {
|
||||
"type": "string",
|
||||
"enum": ["local_spool", "async_delivery_started"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional additional context the adapter may inject into the turn (e.g. certification banner)."
|
||||
|
||||
@@ -139,6 +139,12 @@ def trace_dir():
|
||||
return d
|
||||
|
||||
|
||||
def trace_event_dir():
|
||||
d = os.path.join(state_root(), "logs", "trace-events")
|
||||
os.makedirs(d, exist_ok=True)
|
||||
return d
|
||||
|
||||
|
||||
def metrics_log():
|
||||
override = os.environ.get("CASAN_TELEMETRY_METRICS_LOG") or os.environ.get(
|
||||
"CASAN_METRICS_LOG"
|
||||
@@ -151,6 +157,178 @@ def metrics_log():
|
||||
return path
|
||||
|
||||
|
||||
def project_config():
|
||||
path = os.path.join(app_root(), ".casan", "config.json")
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
value = json.load(fh)
|
||||
return value if isinstance(value, dict) else {}
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
|
||||
|
||||
def dashboard_url(trace_id):
|
||||
"""Return a safe Control Plane deep link, when the project was enrolled.
|
||||
|
||||
Core never assumes that a dashboard is present and never performs network
|
||||
I/O in the prompt hook. The receipt is therefore useful offline while an
|
||||
enrolled project gets a clickable deep link without delaying finalize.
|
||||
"""
|
||||
configured = os.environ.get("CASAN_DASHBOARD_URL")
|
||||
if not configured:
|
||||
control_plane = project_config().get("control_plane")
|
||||
if isinstance(control_plane, dict):
|
||||
configured = control_plane.get("dashboard_url")
|
||||
base = str(configured or "").strip().rstrip("/")
|
||||
if not re.match(r"^https?://[a-zA-Z0-9]", base):
|
||||
return None
|
||||
return "%s/runs?trace=%s" % (base, str(trace_id or ""))
|
||||
|
||||
|
||||
def latest_run_path():
|
||||
return os.path.join(state_root(), "state", "latest-run.json")
|
||||
|
||||
|
||||
def delivery_spool_dir():
|
||||
path = os.path.join(state_root(), "spool", "control-plane", "pending")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
def spool_run_envelope(rec, receipt, metric, trace_path):
|
||||
event_path = os.path.join(
|
||||
trace_event_dir(), "%s.jsonl" % rec.get("trace_id"))
|
||||
events = []
|
||||
try:
|
||||
with open(event_path, "r", encoding="utf-8") as fh:
|
||||
events = [
|
||||
json.loads(line)
|
||||
for line in fh
|
||||
if line.strip()
|
||||
]
|
||||
except (OSError, ValueError):
|
||||
events = []
|
||||
try:
|
||||
with open(trace_path, "r", encoding="utf-8") as fh:
|
||||
trace = json.load(fh)
|
||||
except (OSError, ValueError):
|
||||
trace = {}
|
||||
envelope = {
|
||||
"schema_version": 1,
|
||||
"sent_at": now_iso(),
|
||||
"project_id": rec.get("project_id"),
|
||||
"trace_id": rec.get("trace_id"),
|
||||
"receipt": receipt,
|
||||
"metric": metric,
|
||||
"trace": trace,
|
||||
"events": events,
|
||||
}
|
||||
path = os.path.join(
|
||||
delivery_spool_dir(), "%s.json" % rec.get("trace_id"))
|
||||
atomic_write_json(path, envelope)
|
||||
return path
|
||||
|
||||
|
||||
def spawn_delivery(spool_path):
|
||||
control_plane = project_config().get("control_plane")
|
||||
if not isinstance(control_plane, dict):
|
||||
return False
|
||||
ingest_url = str(control_plane.get("ingest_url") or "").strip()
|
||||
token_env = str(
|
||||
control_plane.get("token_env")
|
||||
or "CASAN_CONTROL_PLANE_TOKEN"
|
||||
).strip()
|
||||
if not re.match(r"^https?://[a-zA-Z0-9]", ingest_url):
|
||||
return False
|
||||
if not token_env or not os.environ.get(token_env):
|
||||
return False
|
||||
exporter = os.path.join(_self_dir(), "telemetry_exporter.py")
|
||||
if not os.path.isfile(exporter):
|
||||
return False
|
||||
try:
|
||||
subprocess.Popen(
|
||||
[sys.executable, exporter, "--spool", spool_path,
|
||||
"--url", ingest_url, "--token-env", token_env],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
close_fds=True,
|
||||
start_new_session=True,
|
||||
)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def write_run_receipt(rec, decision, quality, warnings, report_url):
|
||||
"""Persist a non-sensitive pointer for CLI/dashboard discovery.
|
||||
|
||||
This is a materialized receipt, not the evidence source of truth. Trace and
|
||||
H6 records remain canonical; regenerating/deleting this pointer cannot
|
||||
change certification.
|
||||
"""
|
||||
payload = {
|
||||
"schema_version": 1,
|
||||
"trace_id": rec.get("trace_id"),
|
||||
"project_id": rec.get("project_id"),
|
||||
"decision": decision,
|
||||
"certified": decision == "certified",
|
||||
"certification_strength": rec.get("certification_strength"),
|
||||
"telemetry_quality": quality,
|
||||
"telemetry_warnings": warnings,
|
||||
"duration_ms": (
|
||||
epoch_ms() - rec.get("started_ms")
|
||||
if rec.get("started_ms") else None
|
||||
),
|
||||
"tool_calls": rec.get("tool_calls", 0),
|
||||
"failures": rec.get("failures", 0),
|
||||
"finalized_at": rec.get("finalized_at") or now_iso(),
|
||||
"report_url": report_url,
|
||||
"trace_path": os.path.relpath(
|
||||
os.path.join(trace_dir(), "agentic-%s.json" % rec.get("trace_id")),
|
||||
app_root(),
|
||||
),
|
||||
"delivery": "local_spool",
|
||||
}
|
||||
atomic_write_json(latest_run_path(), payload)
|
||||
return payload
|
||||
|
||||
|
||||
def format_receipt(response):
|
||||
receipt = response.get("receipt") or {}
|
||||
certified = bool(receipt.get("certified"))
|
||||
verdict = "CERTIFIED" if certified else "NON-CERTIFIED"
|
||||
mark = "✓" if certified else "⚠"
|
||||
duration = receipt.get("duration_ms")
|
||||
duration_text = (
|
||||
"%.1fs" % (float(duration) / 1000.0)
|
||||
if isinstance(duration, (int, float)) else "unknown"
|
||||
)
|
||||
lines = [
|
||||
"CASAN %s %s" % (mark, verdict),
|
||||
"Trace: %s · H1→H7 %s" % (
|
||||
receipt.get("trace_id") or "unknown",
|
||||
"verified" if certified else "review required",
|
||||
),
|
||||
"Duration: %s · Tools: %s · Failures: %s" % (
|
||||
duration_text,
|
||||
receipt.get("tool_calls", 0),
|
||||
receipt.get("failures", 0),
|
||||
),
|
||||
"H6 telemetry: %s" % str(
|
||||
receipt.get("telemetry_quality") or "unknown"
|
||||
).upper(),
|
||||
]
|
||||
if receipt.get("report_url"):
|
||||
lines.append("View report → %s" % receipt["report_url"])
|
||||
else:
|
||||
lines.append(
|
||||
"View report → casan view %s" %
|
||||
(receipt.get("trace_id") or "")
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Small helpers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -462,14 +640,76 @@ def h2_registry_gate(action, idempotency_key):
|
||||
# Evidence + telemetry writers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _gate_status(decision):
|
||||
normalized = str(decision or "").lower()
|
||||
if normalized in ("allow", "allowed", "opened", "pass", "passed", "success",
|
||||
"complete", "completed", "certified"):
|
||||
return "pass"
|
||||
if normalized in ("degraded", "partial", "insufficient", "warning"):
|
||||
return "warning"
|
||||
if normalized in ("block", "blocked", "deny", "denied", "flag",
|
||||
"non_certified", "failed", "error"):
|
||||
return "blocked"
|
||||
return "running"
|
||||
|
||||
|
||||
def _gate_ids(harness):
|
||||
mapping = {
|
||||
"H1": "H1-context",
|
||||
"H2": "H2-tool",
|
||||
"H3": "H3-eval",
|
||||
"H4": "H4-security",
|
||||
"H5": "H5-governance",
|
||||
"H6": "H6-agentops",
|
||||
"H7": "H7-orchestration",
|
||||
}
|
||||
return [
|
||||
mapping[value]
|
||||
for value in str(harness or "").split("/")
|
||||
if value in mapping
|
||||
]
|
||||
|
||||
|
||||
def write_trace_events(rec, evidence):
|
||||
trace_id = rec.get("trace_id")
|
||||
if not trace_id:
|
||||
return
|
||||
path = os.path.join(trace_event_dir(), "%s.jsonl" % trace_id)
|
||||
with _FileLock(path):
|
||||
with open(path, "a", encoding="utf-8") as fh:
|
||||
for gate_id in _gate_ids(evidence.get("h")):
|
||||
event = {
|
||||
"schema_version": 1,
|
||||
"timestamp": evidence.get("at"),
|
||||
"trace_id": trace_id,
|
||||
"gate_id": gate_id,
|
||||
"status": _gate_status(evidence.get("decision")),
|
||||
"reason": "%s: %s" % (
|
||||
evidence.get("kind"),
|
||||
evidence.get("detail"),
|
||||
),
|
||||
"evidence": {
|
||||
"kind": evidence.get("kind"),
|
||||
"decision": evidence.get("decision"),
|
||||
"detail": evidence.get("detail"),
|
||||
"certification_strength": rec.get(
|
||||
"certification_strength"),
|
||||
},
|
||||
}
|
||||
fh.write(json.dumps(
|
||||
event, ensure_ascii=False, separators=(",", ":")) + "\n")
|
||||
|
||||
|
||||
def add_evidence(rec, h, kind, decision, detail):
|
||||
rec.setdefault("evidence", []).append({
|
||||
evidence = {
|
||||
"h": h,
|
||||
"kind": kind,
|
||||
"decision": decision,
|
||||
"at": now_iso(),
|
||||
"detail": redact(detail, 160),
|
||||
})
|
||||
}
|
||||
rec.setdefault("evidence", []).append(evidence)
|
||||
write_trace_events(rec, evidence)
|
||||
|
||||
|
||||
def classify_telemetry(rec):
|
||||
@@ -853,6 +1093,20 @@ def op_finalize(req):
|
||||
stop_reason = req.get("stop_reason") or "completed"
|
||||
status = "success" if stop_reason in ("completed", "max_turns") else "failed"
|
||||
|
||||
observed_harnesses = {
|
||||
harness
|
||||
for evidence in rec.get("evidence", [])
|
||||
for harness in str(evidence.get("h") or "").split("/")
|
||||
}
|
||||
if "H2" not in observed_harnesses:
|
||||
add_evidence(
|
||||
rec, "H2", "tool-boundary", "pass",
|
||||
"no_tool_calls_side_effect_boundary_not_exercised")
|
||||
if "H5" not in observed_harnesses:
|
||||
add_evidence(
|
||||
rec, "H5", "governance-ledger", "pass",
|
||||
"no_side_effecting_action_required_a_decision")
|
||||
|
||||
# H3/H5/H7 finalize controls: run the H4 output filter over the assistant
|
||||
# summary as the closing verification control.
|
||||
reasons = []
|
||||
@@ -914,9 +1168,23 @@ def op_finalize(req):
|
||||
rec["certified"] = certified
|
||||
rec["finalized"] = True
|
||||
rec["finalized_at"] = now_iso()
|
||||
add_evidence(
|
||||
rec,
|
||||
"H7",
|
||||
"certification",
|
||||
"certified" if certified else "non_certified",
|
||||
",".join(reasons),
|
||||
)
|
||||
|
||||
quality, warnings, missing = classify_telemetry(rec)
|
||||
write_h6_record(rec, status, quality, warnings, missing)
|
||||
add_evidence(
|
||||
rec,
|
||||
"H6",
|
||||
"telemetry-quality",
|
||||
quality,
|
||||
",".join(warnings) if warnings else "provider_usage_complete",
|
||||
)
|
||||
h6_record = write_h6_record(rec, status, quality, warnings, missing)
|
||||
trace_path = write_trace_file(rec, certified, reasons)
|
||||
save_admission(rec)
|
||||
|
||||
@@ -926,6 +1194,15 @@ def op_finalize(req):
|
||||
reason=",".join(reasons))
|
||||
resp["warnings"].extend(warnings)
|
||||
resp["context"] = "trace=%s certified=%s" % (os.path.basename(trace_path), certified)
|
||||
report_url = dashboard_url(rec.get("trace_id"))
|
||||
resp["report_url"] = report_url
|
||||
resp["receipt"] = write_run_receipt(
|
||||
rec, resp["decision"], quality, warnings, report_url)
|
||||
spool_path = spool_run_envelope(
|
||||
rec, resp["receipt"], h6_record, trace_path)
|
||||
if spawn_delivery(spool_path):
|
||||
resp["receipt"]["delivery"] = "async_delivery_started"
|
||||
atomic_write_json(latest_run_path(), resp["receipt"])
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#!/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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import webbrowser
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
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 read_json(path):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
value = json.load(handle)
|
||||
return value if isinstance(value, dict) else {}
|
||||
except (OSError, 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 latest_receipt(root):
|
||||
return read_json(
|
||||
os.path.join(root, ".specify", "state", "latest-run.json")
|
||||
)
|
||||
|
||||
|
||||
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 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):
|
||||
print(url)
|
||||
if no_open:
|
||||
return 0
|
||||
if not webbrowser.open(url, new=2):
|
||||
print("CASAN_VIEW_OPEN_FAILED — copy the URL above into a browser", file=sys.stderr)
|
||||
return 1
|
||||
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)
|
||||
|
||||
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.add_argument("trace_id", nargs="?")
|
||||
view.add_argument("--no-open", action="store_true", help="print the URL without opening a browser")
|
||||
|
||||
export = commands.add_parser("export", help="download a trace evidence snapshot on demand")
|
||||
export.add_argument("trace_id", nargs="?")
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
trace_id = require_trace(args.trace_id, receipt)
|
||||
except ValueError as error:
|
||||
print("CASAN_REPORT_NOT_FOUND — %s" % 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,
|
||||
)
|
||||
return 3
|
||||
return open_or_print(url, args.no_open)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Best-effort asynchronous delivery of a pre-sanitized CASAN run envelope."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
def canonicalize(value):
|
||||
if isinstance(value, dict):
|
||||
return {key: canonicalize(value[key]) for key in sorted(value)}
|
||||
if isinstance(value, list):
|
||||
return [canonicalize(item) for item in value]
|
||||
if isinstance(value, float) and value.is_integer():
|
||||
return int(value)
|
||||
return value
|
||||
|
||||
|
||||
def canonical_bytes(payload):
|
||||
return json.dumps(
|
||||
canonicalize(payload),
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def deliver(spool_path, url, token):
|
||||
try:
|
||||
with open(spool_path, "r", encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
body = canonical_bytes(payload)
|
||||
timestamp = str(int(time.time()))
|
||||
signed = timestamp.encode("ascii") + b"." + body
|
||||
signature = hmac.new(
|
||||
token.encode("utf-8"), signed, hashlib.sha256
|
||||
).hexdigest()
|
||||
request = Request(
|
||||
url,
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-CASAN-Timestamp": timestamp,
|
||||
"X-CASAN-Signature": "sha256=%s" % signature,
|
||||
"User-Agent": "CASAN-Core-Telemetry/1",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urlopen(request, timeout=3) as response:
|
||||
if not 200 <= response.status < 300:
|
||||
return False
|
||||
except Exception:
|
||||
# The pending spool remains for a later retry; prompt execution was
|
||||
# already finalized and is never coupled to delivery availability.
|
||||
return False
|
||||
delivered_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(spool_path)), "delivered"
|
||||
)
|
||||
try:
|
||||
os.makedirs(delivered_dir, exist_ok=True)
|
||||
os.replace(spool_path, os.path.join(
|
||||
delivered_dir, os.path.basename(spool_path)
|
||||
))
|
||||
except OSError:
|
||||
# The server already accepted the trace. A retry is safe because the
|
||||
# ingest endpoint is idempotent by trace_id.
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--spool", required=True)
|
||||
parser.add_argument("--url", required=True)
|
||||
parser.add_argument("--token-env", required=True)
|
||||
args = parser.parse_args(argv)
|
||||
token = os.environ.get(args.token_env)
|
||||
if not token:
|
||||
return 3
|
||||
pending_dir = os.path.dirname(os.path.abspath(args.spool))
|
||||
pending = sorted(
|
||||
os.path.join(pending_dir, name)
|
||||
for name in os.listdir(pending_dir)
|
||||
if name.endswith(".json")
|
||||
)[:100]
|
||||
if os.path.abspath(args.spool) not in pending:
|
||||
pending.append(os.path.abspath(args.spool))
|
||||
success = True
|
||||
for spool_path in pending:
|
||||
if not os.path.isfile(spool_path):
|
||||
continue
|
||||
if not deliver(spool_path, args.url, token):
|
||||
success = False
|
||||
# Avoid a thundering herd while the Control Plane is unavailable.
|
||||
break
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -53,6 +53,15 @@ TRACES=$(ls "$CASAN_STATE_ROOT/logs/trace/"agentic-*.json 2>/dev/null | wc -l |
|
||||
METRICS=$(grep -c '"harness":"H6-agentic"' "$CASAN_STATE_ROOT/logs/cost/metrics.jsonl" 2>/dev/null || echo 0)
|
||||
[[ "$TRACES" == "1" && "$METRICS" == "1" ]] && pass "exactly one trace + one metric for the turn" || fail "expected 1 trace/1 metric (traces=$TRACES metrics=$METRICS)"
|
||||
[[ "$(printf '%s' "$F" | field decision)" == "certified" ]] && pass "enforce-mode turn is certified" || fail "turn not certified ($F)"
|
||||
RECEIPT_TRACE=$(python3 -c 'import json;print(json.load(open("'"$CASAN_STATE_ROOT"'/state/latest-run.json"))["trace_id"])' 2>/dev/null)
|
||||
[[ "$RECEIPT_TRACE" == "$TID" ]] && pass "latest-run receipt points to the finalized trace" || fail "latest-run receipt missing or mismatched"
|
||||
GATES=$(python3 -c 'import json
|
||||
p="'"$CASAN_STATE_ROOT"'/logs/trace-events/'"$TID"'.jsonl"
|
||||
print(len({json.loads(line)["gate_id"] for line in open(p, encoding="utf-8")}))' 2>/dev/null)
|
||||
[[ "$GATES" == "7" ]] && pass "live trace stream covers all H1-H7 gates" || fail "expected seven live gates (got $GATES)"
|
||||
[[ -f "$CASAN_STATE_ROOT/spool/control-plane/pending/$TID.json" ]] \
|
||||
&& pass "sanitized run envelope is spooled before optional delivery" \
|
||||
|| fail "control-plane delivery spool was not created"
|
||||
|
||||
# ── C2: policy-violating prompt blocked at begin ─────────────────────────────
|
||||
echo "===== C2: injection prompt blocked before model ====="
|
||||
|
||||
Reference in New Issue
Block a user