mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(orchestrator): stop auditor alert-spawn rotation — ack as auditor on dispatch
The auditor respawned every ~3 min on the same stale rework alerts. Root cause: _dispatch_audit_work's alert path fetched the SYSTEM-wide "not fully acked" view (list_system_notifications), but the auditor is read-only (no ack verb) and auditor_triage never acks — so once an alert existed the CEO was the only party who could clear it, and the CEO hadn't acked. The per-alert cooldown (PR #499) only paced a rotation through the N un-acked alerts; it was a damper, not a fix. Fix: fetch the auditor's OWN pending-ack view (GET /notifications authed as the auditor -> list_for_agent, which filters acked_by for the auditor) and ack the alert as the auditor on dispatch. Each alert is now a one-shot, DB-persistent: the next tick cannot respawn on an alert the auditor already observed — even one the CEO hasn't acked. Authed as the auditor (not the system identity) so the route selects the per-recipient view; HTTP rather than DB-direct so it shares the orchestrator's loop in prod and stays loop-safe in the e2e harness (which runs _dispatch_audit_work in its own asyncio.run loop, away from the app's DB engine). e2e now asserts the alert is in acked_by for the auditor after dispatch — the rotation-stopper itself, not just the spawn.
This commit is contained in:
@@ -228,3 +228,30 @@ def test_reactive_alert_producer_spawns_auditor(
|
||||
prompt = call.kwargs["initial_prompt"]
|
||||
assert "QUALITY ALERT" in prompt
|
||||
assert "missing edge-case coverage" in prompt
|
||||
|
||||
# Rotation-stopper: dispatch acks the alert as the auditor (the auditor is
|
||||
# read-only, no ack verb), so the auditor's pending-ack view no longer
|
||||
# returns it and the next tick cannot respawn on this same alert. Without
|
||||
# this ack the per-alert cooldown only paced a rotation through every
|
||||
# stale not-fully-acked alert — the loop being fixed.
|
||||
async def _acked_by_auditor(session: AsyncSession) -> bool:
|
||||
from roboco.db.tables import NotificationTable
|
||||
from sqlalchemy import select
|
||||
|
||||
row = (
|
||||
(
|
||||
await session.execute(
|
||||
select(NotificationTable).where(
|
||||
NotificationTable.related_task_id == task_id,
|
||||
NotificationTable.type == NotificationType.ALERT,
|
||||
)
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.one()
|
||||
)
|
||||
return auditor_id in row.acked_by
|
||||
|
||||
assert stack.run_db(_acked_by_auditor), (
|
||||
"alert not acked as auditor after dispatch — rotation not stopped"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user