docs(6209): rulebooks, pull telemetry, lesson usage, duplicate arm, cited status, trigger shape
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m3s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 13s

Six more test files state their pinned properties without the issue and
milestone history: the pull-telemetry guard explains enumerating kinds
instead of listing two getter incidents, cited-record status explains why
a hint's id needs its status instead of recounting one misreported step,
and the trigger-shape guard keeps its reasoning about why prose heuristics
fail while dropping the dated corpus audit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 21:28:35 -04:00
co-authored by Claude Opus 5.5
parent ff08500128
commit 17e1931c0b
6 changed files with 91 additions and 112 deletions
+14 -21
View File
@@ -11,19 +11,12 @@ surfaced:pulled ratio is what makes dead weight visible and prunable. A getter
that opens a record without recording it leaves that kind permanently at zero
pulls, so it looks like dead weight beside kinds that merely had a counter.
That has now happened twice:
#2245 `get_task` recorded nothing while auto-inject surfaced mostly tasks.
Fixed by adding the call to notes, tasks and snippets.
#2476 `get_process` recorded nothing — and the auto-inject menu header names
`get_process` as the way to open that kind. Processes were embedded
when #2245 was fixed; the fix enumerated the kinds someone thought of
rather than the kinds that exist. The same issue found the REST half:
`rest_snippet` was recorded, note and task detail were not, and the
model's own comment named a `'rest_note'` source nothing wrote.
A fix that enumerates the kinds someone thought of, rather than the kinds that
exist, leaves the next kind's getter silent — and the REST detail views are a
second half that is easy to forget.
A missing call is the shape no behavioural test catches: it changes no return
value (#2278, shape 4). Source inspection is the only thing that sees it.
value. Source inspection is the only thing that sees it.
WHAT MAKES THIS DERIVED RATHER THAN A LIST
@@ -203,14 +196,14 @@ def test_every_named_loader_still_exists():
)
# ── the reading project travels with the event (#4196, #3735) ────────────────
# ── the reading project travels with the event ───────────────────────────────
#
# The same sibling-drift shape as above, one dimension over. `RetrievalLog` has
# always carried `project_id`, so "surfaced on project B" was answerable; the
# usage table had none, so "opened on project B" was not, and the two cannot be
# joined (no session identity server-side — NoteUsageEvent's own docstring).
# That gap sat exactly on milestone 385's acceptance: a lesson's claim is that
# it reaches a session on a project it was NOT written on.
# The same sibling-drift shape as above, one dimension over. `RetrievalLog`
# carries `project_id`, so "surfaced on project B" is answerable; without it on
# the usage row, "opened on project B" is not, and the two cannot be joined (no
# session identity server-side — NoteUsageEvent's own docstring). That is
# exactly a lesson's claim: it reaches a session on a project it was NOT
# written on.
#
# A getter that takes the project and forgets to pass it fails the same way a
# missing record_pulled did: silently, changing no return value, and showing up
@@ -246,7 +239,7 @@ def _takes_reading_project(body: str) -> bool:
def test_every_getter_that_pulls_takes_the_reading_project():
"""Asserted on structure (rule 167): a behavioural test cannot see a
"""Asserted on structure: a behavioural test cannot see a
parameter that was never threaded through."""
missing = [
f"{module}.{name}"
@@ -277,7 +270,7 @@ def test_every_getter_that_pulls_passes_the_project_through():
# The arms are the other half: unlike a getter, a surfacing arm ALWAYS knows
# the project — it is the scope it just searched — so there is no excuse for a
# null there, and `lesson_slot` is the one milestone 385 is measured through.
# null there, and `lesson_slot` is the one a lesson's transfer is measured through.
#
# `process_skill_sync` is exempt and named here rather than pattern-matched: it
# installs every Process the operator can reach, which is not a project-scoped
@@ -310,7 +303,7 @@ def test_every_project_scoped_surfacing_arm_records_its_project():
def test_the_reading_project_guards_can_fail():
"""Rule 167: shown turning red once, so a guard that has quietly stopped
"""Shown turning red once, so a guard that has quietly stopped
matching anything is distinguishable from one that passes."""
assert list(_pulling_getters()), "the pulling-getter detection matches nothing"
fake = "async def get_thing(thing_id: int) -> dict:\n record_pulled(x)"