#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:
parent
4b85a0af25
commit
43380b11b7
2 changed files with 181 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue