The lesson slot has recorded surfaced-vs-opened since it shipped. Nothing
showed it. `get_lesson`'s REST door attached `usage` to the payload and no
view rendered it; the listing did not attach it at all, and neither MCP door
did.
#4196 asks when a lesson that keeps getting followed should become a rule, and
names the trap in the same breath: raw frequency cannot separate "this should
bind" from "this trigger is too broad", and the second is the commoner reading
by a wide margin. Surfaced-AND-opened can separate them. Neither question is
answerable by a reader who cannot see the numbers, which is why this is the
first step and not the threshold.
NO THRESHOLD IS PROPOSED HERE, deliberately. The corpus today is 10 lessons
with 7 recorded surfacings and 3 opens, over about fifteen hours of usage
data. A promotion rule fitted to that would be fitting noise — #3311's failure,
and the warning lesson #4228 was written to carry. `UsageBadge` already
declines to render a verdict under three surfacings for the same reason. So
#4196 stays open: its subject, the promotion path, is still unbuilt. What
lands is the evidence it needs.
- REST `GET /api/lessons` and MCP `list_lessons` attach `usage` to every
row, from one aggregate per page rather than a per-row read, which would
be N+1 by construction. Every row carries the key zero-filled, so "never
surfaced" is a state a reader can see rather than a missing field they
have to interpret.
- MCP `get_lesson` attaches it too, and reads it BEFORE recording its own
pull. That door records a pull on every open — it has to, or the kind sits
permanently at zero — which makes the order load-bearing in a way it is
not for a kind that only counts. The REST detail door already ordered it
this way; the two now agree about what the number means.
- `LessonDetailView` renders `UsageBadge` (snippet #3460) rather than
re-spelling the chip, with the advice keyed to this kind: a lesson that is
repeatedly offered and never opened is usually keyed to a situation nobody
is in, so it points at re-keying `when_to_apply`, not at deleting the
claim.
Guards, in the two styles this pair of doors already uses: the MCP side driven
behaviourally through mocks, including the call ORDER for `get_lesson`; the
REST side on structure like its siblings in test_lesson_rest_door.py, because
the route is decorated and returns a Quart response. Rule 167's falsifier is
included.
KNOWN GAP, not fixed here: `KnowledgeView` is the only lesson LIST in the UI
and it reads `/knowledge`, not `/lessons` — so the REST listing change reaches
`frontend/src/api/lessons.ts::listLessons`, which currently has no consumer.
The agent-facing listing does reach a reader today.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy