teleo-infrastructure/scripts/probe_argus_kb_state.py
twentyOne2x 34b45365ec
Some checks are pending
CI / lint-and-test (push) Waiting to run
Add Leo direct-claim preflight probes
2026-07-10 04:50:04 +02:00

216 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""Probe read-only Argus KB diagnostics for current approved-proposal state."""
from __future__ import annotations
import argparse
import base64
import json
import urllib.error
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable
from urllib.parse import urljoin
DEFAULT_BASE_URL = "http://77.42.65.182:8081"
REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
DEFAULT_OUT = REPORT_DIR / "argus-kb-state-current.json"
DEFAULT_MARKDOWN_OUT = REPORT_DIR / "argus-kb-state-current.md"
FetchJson = Callable[[str, int], dict[str, Any]]
def fetch_json(url: str, timeout: int) -> dict[str, Any]:
request = urllib.request.Request(url, headers={"Accept": "application/json"})
try:
with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310 - operator-supplied URL
return json.loads(response.read().decode("utf-8"))
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as exc:
return {"_fetch_error": str(exc)}
def endpoint(base_url: str, path: str) -> str:
return urljoin(base_url.rstrip("/") + "/", path.lstrip("/"))
def decode_supplied_json(value: str | None) -> dict[str, Any] | None:
if not value:
return None
try:
return json.loads(base64.b64decode(value, validate=True).decode("utf-8"))
except Exception as exc: # noqa: BLE001 - retained as a structured fetch error
return {"_fetch_error": f"failed to parse supplied base64 JSON: {exc}"}
def supplied_payloads(args: argparse.Namespace) -> dict[str, dict[str, Any] | None]:
return {
"kb_proposals": decode_supplied_json(args.kb_proposals_json_base64),
"vital_signs": decode_supplied_json(args.vital_signs_json_base64),
"metrics": decode_supplied_json(args.metrics_json_base64),
}
def proposal_summary(kb_proposals: dict[str, Any]) -> dict[str, Any]:
packets = kb_proposals.get("packets") if isinstance(kb_proposals.get("packets"), list) else []
return {
"generated_at": kb_proposals.get("generated_at"),
"filters": kb_proposals.get("filters"),
"total": kb_proposals.get("total"),
"status_counts": kb_proposals.get("status_counts") or {},
"review_state_counts": kb_proposals.get("review_state_counts") or {},
"worker_applyable_count": kb_proposals.get("worker_applyable_count"),
"read_only": kb_proposals.get("read_only"),
"proposal_ids": [packet.get("proposal_id") for packet in packets],
"proposal_review_states": {
packet.get("proposal_id"): packet.get("review_state") for packet in packets if packet.get("proposal_id")
},
"proposal_applyability": {
packet.get("proposal_id"): {
"worker_applyable": packet.get("worker_applyable"),
"has_apply_payload": packet.get("has_apply_payload"),
"next_admin_action": packet.get("next_admin_action"),
}
for packet in packets
if packet.get("proposal_id")
},
}
def collect(args: argparse.Namespace, *, fetcher: FetchJson = fetch_json) -> dict[str, Any]:
urls = {
"kb_proposals": endpoint(args.base_url, "/api/kb-proposals"),
"vital_signs": endpoint(args.base_url, "/api/vital-signs"),
"metrics": endpoint(args.base_url, "/api/metrics"),
}
supplied = supplied_payloads(args)
raw = {name: payload if payload is not None else fetcher(urls[name], args.timeout) for name, payload in supplied.items()}
fetch_sources = {name: "supplied_base64_json" if payload is not None else "urllib" for name, payload in supplied.items()}
proposals = proposal_summary(raw["kb_proposals"])
proposal_total = proposals.get("total")
review_state_counts = proposals.get("review_state_counts") or {}
status_counts = proposals.get("status_counts") or {}
checks = {
"http_readback_ok": all("_fetch_error" not in payload for payload in raw.values()),
"approved_proposals_visible": isinstance(proposal_total, int)
and proposal_total > 0
and status_counts.get("approved") == proposal_total,
"approved_need_apply_payload": isinstance(proposal_total, int)
and review_state_counts.get("approved_needs_apply_payload") == proposal_total,
"no_worker_applyable_approved_proposals": proposals.get("worker_applyable_count") == 0,
"kb_proposals_endpoint_read_only": proposals.get("read_only") is True,
}
return {
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
"mode": "argus_kb_state_readonly_http_probe",
"base_url": args.base_url,
"urls": urls,
"fetch_sources": fetch_sources,
"status": "pass" if all(checks.values()) else "fail",
"checks": checks,
"summary": {
"proposals": proposals,
"vital_signs": {
"claim_index_status": raw["vital_signs"].get("claim_index_status"),
"review_throughput": raw["vital_signs"].get("review_throughput"),
"funnel": raw["vital_signs"].get("funnel"),
"queue_staleness": raw["vital_signs"].get("queue_staleness"),
},
"metrics": {
"status_map": raw["metrics"].get("status_map"),
"source_map": raw["metrics"].get("source_map"),
"breakers": raw["metrics"].get("breakers"),
},
},
"raw": raw,
"claim_ceiling": (
"This is public Argus HTTP diagnostics evidence only. It proves the diagnostics surface can see "
"approved KB proposals and their applyability state; it does not prove Docker/Postgres row counts, "
"gateway systemd state, Telegram-visible reply quality, or production apply execution."
),
}
def write_json(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
def write_markdown(path: Path, data: dict[str, Any]) -> None:
proposals = data["summary"]["proposals"]
lines = [
"# Argus KB State Probe",
"",
f"Generated UTC: `{data['generated_at_utc']}`",
f"Mode: `{data['mode']}`",
f"Status: `{data['status']}`",
f"Base URL: `{data['base_url']}`",
"",
"## Fetch Sources",
"",
]
lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(data["fetch_sources"].items()))
lines.extend(
[
"",
"## Checks",
"",
]
)
lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(data["checks"].items()))
lines.extend(
[
"",
"## Approved Proposal State",
"",
f"- Total: `{proposals.get('total')}`",
f"- Status counts: `{json.dumps(proposals.get('status_counts'), sort_keys=True)}`",
f"- Review state counts: `{json.dumps(proposals.get('review_state_counts'), sort_keys=True)}`",
f"- Worker-applyable count: `{proposals.get('worker_applyable_count')}`",
f"- Read-only endpoint: `{proposals.get('read_only')}`",
"",
"## Proposal IDs",
"",
]
)
lines.extend(f"- `{proposal_id}`" for proposal_id in proposals.get("proposal_ids") or [])
lines.extend(["", "## Claim Ceiling", "", data["claim_ceiling"], ""])
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines), encoding="utf-8")
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
parser.add_argument("--timeout", type=int, default=10)
parser.add_argument("--kb-proposals-json-base64")
parser.add_argument("--vital-signs-json-base64")
parser.add_argument("--metrics-json-base64")
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
data = collect(args)
write_json(args.out, data)
write_markdown(args.markdown_out, data)
print(
json.dumps(
{
"out": str(args.out),
"markdown_out": str(args.markdown_out),
"status": data["status"],
"checks": data["checks"],
},
indent=2,
sort_keys=True,
)
)
return 0 if data["status"] == "pass" else 2
if __name__ == "__main__":
raise SystemExit(main())