feat(rulings): system_usage_events is read back — per-System counts and a telemetry block (#4769)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 36s

#4769 "Rulings are counted where someone will read them": milestone 444
step 4 wrote system_usage_events and nothing read it.

- retrieval_telemetry gains a `system_usage` block: surfacings and opens by
  source, distinct counts, and `by_system` naming the areas most shown.
  There is deliberately no pull-through ratio, because rulings travel in full
  in the line and opens are the exception.
- usage_for_systems (one GROUP BY) adds `usage` to the REST Systems list and
  detail, and to MCP get_system. MCP list_systems is unchanged.
- The Systems UI shows a "rulings shown N×" chip.
- rulings_pre_tool, rulings_write_path and mcp_get_system are now declared
  registry points; the registry guard covers their recorders.
- The Systems store merges a PATCH reply instead of replacing the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 08:49:20 -04:00
co-authored by Claude Opus 5.5
parent 1b973ebd13
commit 0a1bb68808
12 changed files with 457 additions and 4 deletions
+20
View File
@@ -131,6 +131,26 @@ def _no_rulings_arm():
yield
@pytest.fixture(autouse=True)
def _no_system_usage_readout():
"""Stub the per-System usage lookup the Systems list and get_system carry
(#4769).
Autouse for _no_rulings_arm's reason: every route and tool test that
reads a System now reaches a GROUP BY on system_usage_events. Stubbed to
the zero shape for whatever ids were asked. tests/test_system_usage_readout.py
binds the real function at import time, before this patch runs.
"""
from scribe.services.system_usage import empty_system_usage
async def _zeros(system_ids):
return {int(s): empty_system_usage() for s in system_ids}
with patch("scribe.services.system_usage.usage_for_systems",
AsyncMock(side_effect=_zeros)):
yield
@pytest.fixture(autouse=True)
def _no_task_log_arm():
"""Stub the task-log read arm that get_task / list_tasks / get_milestone