Milestone 440 steps 4, 5, 7: rules surface through their lessons, convergence named at the write, both records show the link #191

Merged
bvandeusen merged 4 commits from dev into main 2026-10-01 16:06:03 -04:00
Owner

Milestone 440 "Lessons point at rules — a rule is found through the situations that keep proving it".

  • #4633 "A rule surfaces through its lessons on the three rule arms" (fd2ebf4, f34249a). On the prompt, pre-tool and write-path arms, a lesson with a confirmed link brings its rule along.
    • It has its own slot (VIA_LESSON_LIMIT = 1) and its own source, rule_via_lesson.
    • The rule must be global or belong to this project.
  • #4634 "Convergence is named at the write" (6d3dca0). When a "no rule fits" answer completes a group of 3 or more no-rule lessons in one situation, the reply returns a convergence payload suggesting a rule.
  • #4635 "Both records show the link in the web UI" (75cefe6).
    • The lesson page shows its rules, or "No rule — why", or "Not yet judged". Suggested links come with Confirm / Not an instance buttons.
    • The rule panel lists the lessons that are instances of it.
  • #4640 "The snippet page hid its edit controls from shared editors", fixed in the same commit. The page's write check now uses a shared helper pinned to access.py's permission levels.

CI run 715 is green on 75cefe6.

🤖 Generated with Claude Code

Milestone 440 "Lessons point at rules — a rule is found through the situations that keep proving it". - **#4633 "A rule surfaces through its lessons on the three rule arms"** (fd2ebf4, f34249a). On the prompt, pre-tool and write-path arms, a lesson with a confirmed link brings its rule along. - It has its own slot (`VIA_LESSON_LIMIT = 1`) and its own source, `rule_via_lesson`. - The rule must be global or belong to this project. - **#4634 "Convergence is named at the write"** (6d3dca0). When a "no rule fits" answer completes a group of 3 or more no-rule lessons in one situation, the reply returns a `convergence` payload suggesting a rule. - **#4635 "Both records show the link in the web UI"** (75cefe6). - The lesson page shows its rules, or "No rule — why", or "Not yet judged". Suggested links come with Confirm / Not an instance buttons. - The rule panel lists the lessons that are instances of it. - **#4640 "The snippet page hid its edit controls from shared editors"**, fixed in the same commit. The page's write check now uses a shared helper pinned to access.py's permission levels. CI run 715 is green on 75cefe6. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 4 commits 2026-10-01 16:06:00 -04:00
feat(rules): a rule surfaces through the lessons confirmed as its instances (milestone 440 step 4, #4633)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 55s
CI & Build / Python tests (push) Failing after 1m16s
CI & Build / Build & push image (push) Skipped
fd2ebf4c13
On all three rule arms (prompt_rule, pre_tool_rule, write_path_rule), after
the direct match, a lesson matching the moment at the notes menu's own bar
brings the rule(s) it is CONFIRMED to be an instance of, rendered in rule
voice with "Reached through lesson #N “…”, a recorded instance of it."

- Confirmed links only (lesson_rules.confirmed_lessons /
  confirmed_rules_in_scope). A suggested link carrying its rule would
  manufacture the co-arrival #4637 counts and prove itself.
- Scope kept: a lesson surfaces everywhere, but the rule it brings must be
  global or this project's own — milestone 414's boundary, not reopened
  through a side door.
- Suppression is by RULE: anything the direct band named or the session
  ledger holds is skipped, whichever lesson reached it.
- Its own slot (VIA_LESSON_LIMIT = 1), not a rule slot — a stated default,
  since the plan's "decide by measurement" has nothing to measure until
  links are confirmed (#4632). Its own source, rule_via_lesson: registered,
  RANKED for pull-through, logged whenever it searches.
- Nothing searched at all while no lesson carries a confirmed link, which is
  every install until one is judged.
- build_prompt_rule_hint / build_tool_rule_hint are now thin wrappers over
  the direct arms (_prompt_rule_hint / _tool_rule_hint); the arm leaves its
  query in `_via_query` only when it ran, so a disabled arm or a blank prompt
  brings no rule in either, and the key never leaves the server.
- Via-lesson rules are not fed to the #4637 co-surfacing recorder: only
  direct matches are evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(rules): locate the pre-tool arm by what it does, not by its name (#4633)
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / Python tests (push) Successful in 1m48s
CI & Build / Build & push image (push) Successful in 26s
f34249a2d8
CI run 712 failed test_neither_rule_arm_logs_its_call_behind_a_results_guard
with "min() iterable argument is empty": the guard walked the function named
build_tool_rule_hint, which since fd2ebf4 is a wrapper that searches nothing
— the arm's body moved to _tool_rule_hint. The property it guards (the call
is logged before any early return) still held; the guard had pinned a name.

It now finds the async function that calls semantic_search_rules under the
pre_tool_rule source, so a later rename moves the guard with the arm instead
of emptying it (rule 167).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(lessons): convergence is named at the write — no-rule lessons that keep landing in one situation suggest a rule (milestone 440 step 5, #4634)
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 59s
CI & Build / Python tests (push) Successful in 1m45s
CI & Build / Build & push image (push) Successful in 27s
6d3dca0af5
When a lesson is answered "no rule fits" (create_lesson / update_lesson on
both doors), the response looks for other no-rule lessons it resembles and,
once there are CONVERGENCE_LESSONS (3) of them, carries `convergence`: the
members, their incidents and projects, and a hint to draft the missing rule
with create_rule (operator approval as always) and point each lesson at it —
or to leave them as lessons when no single choice is right every time.

- convergence_group is the pure bar: distinct LESSONS count, incidents never
  stand in for them (one broad lesson cannot trigger it), and a group whose
  sources all point at one incident is one event written up several times.
- convergence_for searches lessons by the new one's claim + trigger
  (trigger_title) at CONVERGENCE_THRESHOLD 0.65 — above the menu's "worth
  showing", below the duplicate gate's "same record" — then keeps the ones
  with a lesson_no_rule answer. Fail-open. No sweep, no timer (#4183).
- Defaults stated as defaults (rules 32, 115).
- Tests: the bar (pure), the search with stubs, the door, and the no-rule
  filter against Postgres; conftest stubs convergence_for for unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(lessons): both records show the link in the web UI (milestone 440 step 7, #4635)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 1m53s
CI & Build / Build & push image (push) Successful in 41s
75cefe60e4
The lesson page gains an "Instance of" panel that holds one of three answers.
Unjudged is the fall-through, so it is stated rather than left blank:
- the rule(s) it was judged an instance of;
- "No rule — <why>";
- "Not yet judged".

Suggested links show what they rest on (distinct situations, and projects
when more than one), with Confirm / Not an instance for a reader who can
write. Rejected links stay listed with their reason.

The rule slide-over lists the lessons that are instances of it, plus the
suggestions waiting on a judgment. Each entry links through to the other
record, and kind and state wear the existing .rule-chip.

The write check moves to utils/permission.ts. The copy on the snippet page
looked for "edit", which the server never sends, so shared editors saw a
read-only page (#4640 "The snippet page hid its edit controls from shared
editors"). Guards pin the client's unions and write levels to the
service's, the model's and access.py's own values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bvandeusen merged commit 6024230fa9 into main 2026-10-01 16:06:03 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledScribe#191