feat(4636): rule pull-through per surface, and per-rule usage with confirmed lessons on list_rules
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 22s

retrieval_telemetry gains rule_usage.by_source, the twin of usage.by_source:
distinct rules each surface showed, how many an agent opened, and a ratio
that is null on a bulk delivery. list_rules(with_usage=True, days=N) adds
each rule's usage, windowed, and how many lessons are confirmed instances
of it, so linked and unlinked rules can be compared through MCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 19:20:32 -04:00
co-authored by Claude Opus 5.5
parent 71260a28e2
commit d05d4f3632
9 changed files with 287 additions and 9 deletions
@@ -93,6 +93,22 @@ async def test_naming_rules_confirms_them_and_both_sides_read_the_link(world):
assert [(l["id"], l["state"]) for l in lessons] == [(world["lesson"], "confirmed")]
async def test_a_rule_list_counts_confirmed_lessons_only(world):
"""The bulk count a rule list reads: a rejected link is not a lesson
proving the rule, a rule with no links reads 0, and a stranger counts
nothing through a lesson they cannot read."""
owner = world["owner"]
await links_svc.set_lesson_rules(owner, world["lesson"], [world["r1"], world["r2"]])
await links_svc.judge_link(owner, world["lesson"], world["r2"], "reject", "not it")
rules = [world["r1"], world["r2"], world["foreign"]]
assert await links_svc.confirmed_lesson_counts(owner, rules) == {
world["r1"]: 1, world["r2"]: 0, world["foreign"]: 0,
}
assert (await links_svc.confirmed_lesson_counts(world["stranger"], rules))[world["r1"]] == 0
assert await links_svc.confirmed_lesson_counts(owner, []) == {}
async def test_a_rule_left_out_of_the_set_is_rejected_not_forgotten(world):
owner = world["owner"]
await links_svc.set_lesson_rules(owner, world["lesson"], [world["r1"], world["r2"]])