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:
parent
6734f8037f
commit
cc72784cec
3 changed files with 277 additions and 1 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue