From 5605b863b243b62dd1539fec4a8f468a16d24cee Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 20:23:11 -0400 Subject: [PATCH] docs(6205): lesson_rules comments state the reason, not the instance that taught it Issue, milestone, snippet and rule-number citations are removed; the scope docstring states the leak it prevents without naming the milestone that closed it for the direct arms. Co-Authored-By: Claude Opus 5.5 --- src/scribe/services/lesson_rules.py | 57 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/scribe/services/lesson_rules.py b/src/scribe/services/lesson_rules.py index 95114ffe..78692074 100644 --- a/src/scribe/services/lesson_rules.py +++ b/src/scribe/services/lesson_rules.py @@ -1,4 +1,4 @@ -"""Lessons point at rules (milestone 440, #4196). +"""Lessons point at rules. A lesson is a non-binding record of one situation; a rule is the binding choice for a class of them. This service owns the link between the two — which @@ -7,17 +7,18 @@ rule a lesson is an instance of — and both directions of reading it. THE STATES (models/lesson_rule_link.py says why each exists): `suggested` while evidence accumulates, `confirmed` or `rejected` once a judgment is made. Every write here is a JUDGMENT, so every write lands as -confirmed or rejected; `suggested` rows come from the co-surfacing recorder -(#4637), never from a caller naming a rule. +confirmed or rejected; `suggested` rows come from the co-surfacing recorder, +never from a caller naming a rule. -THE THIRD ANSWER (#4631). "Which rule is this an instance of?" has three +THE THIRD ANSWER. "Which rule is this an instance of?" has three answers, and only two of them are links: a rule named (confirmed), or NO RULE FITS — a `lesson_no_rule` row carrying the reason. A lesson with neither is UNJUDGED, and `list_unjudged` lists those. The three are kept apart because a lesson that stands alone is the raw material for a rule nobody has written yet -(#4634), and that is unreadable if "nobody looked" means the same thing. +(see Convergence below), and that is unreadable if "nobody looked" means the +same thing. -THE SOFT LINK (#4637). When one hook request puts a lesson and a rule in +THE SOFT LINK. When one hook request puts a lesson and a rule in front of the reader together, `co_surfaced` records the pair as evidence on a SUGGESTED link, and once that evidence crosses a bar the next co-arrival asks the reader to judge it. The evidence counts distinct SITUATIONS, not @@ -25,7 +26,7 @@ arrivals: two records that resemble each other arrive together on every similar prompt, and counting each time would measure their similarity over and over rather than any relation between them. -ACL (rule 78). Linking changes what a lesson says about itself, so it needs +ACL (services/access.py). Linking changes what a lesson says about itself, so it needs WRITE on the lesson (`access.can_write_note`, share-aware). It names a rule, so it needs the rule to be one the caller may read — rules are owner-scoped, and `rulebooks._fetch_owned_rule` / `_owned_rules_clause` are that check's two @@ -67,8 +68,8 @@ _NO_RULE_NOTE = "no rule fits: " # What `rule_judgment` reads on a lesson payload — the three answers. LINKED, NO_RULE, UNJUDGED = "linked", "no_rule", "unjudged" -# THE SOFT-LINK EVIDENCE BAR (#4637) — defaults, stated as defaults (rules 32, -# 115): nothing about this install chose them. +# THE SOFT-LINK EVIDENCE BAR — defaults, stated as defaults: nothing about an +# install chose them. # # Three distinct situations before a pair is put to the reader. One is # coincidence and two is a pattern only by courtesy; three different prompts @@ -361,7 +362,7 @@ async def attach_lesson_rules(user_id: int, rows: list[dict], *, key: str = "id" """Add `rules` and `rule_judgment` (plus `no_rule` when that is the answer) to each lesson payload row, in place — a fixed number of queries per page. - Fail-open (snippet #4286): this decorates a lesson the caller already has, + Fail-open: this decorates a lesson the caller already has, so a failed lookup leaves the keys off and logs, rather than refusing the lesson. An absent key reads as "not attached", never as "no rule". """ @@ -470,7 +471,7 @@ async def rule_candidates( async def attach_rule_lessons(user_id: int, data: dict, rule_id: int) -> None: """Add `lessons` to a rule payload, in place, when there are any. - Present-only, as a rule's `systems` and `relations` are (#2483), and + Present-only, as a rule's `systems` and `relations` are, and fail-open for the reason `attach_lesson_rules` gives. """ try: @@ -487,8 +488,8 @@ def situation_key(arm: str, text: str) -> str: """A fingerprint of one situation, so a repeat counts once. `arm` is part of the key — "p" for a prompt, "w" for a file being written, - "s" for a session (the rule↔moment signal, milestone 458 step 7), "d" - for a day (the misfire signal, step 7b, whose door carries no session) — + "s" for a session (the rule↔moment signal), "d" for a day (the misfire + signal, whose door carries no session) — because each names a different kind of situation and must not collide. On the prompt arm the text is the prompt: lowercased, split into word tokens of _MIN_TOKEN or more, de-duplicated and sorted, so the same ask @@ -657,7 +658,7 @@ async def _co_surfaced(user_id, lesson_ids, rule_ids, arm, situation, project_id return _proposal_line(lid, lesson_title, rid, rule.title, rule.kind or "rule", ev) -# ── Reaching a rule through its lessons (#4633) ────────────────────────────── +# ── Reaching a rule through its lessons ────────────────────────────────────── async def confirmed_lessons(user_id: int) -> set[int]: @@ -666,8 +667,8 @@ async def confirmed_lessons(user_id: int) -> set[int]: CONFIRMED ONLY, and the reason is the soft link's: a suggested link that carried its rule would put the two side by side on every prompt that - found the lesson, which is exactly the co-arrival #4637 counts as - evidence — the suggestion would manufacture its own proof. + found the lesson, which is exactly the co-arrival the soft link counts + as evidence — the suggestion would manufacture its own proof. Asked first and cheaply, so an arm on an install with no confirmed links — every install, until someone judges one — never pays for a search. @@ -695,11 +696,11 @@ async def confirmed_rules_in_scope( ) -> dict[int, list]: """{lesson_id: [Rule]} — each lesson's CONFIRMED rules that may surface here. - "May surface here" is the rule arms' own scope (milestone 414): a global - rule, or a rule of THIS project. A lesson is project-independent and - surfaces everywhere, so without this a lesson linked to project A's rule - would carry that rule into project B's sessions — the leak milestone 414 - closed for the direct arms, reopened through a side door. + "May surface here" is the rule arms' own scope: a global rule, or a rule + of THIS project. A lesson is project-independent and surfaces everywhere, + so without this a lesson linked to project A's rule would carry that rule + into project B's sessions — a leak the direct arms' scope prevents, + reopened through a side door. """ from scribe.services.rulebooks import _owned_rules_clause @@ -727,16 +728,16 @@ async def confirmed_rules_in_scope( return out -# ── Convergence: lessons with no rule that keep landing in one place (#4634) ─ +# ── Convergence: lessons with no rule that keep landing in one place ──────── # # A lesson answered "no rule fits" is a situation nothing binds. One is a # lesson. Several that resemble each other are what a missing rule looks like # from the outside — the same moment met again and again, each time written # down as advice. This is noticed at the WRITE, when the newest of them is -# answered, and never by a sweep or a timer (#4183): the reader is in the +# answered, and never by a sweep or a timer: the reader is in the # situation then, and a nudge arriving anywhere else is one nobody acts on. # -# WHAT "RESEMBLE" MEANS (#5193). Not a fixed similarity. Lessons are written +# WHAT "RESEMBLE" MEANS. Not a fixed similarity. Lessons are written # in one register — a sentence of advice and the moment it applies — so an # embedder scores any two of them well above two unrelated texts, and a # constant bar sits inside that band: every no-rule lesson then "resembles" @@ -750,10 +751,10 @@ async def confirmed_rules_in_scope( # # And resemblance must be MUTUAL, among every member, not just to the lesson # being answered. A lesson broad enough to stand near many others is a hub, -# and hub-and-spoke is how three unrelated lessons used to make a "group" -# through one general one. A clique is the shape of one situation met again. +# and hub-and-spoke lets three unrelated lessons make a "group" through one +# general one. A clique is the shape of one situation met again. # -# DEFAULTS, stated as defaults (rules 32, 115). Three lessons — the new one +# DEFAULTS, stated as defaults. Three lessons — the new one # and two it resembles — is the smallest group that is a pattern rather than # a pair. One spread above the background is a modest bar for one direction # alone; the strictness comes from requiring it of every pair in both @@ -834,7 +835,7 @@ async def convergence_for(user_id: int, lesson_id: int) -> dict | None: """The convergence a newly answered no-rule lesson completes, or None. Fail-open: this decorates a write that already succeeded, so a failed - search leaves the response as it was (snippet #4286's reasoning). + search leaves the response as it was. """ try: return await _convergence_for(user_id, lesson_id)