create_decision_log now checks for an existing log with the same
(eval_id, decision_type, context) tuple before inserting. If found, it
returns the existing row's dict instead of appending a duplicate. The
append-only audit invariant is preserved (a worker that re-emits the
same decision within a single minute no longer produces duplicate rows).
Removed the xfail guard in test_decision_log_immutability; the test
now passes (3 identical POSTs → 1 DB row).
T2 frontend CronPoolMonitor: polls every 5s, unmount clears interval,
plus it.fails guard for missing visibilitychange listener (S6).
T3 router ORM contract: heartbeat updates fields + 404, decision-logs
POST persists + 404, GET lists inserted (guards S2 — must continue to
pass after router handlers move into a service in P3).
T8 decision-log immutability: append-only on context change passes;
dedupe of identical (decision_type, context) is xfail (real gap).
T7 alert→auto-scale: task_backlog alert recorded on first call (passes);
check_alerts never invokes auto_scale is xfail (real gap, §6.4).
All real gaps are logged in .scratch/v111-architecture-scan.md §6.