arborist controller-events: read-only inspector for #000037 Phase 2 rows

The Phase 2 advisory writes (_emit_qa_controller_advisory) populate
the controller_events sibling table on every QA cycle. Until now
the only way to inspect was raw SQL. This adds a top-level
arborist subcommand that walks every shard, surfaces decision /
difficulty / budget_allocation rows, and renders either a compact
terminal table or JSON.

Flags:
- --limit (default 20)
- --kind {controller_decision|controller_difficulty|controller_budget_allocation}
- --organism-prefix PREFIX  (LIKE prefix; "qa:" matches QA-runner advisories)
- --since-seconds N         (rows recorded within the last N seconds)
- --body                    (include JSON body_blob in --json output)
- --json                    (machine-readable {summary, rows})

Reads via sqlite3 read-only URI; silently skips shards without a
controller_events table. No writes, no schema migration triggered.

Wires into the #000045 Retrigger 1 measurement story (need ≥1000
advisory rows from Phase 2 wiring before Phase 3 implementation
opens) — operators now have a one-line check for that signal.

Tests: 5 new in tests/test_prometheus_audit.py — happy-path table
output, --kind filter, --organism-prefix filter, --json shape,
graceful skip of non-arborist sqlite files in the shards-dir.
test_cli_smoke parameterized list updated so the argparse-
construction smoke test also covers the new subcommand.
This commit is contained in:
russell@unturf.com 2026-05-10 19:25:54 -04:00
parent 6734f8037f
commit cc72784cec
No known key found for this signature in database
3 changed files with 277 additions and 1 deletions

View file

@ -75,7 +75,8 @@ def test_arborist_no_subcommand_returns_error():
# contains a verb not in this list).
_TOP_LEVEL_SUBCOMMANDS = [
"ingest", "search", "verify", "distill", "ask", "query",
"inspect", "losses", "providence", "burn", "burn-kindergarten",
"inspect", "losses", "providence", "controller-events",
"burn", "burn-kindergarten",
"reclassify", "emergent", "evict", "rehydrate", "activity",
"stats", "canon", "analyze", "snapshot", "substrate", "memory",
"capital", "selfmodel", "warrant-status", "warrant-resolve",

View file

@ -475,3 +475,112 @@ def test_qa_runner_advisory_idempotent_on_same_verdict(shard):
"SELECT COUNT(*) FROM controller_events"
).fetchone()[0]
assert first_count == second_count
# ---------------------------------------------------------------------
# `arborist controller-events` CLI inspector (#000037 follow-through)
# ---------------------------------------------------------------------
def _run_inspector(shard_dir, **kwargs):
"""Invoke ``_cmd_controller_events`` against a tmp shards dir."""
import argparse
from arborist.cli import _cmd_controller_events
args = argparse.Namespace(
global_shards_dir=str(shard_dir),
db=None,
limit=kwargs.get("limit", 20),
kind=kwargs.get("kind", None),
organism_prefix=kwargs.get("organism_prefix", None),
since_seconds=kwargs.get("since_seconds", None),
body=kwargs.get("body", False),
json=kwargs.get("json", False),
)
return _cmd_controller_events(args)
def test_inspector_lists_rows_from_a_shard_dir(shard, capsys):
"""Inspector reads every shard with a controller_events table.
Empty/missing-table shards are silently skipped."""
conn, db_path = shard
with transaction(conn):
emit_controller_events(
conn, _stub_decision(), organism_root="qa:abc123"
)
rc = _run_inspector(db_path.parent, limit=20)
out = capsys.readouterr().out
assert rc == 0
assert "controller_events" in out
assert "qa:abc123" in out
assert "controller_decision" in out
def test_inspector_filters_by_kind(shard, capsys):
"""``--kind controller_difficulty`` returns only difficulty rows."""
conn, db_path = shard
with transaction(conn):
emit_controller_events(
conn, _stub_decision(), organism_root="qa:k1"
)
rc = _run_inspector(
db_path.parent, kind="controller_difficulty", limit=20
)
out = capsys.readouterr().out
assert rc == 0
assert "controller_difficulty" in out
assert "controller_decision" not in out
assert "controller_budget_allocation" not in out
def test_inspector_filters_by_organism_prefix(shard, capsys):
"""``--organism-prefix`` matches LIKE prefix."""
conn, db_path = shard
with transaction(conn):
emit_controller_events(
conn, _stub_decision(), organism_root="qa:keep"
)
emit_controller_events(
conn,
_stub_decision(selected_branch_id="X", label="REJECT"),
organism_root="sweep:drop",
)
rc = _run_inspector(
db_path.parent, organism_prefix="qa:", limit=20
)
out = capsys.readouterr().out
assert rc == 0
assert "qa:keep" in out
assert "sweep:drop" not in out
def test_inspector_json_emits_summary_and_rows(shard, capsys):
"""``--json`` returns ``{"summary": {...}, "rows": [...]}``."""
conn, db_path = shard
with transaction(conn):
emit_controller_events(
conn, _stub_decision(), organism_root="qa:json-test"
)
rc = _run_inspector(db_path.parent, json=True, limit=20)
payload = json.loads(capsys.readouterr().out)
assert rc == 0
assert payload["summary"]["controller_decision"] >= 1
assert payload["summary"]["controller_difficulty"] >= 1
assert any(r["organism_root"] == "qa:json-test" for r in payload["rows"])
def test_inspector_skips_shards_without_table(tmp_path, capsys):
"""A shards-dir with a non-arborist sqlite file is skipped, not
crashed."""
import sqlite3 as _sqlite3
bogus = tmp_path / "not-arborist.db"
c = _sqlite3.connect(bogus)
c.execute("CREATE TABLE foo (x INTEGER)")
c.commit()
c.close()
rc = _run_inspector(tmp_path, limit=5)
out = capsys.readouterr().out
assert rc == 0
assert "no controller_events rows matched" in out