#000037 Phase 2: wire QA runner → controller_events advisory

arborist.qa.runner.ask() now emits one controller_decision +
controller_difficulty + controller_budget_allocation triple per QA
cycle via _emit_qa_controller_advisory(conn, cache_key, verdict).
Single-branch synthesis from the verdict's audit_mode (Δ5F mapping
matching the dry-run simulator) and n_unverified/n_quotes
(witness_divergence). Wrapped in try/except so any advisory failure
never blocks the QA result; pure audit-only — does not enter
audit_events.event_hash preimage, audit chain semantics unchanged.

Lazy import keeps the QA hot path free of substrate-module load on
calls that never reach this helper (cache hits + early returns).

Tests: 3 new in tests/test_prometheus_audit.py — happy-path emits
all three event kinds, defensive parsing tolerates malformed verdict
without raising, second call with same (event_kind, body_hash) is
idempotent under the existing UNIQUE constraint.
This commit is contained in:
russell@unturf.com 2026-05-10 18:29:48 -04:00
parent 4b85a0af25
commit 43380b11b7
No known key found for this signature in database
2 changed files with 181 additions and 0 deletions

View file

@ -386,3 +386,92 @@ def test_query_by_kind_returns_rows(shard):
"WHERE event_kind='controller_budget_allocation'"
).fetchone()[0]
assert n_alloc == 2
# ---------------------------------------------------------------------
# Phase 2 integration: arborist.qa.runner._emit_qa_controller_advisory
# wires real QA verdicts → controller_events rows
# ---------------------------------------------------------------------
def test_qa_runner_advisory_writes_controller_events(shard):
"""The QA runner's `_emit_qa_controller_advisory` synthesizes a
single-branch decision from a verdict dict and writes three rows
(decision + difficulty + budget_allocation) to controller_events.
Wired into the cache-miss path of `arborist.qa.runner.ask` so
every live QA cycle produces a row by default."""
from arborist.qa.runner import _emit_qa_controller_advisory
conn, _ = shard
verdict = {
"audit_mode": "UNGROUNDED",
"n_quotes": 4,
"n_verified": 1,
"unverified_quotes": '["q1", "q2", "q3"]',
"verifier_method": "quote",
}
with transaction(conn):
_emit_qa_controller_advisory(
conn, "abc123def456ghi7" * 4, verdict
)
kinds = [
r["event_kind"]
for r in conn.execute(
"SELECT event_kind FROM controller_events ORDER BY event_id"
).fetchall()
]
assert kinds.count("controller_decision") == 1
assert kinds.count("controller_difficulty") == 1
# single-branch chunk → 1 allocation row.
assert kinds.count("controller_budget_allocation") >= 1
def test_qa_runner_advisory_is_non_blocking_on_bad_verdict(shard):
"""Helper tolerates malformed verdict dicts — never raises into
the QA hot path. The wire-up in ask() also wraps in try/except,
so this test just exercises the helper's defensive parsing."""
from arborist.qa.runner import _emit_qa_controller_advisory
conn, _ = shard
# Bad shape: missing n_quotes, garbage unverified_quotes JSON.
bad_verdict = {
"audit_mode": "HYBRID",
"unverified_quotes": "not-valid-json[",
"verifier_method": "paraphrase",
}
with transaction(conn):
_emit_qa_controller_advisory(conn, "bad-cache-key", bad_verdict)
# Made it through; rows still written.
n = conn.execute(
"SELECT COUNT(*) FROM controller_events"
).fetchone()[0]
assert n >= 1
def test_qa_runner_advisory_idempotent_on_same_verdict(shard):
"""Calling the helper twice with the same verdict + cache_key
is a no-op on the second call (the underlying UNIQUE constraint
on controller_events catches it)."""
from arborist.qa.runner import _emit_qa_controller_advisory
conn, _ = shard
verdict = {
"audit_mode": "STRICT",
"n_quotes": 3,
"n_verified": 3,
"unverified_quotes": "[]",
"verifier_method": "quote",
}
cache_key = "stable-cache-key-stable-cache"
with transaction(conn):
_emit_qa_controller_advisory(conn, cache_key, verdict)
first_count = conn.execute(
"SELECT COUNT(*) FROM controller_events"
).fetchone()[0]
with transaction(conn):
_emit_qa_controller_advisory(conn, cache_key, verdict)
second_count = conn.execute(
"SELECT COUNT(*) FROM controller_events"
).fetchone()[0]
assert first_count == second_count