docs(6205): lesson_rules comments state the reason, not the instance that taught it
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m10s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / Python tests (push) Canceled after 1m48s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m10s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / Python tests (push) Canceled after 1m48s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user