test(410): an exactly-one-owner guard replaces the tests that required every surface to repeat itself (#4033)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 56s
CI & Build / Python tests (push) Successful in 1m30s
CI & Build / Build & push image (push) Successful in 15s

Step 6 of milestone 410 "One owner per piece of guidance". CI now keeps the
shape decision #4027 set, so the next feature cannot quietly add a copy.

tests/test_guidance_ownership.py, 33 topics, each with an owner, markers, a
statement distinctive to the owner's full wording, and optional index markers:
- test_every_topic_is_stated_by_its_owner: markers and statement on the owner
- test_no_topic_is_stated_in_full_off_its_owner: the statement appears on no
  other session surface (index, adapter static text and commands, live
  context, other skills). Tool docstrings are not scanned; a contract may
  elaborate the reflex that calls it.
- test_the_index_names_each_reflex_it_points_at: _INSTRUCTIONS keeps a
  one-line pointer for each session-start reflex
- shared_with declares the one deliberate sharing: the note-check question
  lives in create_note and in using-scribe for two different moments,
  already pinned by test_verification_guidance_survives
- test_the_ownership_guards_can_fail shows each guard turning red (rule 167)
- the process topic now keys on get_process's "follow the returned body";
  it had been passing on an unrelated "verbatim" in two other tools

tests/test_instruction_surfaces_agree.py keeps only what ownership cannot
enforce: the fold budget, rules-bind-names-preferences, and using-scribe's
pointer to reporting-back. Retired: the every-session-start-surface ask and
absence tests, the Systems and snippet owner pins (now registry topics), and
the SessionStart-without-ask test (#2497's shape). The push has carried no
rules since milestone 394, and requiring the ask beside every mention of it
would force a copy. The module docstring records where each protection went.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-14 13:29:34 -04:00
co-authored by Claude Opus 5
parent 15621fa873
commit c440c49f5b
2 changed files with 203 additions and 297 deletions
+176 -109
View File
@@ -1,4 +1,4 @@
"""Every piece of agent guidance has one owner — and none of it is lost on the way there.
"""Every piece of agent guidance has exactly one owner that states it.
WHY THIS EXISTS (milestone 410, decision #4027)
@@ -8,29 +8,36 @@ the server builds, the `using-scribe` skill, and tool docstrings. An earlier
design (#2494) made that deliberate — insurance against any one surface failing
silently — and the copies drifted apart instead (#2497, #4022).
Decision #4027 replaced the redundancy with ownership: the server orients, the
skills hold the depth, and each client adapter holds only its own timing and
conventions. This module is the registry that decision is enforced from.
Decision #4027 replaced the redundancy with ownership: the server orients with
a short index, the skills hold the depth, tool docstrings hold each tool's
contract, and each client adapter holds only its own timing and conventions.
This module is the registry that decision is enforced from.
WHAT IT PINS NOW, AND WHAT COMES LATER
WHAT IT PINS
Step 1 — the LOSS GUARD. Every topic below must still be stated on at least one
surface a session actually receives. Milestone 410's later steps delete copies;
this is what stops the last copy of a topic going with them.
1. OWNERS STATE THEIR TOPIC. Every topic's markers and its distinctive
statement appear on its owner — so moving or trimming text cannot drop a
topic without failing here.
2. NOWHERE ELSE STATES IT IN FULL. The statement — a phrase distinctive to
the owner's full wording — must not appear on any other session surface
(the index, the adapter's static text and commands, the live context, the
other skills). A topic legitimately stated in two places at two different
moments declares that in `shared_with`, with the reason beside it.
3. THE INDEX NAMES THE SESSION-START REFLEXES. `_INSTRUCTIONS` is the one
surface every MCP client receives, so each reflex it indexes keeps its
`index` markers there — a one-line pointer, not a copy.
Step 6 will add the OWNERSHIP guard: a topic's full statement on its `owner`
and nowhere else. The `owner` column is recorded here from the start so the
registry is written once, but nothing asserts it yet — during the moves a
topic is legitimately in several places at once.
WHAT IT CANNOT SEE
MARKERS ARE PHRASES, NOT WORDS
A copy reworded so it no longer contains the statement phrase passes. The
guard catches the ordinary way duplication happens — pasting a paragraph
into a second surface — not a determined paraphrase. Tool docstrings are not
scanned for copies: a tool's contract may elaborate the reflex that calls it.
A topic is "stated" when ALL of its markers appear on one surface — so a
topic's markers must travel together, and a lone common word never counts
(BINDING_CLAIMS in test_instruction_surfaces_agree explains the false alarm a
bare word raises). Tool names are the preferred marker: they change only when
the tool does. Reword a topic deliberately and update its markers in the same
commit; the failure message names which marker went missing where.
MARKERS AND STATEMENTS ARE PHRASES, NOT WORDS
Reword a topic deliberately and update its markers/statement in the same
commit; each failure message names the topic, the surface and the phrase.
"""
from __future__ import annotations
@@ -42,7 +49,7 @@ ROOT = pathlib.Path(__file__).resolve().parents[1]
def _norm(text: str) -> str:
# Whitespace-flattened and lowercased: prose is hard-wrapped, so a marker
# Whitespace-flattened and lowercased: prose is hard-wrapped, so a phrase
# can straddle a line break without the guidance having changed.
return " ".join(text.split()).lower()
@@ -62,7 +69,7 @@ def _live_session_context_source() -> str:
def delivered_surfaces() -> dict[str, str]:
"""Every surface a session receives as guidance, by label.
The one definition of "delivered" for this module and its successors:
The one definition of "delivered" for this module:
- `instructions` — the MCP server's `_INSTRUCTIONS` (every MCP client)
- `docstrings` — the MCP tool modules (tool descriptions, every client)
- `skill:<name>` — each bundled Agent Skill
@@ -85,99 +92,155 @@ def delivered_surfaces() -> dict[str, str]:
return {label: _norm(text) for label, text in surfaces.items()}
# Not scanned for copies (see the module docstring).
NOT_COPY_SCANNED = frozenset({"docstrings"})
class Topic(NamedTuple):
key: str
owner: str # a label from delivered_surfaces(); asserted from step 6
markers: tuple[str, ...]
owner: str # a label from delivered_surfaces()
markers: tuple[str, ...] # what the topic is about; all on the owner
statement: str # distinctive to the owner's full wording
index: tuple[str, ...] = () # required in _INSTRUCTIONS, if it indexes this
shared_with: tuple[str, ...] = () # other surfaces allowed the statement, with a reason
# The ownership map from milestone 410's body, one row per topic, plus the
# topics once guarded as "displaced from _INSTRUCTIONS" (#2562), folded in so
# there is one list. Retired topics (the surface-precedence tiebreaker) are
# absent on purpose: nothing has to keep saying them.
U = "skill:using-scribe"
TOPICS: tuple[Topic, ...] = (
# ── the working reflexes — owned by the using-scribe skill ──
Topic("scribe is the system of record; keep one copy", "skill:using-scribe", ("one copy",)),
Topic("orient: enter the project, check repo bindings", "skill:using-scribe",
("enter_project", "list_repo_bindings")),
Topic("rules are retrieved; ask before a consequential act", "skill:using-scribe",
('content_type="rule"', "nothing matched")),
Topic("rules bind, preferences guide and are kept current", "skill:using-scribe",
("preference", "update_preference")),
Topic("recall before acting", "skill:using-scribe", ("recall before acting",)),
Topic("stay inside the active project's scope", "skill:using-scribe",
("stay inside the active project", "cross-project")),
Topic("record as you go; honest status; fixes are issues", "skill:using-scribe",
("add_task_log", "in_progress", 'kind="issue"')),
Topic("an id exists only once a create returns it", "skill:using-scribe",
("exists only once a create", "{{ref:")),
Topic("tag records to systems as you write", "skill:using-scribe", ("system_ids", "create_system")),
Topic("answer the systems_hint at the moment of work", "skill:using-scribe", ("systems_hint",)),
Topic("a retrieved rule outranks a default habit", "skill:using-scribe", ("outranks a default habit",)),
Topic("log on completion and on a problem", "skill:using-scribe", ("hit or discover a problem",)),
Topic("the project's design system binds ui", "skill:using-scribe", ("resolve_design_system",)),
Topic("name the record, never just its number", "skill:using-scribe", ("name the record",)),
Topic("project inception is a decision", "skill:using-scribe", ("decide_project_inception",)),
Topic("where a new rule goes, and its trigger", "skill:using-scribe",
("create_project_rule", "when_to_apply")),
Topic("a rule vs the other entities", "skill:using-scribe", ("standing instruction",)),
Topic("reference notes update in place; dev-logs don't", "skill:using-scribe", ("reference note",)),
Topic("scribe is the system of record; keep one copy", U, ("one copy",),
"let any existing local memory shrink", index=("one copy",)),
Topic("orient: enter the project, check repo bindings", U, ("enter_project", "list_repo_bindings"),
"returns the project plus the rules bound to the areas it works in",
index=("enter_project",)),
Topic("rules are retrieved; ask before a consequential act", U, ('content_type="rule"', "nothing matched"),
"an empty session is not evidence of an empty rulebook",
index=('content_type="rule"', "nothing matched")),
Topic("rules bind, preferences guide and are kept current", U, ("preference", "update_preference"),
"a preference is the one record you keep current yourself", index=("preferences guide",)),
Topic("recall before acting", U, ("recall before acting",), "for related prior work", index=("recall",)),
Topic("stay inside the active project's scope", U, ("stay inside the active project", "cross-project"),
"stay inside the active project's scope", index=("project_id",)),
Topic("record as you go; honest status; fixes are issues", U, ("add_task_log", "in_progress", 'kind="issue"'),
"fixes are issues, not work-logs", index=("add_task_log", 'kind="issue"')),
Topic("an id exists only once a create returns it", U, ("exists only once a create", "{{ref:"),
"exists only once a create call returns it", index=("create_records", "{{ref:n}}")),
Topic("tag records to systems as you write", U, ("system_ids", "create_system"),
"would someone investigating that subsystem want this record", index=("system_ids",)),
Topic("answer the systems_hint at the moment of work", U, ("systems_hint",),
"treat it as the tagging question asked at the moment of work"),
Topic("a retrieved rule outranks a default habit", U, ("outranks a default habit",),
"a retrieved rule outranks a default habit"),
Topic("log on completion and on a problem", U, ("hit or discover a problem",), "hit or discover a problem"),
Topic("the project's design system binds ui", U, ("resolve_design_system",),
"building ui: the project's design system binds", index=("resolve_design_system",)),
Topic("name the record, never just its number", U, ("name the record",),
"a bare id reads as complete to you and as homework to them"),
Topic("project inception is a decision", U, ("decide_project_inception",),
"starting a project: decide what it inherits", index=("decide_project_inception",)),
Topic("where a new rule goes, and its trigger", U, ("create_project_rule", "when_to_apply"),
"whichever home it gets"),
Topic("a rule vs the other entities", U, ("standing instruction",), "first ask whether it's a rule at all"),
Topic("reference notes update in place; dev-logs don't", U, ("reference note",),
"state updates in place; chronicles don't"),
# ── process arcs — owned by their skills ──
Topic("plan in a milestone, steps created together", "skill:writing-plans", ("start_planning", "{{ref:")),
Topic("plan in a milestone, steps created together", "skill:writing-plans", ("start_planning", "{{ref:"),
"a milestone earns its place when the work has an arc", index=("start_planning",)),
Topic("reuse recorded shapes; record at first build", "skill:reusing-code",
("create_snippet", "when_to_use", "first build", "second copy")),
Topic("report back where the work stands", "skill:reporting-back", ("reporting-back", "placement")),
("create_snippet", "when_to_use", "first build", "second copy"),
"prior art offered beside a write is not noise", index=("create_snippet",)),
Topic("report back where the work stands", "skill:reporting-back", ("reporting-back", "placement"),
"take the placement from the record", index=("placement",)),
# ── per-tool contracts and in-band behaviour — owned by the server ──
Topic("closing a task cues the report", "docstrings", ("report_back",)),
Topic("a note that asserts a fact carries its check", "docstrings", ("verify_with", "expires_when")),
Topic("supersession demotes, never hides", "docstrings", ("supersedes",)),
Topic("deletes are recoverable from the trash", "docstrings", ("deleted_batch_id",)),
Topic("creates are duplicate-gated", "docstrings", ("near-duplicate",)),
Topic("shared records are another user's suggestion", "docstrings", ("shared: true",)),
Topic("stored processes are followed verbatim", "docstrings", ("stored processes", "verbatim")),
Topic("a project is never guessed", "docstrings", ("never guessing a project",)),
Topic("an unbound repo gets a bind hint", "live", ("bind_repo",)),
Topic("closing a task cues the report", "docstrings", ("report_back",), "reporting this to the operator?"),
Topic("a note that asserts a fact carries its check", "docstrings", ("verify_with", "expires_when"),
"could this note become false without anyone editing it",
# Stated at two different moments on purpose: the tool contract is
# read when the field is about to be filled, the skill while deciding
# what to write at all (test_verification_guidance_survives pins both).
shared_with=(U,)),
Topic("supersession demotes, never hides", "docstrings", ("supersedes",),
"it simply stops competing with this one"),
Topic("deletes are recoverable from the trash", "docstrings", ("deleted_batch_id",), "deleted_batch_id"),
Topic("creates are duplicate-gated", "docstrings", ("near-duplicate",), "bypass the near-duplicate gate",
index=("duplicate-gated",)),
Topic("shared records are another user's suggestion", "docstrings", ("shared: true",),
"belongs to another user", index=("shared:true",)),
Topic("stored processes are followed as written", "docstrings", ("get_process",), "follow the returned body"),
Topic("a project is never guessed", "docstrings", ("never guessing a project",), "never guessing a project"),
Topic("an unbound repo gets a bind hint", "live", ("bind_repo",), "isn't mapped to a scribe project"),
# ── the Claude Code adapter's own conventions ──
Topic("compact at clean seams", "static", ("/compact",)),
Topic("stored processes sync into local skills", "commands", ("scribe-proc-",)),
Topic("say so when scribe's tools are unavailable", "static", ("tools are unavailable",)),
Topic("compact at clean seams", "static", ("/compact",), "compact at clean seams"),
Topic("stored processes sync into local skills", "commands", ("scribe-proc-",), "regenerate the local skill stubs"),
Topic("say so when scribe's tools are unavailable", "static", ("tools are unavailable",),
"rather than silently falling back to local notes"),
)
def missing_topics(topics, surfaces: dict[str, str]) -> list[str]:
"""Topics no single surface states in full — with the nearest miss named.
Pure, so the guard's ability to fail is itself testable.
"""
missing = []
for topic in topics:
markers = [m.lower() for m in topic.markers]
if any(all(m in text for m in markers) for text in surfaces.values()):
continue
partial = {
label: [m for m in markers if m not in text]
for label, text in surfaces.items()
if any(m in text for m in markers)
}
missing.append(f"{topic.key!r} — markers {topic.markers}; nearest: {partial or 'nowhere'}")
return missing
def owner_gaps(topics, surfaces: dict[str, str]) -> list[str]:
"""Topics whose owner no longer carries every marker and the statement."""
gaps = []
for t in topics:
text = surfaces.get(t.owner, "")
absent = [p for p in (*t.markers, t.statement) if p.lower() not in text]
if absent:
elsewhere = [label for label, other in surfaces.items() if t.statement.lower() in other]
gaps.append(f"{t.key!r} on {t.owner}: missing {absent}; statement found on {elsewhere or 'nothing'}")
return gaps
def test_no_guidance_topic_has_fallen_off_every_surface():
missing = missing_topics(TOPICS, delivered_surfaces())
assert not missing, (
"these guidance topics are no longer stated in full on ANY delivered "
"surface:\n " + "\n ".join(missing) + "\nMilestone 410 moves guidance "
"to one owner per topic (decision #4027); a move that deletes a copy "
"must leave the topic stated on its owner. If the topic was reworded on "
"purpose, update its markers here in the same commit."
def copies(topics, surfaces: dict[str, str]) -> list[str]:
"""Topics whose full statement also appears on a surface that doesn't own it."""
found = []
for t in topics:
allowed = {t.owner, *t.shared_with}
extra = [label for label, text in surfaces.items()
if label not in NOT_COPY_SCANNED and label not in allowed and t.statement.lower() in text]
if extra:
found.append(f"{t.key!r} (owner {t.owner}) is also stated on {extra}")
return found
def index_gaps(topics, surfaces: dict[str, str]) -> list[str]:
text = surfaces.get("instructions", "")
return [f"{t.key!r}: {[m for m in t.index if m.lower() not in text]}"
for t in topics if any(m.lower() not in text for m in t.index)]
def test_every_topic_is_stated_by_its_owner():
gaps = owner_gaps(TOPICS, delivered_surfaces())
assert not gaps, (
"these topics are no longer stated in full by their owner:\n "
+ "\n ".join(gaps) + "\nEvery guidance topic has one owner (decision "
"#4027). If the text moved on purpose, move the topic's owner here in the "
"same commit; if it was reworded, update its markers and statement."
)
def test_every_owner_is_a_surface_that_exists():
def test_no_topic_is_stated_in_full_off_its_owner():
found = copies(TOPICS, delivered_surfaces())
assert not found, (
"guidance stated in full on a surface that doesn't own it:\n "
+ "\n ".join(found) + "\nOne owner per topic (decision #4027): replace "
"the copy with a one-line pointer to the owner. If both places genuinely "
"need it at different moments, declare it in `shared_with` with the reason."
)
def test_the_index_names_each_reflex_it_points_at():
gaps = index_gaps(TOPICS, delivered_surfaces())
assert not gaps, (
f"_INSTRUCTIONS no longer carries the index line for: {gaps}. It is the "
f"one guidance surface every MCP client receives; keep a one-line pointer "
f"per session-start reflex (see the comment above _INSTRUCTIONS)."
)
def test_every_owner_and_sharer_is_a_surface_that_exists():
labels = set(delivered_surfaces())
unknown = [(t.key, t.owner) for t in TOPICS if t.owner not in labels]
assert not unknown, f"owners that name no delivered surface: {unknown}"
unknown = [(t.key, s) for t in TOPICS for s in (t.owner, *t.shared_with) if s not in labels]
assert not unknown, f"owners or sharers that name no delivered surface: {unknown}"
def test_topic_keys_are_unique():
@@ -185,20 +248,24 @@ def test_topic_keys_are_unique():
assert len(keys) == len(set(keys))
def test_the_loss_guard_can_fail():
"""Rule 167: a guard that cannot fail protects nothing.
A topic whose markers are split across two surfaces is NOT stated — the
phrases have to travel together — and one whose marker is nowhere is
reported with 'nowhere'.
"""
surfaces = {"a": "enter_project here", "b": "list_repo_bindings there"}
split = Topic("split", "a", ("enter_project", "list_repo_bindings"))
absent = Topic("absent", "a", ("no such phrase",))
whole = Topic("whole", "a", ("enter_project",))
reported = missing_topics((split, absent, whole), surfaces)
assert len(reported) == 2
assert reported[0].startswith("'split'") and "nowhere" in reported[1]
def test_the_ownership_guards_can_fail():
"""Rule 167: each guard is shown turning red once."""
surfaces = {
"instructions": "use the widget",
"docstrings": "the widget owner statement lives here",
"skill:a": "widget owner statement lives here, and widget tool",
"skill:b": "a pasted copy: widget owner statement lives here",
"static": "",
}
topic = Topic("widget", "skill:a", ("widget tool",), "widget owner statement lives here",
index=("use the widget", "not in the index"))
moved = topic._replace(owner="static")
assert owner_gaps((topic,), surfaces) == []
assert owner_gaps((moved,), surfaces) and "missing" in owner_gaps((moved,), surfaces)[0]
# skill:b pasted it; docstrings are not scanned for copies.
assert copies((topic,), surfaces) == ["'widget' (owner skill:a) is also stated on ['skill:b']"]
assert copies((topic._replace(shared_with=("skill:b",)),), surfaces) == []
assert index_gaps((topic,), surfaces) == ["'widget': ['not in the index']"]
# ── The skills are client-neutral (milestone 410 step 2) ────────────────
+27 -188
View File
@@ -1,39 +1,35 @@
"""The instruction surfaces must agree on how a rule reaches a session.
"""What the instruction surfaces must say that one owner per topic cannot enforce.
WHY THIS EXISTS
WHY THIS FILE IS SMALLER THAN IT WAS
Rule #119 makes the instruction surfaces the SPECIFICATION for product
behaviour — there is no other place the "load the operator's rules" obligation
is written down, and no code path enforces it. So a surface that states it
differently isn't a documentation slip; it is the product behaving differently.
Until milestone 410 this file pinned REDUNDANCY: every session-start surface —
the MCP `_INSTRUCTIONS`, the plugin's static context, the `using-scribe` skill —
had to restate how a rule reaches a session, what an empty session means, the
Systems reflex and the snippet-recording triggers. That was the design #2494
chose after #2497 (two surfaces disagreeing about who loads the rules) and
#2198 (every hook silently inert): say it everywhere, so no single failure
loses it.
That happened (#2497). `_INSTRUCTIONS` said the SessionStart hook "is the
bridge" for getting rules into a session, while the `using-scribe` skill said to
pull them yourself and treat any push as a bonus. An agent weighting the first
would reasonably skip the pull.
Decision #4027 replaced that with ownership, and tests/test_guidance_ownership.py
now enforces it: every topic stated in full by exactly one owner, with a
one-line pointer in the server's index. The tests that required every surface
to repeat itself were retired there, because they enforced the very duplication
that drifted (#4022). What they protected is still protected:
- "state how to ask for a rule" / "say an empty session is not an empty
rulebook" → the rules topic: owned by using-scribe, indexed in
`_INSTRUCTIONS` with both phrases;
- the Systems reflex and the snippet-recording triggers → their topics'
markers, required on their owners;
- "never name the SessionStart push without stating the ask" (#2497's exact
shape) → retired outright. Since milestone 394 the push carries no rules,
so naming it can no longer imply rules were handled, and requiring every
surface that mentions it to restate the ask would force a copy.
#2198 is the case where that is wrong: every plugin hook was silently inert for
an extended period, and nothing announced it. An agent trusting the push would
have run with no binding rules and no signal — while those rules govern branch,
commit, push and other hard-to-reverse actions.
WHAT STAYS HERE
The asymmetry is the whole argument, and it is what these tests pin: asking
when a rule had already arrived costs one redundant call; not asking when
nothing arrived costs the operator's rules entirely.
MILESTONE 394 SHARPENED IT RATHER THAN RETIRING IT. There is no longer a
resident set to pull, so "no rule in front of me" went from a rare and
suspicious state to the ordinary state of most turns. The instruction that
used to be supplementary — go and ask — is now the only route a rule has, and
the surfaces must additionally say what an EMPTY session means, or a session
reads silence as permission on nearly every turn.
WHAT THIS DOES NOT DO
It cannot tell whether two surfaces contradict each other in prose generally —
that needs a reader. It pins the instructions whose absence is known to be
load-bearing, and the specific shape the #2497 defect took: naming the push
without also stating how to ask.
Properties of a CLAIM rather than of who owns a topic: the index fits the
fold; a surface that says rules bind also names what does not; using-scribe
still sends a session to the reporting-back skill.
"""
from __future__ import annotations
@@ -41,50 +37,6 @@ import pathlib
ROOT = pathlib.Path(__file__).resolve().parents[1]
# THE PULL IS NOW THE ASK (milestone 394). This was `list_always_on_rules`,
# the call that fetched the resident set. There is no resident set and no such
# call: a rule reaches a session by retrieval, and the only thing a session can
# DO about a rule it has not been handed is go looking for one.
#
# So the two halves this file used to pin separately — "pull the resident set"
# and "and retrieve the conditional ones too" — have collapsed into one
# instruction, and it is the load-bearing one rather than the supplementary
# one it used to be.
ASK = 'content_type="rule"'
# A surface must also say what an EMPTY session means, which is the half that
# is newly dangerous. Under residency, "no rule in front of me" was rare and
# suspicious. Under retrieval it is the ordinary state of most turns, so a
# session that reads it as "there is no rule" is wrong on nearly every turn
# rather than occasionally — the #3720 defect at session scale.
#
# Claim phrases, not a single word, for the reason BINDING_CLAIMS gives below:
# a bare "matched" or "silence" appears in prose that is not making this claim
# at all. A surface passes by asserting the distinction however it words it.
ABSENCE_CLAIMS = (
"nothing matched",
"is not the same as \"there is no rule",
"never \"there is no rule",
"silence is not absence",
"not evidence there is none",
)
# The surfaces that STATE the rules reflex. Hand-written because "who owns
# this?" is an editorial fact, not a derivable one — but each entry is asserted
# to EXIST, so a move or rename fails loudly here instead of quietly dropping
# that surface from the check.
#
# Since milestone 410 (decision #4027) that is the owner and the index, not
# every surface a session loads: `using-scribe` states the reflex in full and
# the server's `_INSTRUCTIONS` gives it one line for every MCP client. The
# Claude Code adapter's static context used to be a third copy; it now points
# at the skill instead, and tests/test_guidance_ownership.py keeps the topic
# from falling off.
SESSION_START_SURFACES = (
ROOT / "src" / "scribe" / "mcp" / "server.py",
ROOT / "plugin" / "skills" / "using-scribe" / "SKILL.md",
)
def _all_surfaces() -> list[tuple[str, str]]:
"""(label, text) for every file a SESSION loads as instructions.
@@ -105,54 +57,6 @@ def _all_surfaces() -> list[tuple[str, str]]:
return found
def test_every_session_start_surface_states_the_ask():
"""Retrieval is the only delivery, so asking is the only recourse."""
missing = []
for path in SESSION_START_SURFACES:
assert path.exists(), (
f"{path.relative_to(ROOT)} is gone — it was one of the surfaces "
f"carrying the rules instruction. If it moved, update "
f"SESSION_START_SURFACES; if it was retired, check the instruction "
f"still lives somewhere a fresh session reads."
)
if ASK not in path.read_text():
missing.append(str(path.relative_to(ROOT)))
assert not missing, (
f"these surfaces never tell the agent how to ask for a rule "
f"({ASK}): {missing}. Nothing is pushed and nothing is resident, so a "
f"surface that omits this leaves a session with no way to reach a rule "
f"it was not handed — bound by nothing (#2198, #2497, milestone 394)."
)
def test_every_session_start_surface_says_an_empty_session_is_not_an_empty_rulebook():
"""The half that got dangerous when residency went away.
Under the old model a session opened holding every applicable rule, so
"nothing is in front of me" was a rare state and a suspicious one. Under
retrieval it is the NORMAL state of most turns. A surface that describes
where rules come from, without also saying what their absence means, leaves
a session reading silence as permission — on nearly every turn rather than
occasionally.
That is #3720's defect ("absence reads as non-existence") moved from a
readout to the session itself, and this milestone is what makes every
session start in the absent state.
"""
missing = []
for path in SESSION_START_SURFACES:
text = path.read_text().lower()
if not any(c.lower() in text for c in ABSENCE_CLAIMS):
missing.append(str(path.relative_to(ROOT)))
assert not missing, (
f"these surfaces say how a rule arrives but never what it means when "
f"none does: {missing}. 'No rule arrived' means 'nothing matched', "
f"never 'there is no rule' — and only one of those has been checked. "
f"Say it however you like; one of {ABSENCE_CLAIMS} is what this looks "
f"for."
)
def _instructions_text() -> str:
"""The _INSTRUCTIONS literal from server.py, as the client would see it."""
import re
@@ -180,71 +84,6 @@ def test_instructions_fit_the_fold():
)
def test_the_systems_reflex_is_stated_by_its_owner():
"""Write-time tagging guidance must be stated as a reflex, not only per tool.
#2562's behavioral finding: with the guidance only in tool descriptions,
sessions filed records untagged. The tag-as-you-write reflex was pinned on
the static context then; since milestone 410 its owner is using-scribe
(decision #4027), with the in-band `systems_hint` as the half that fires on
its own.
"""
owner = (ROOT / "plugin" / "skills" / "using-scribe" / "SKILL.md").read_text()
for needle in ("system_ids", "create_system", "systems_hint"):
assert needle in owner, (
f"plugin/skills/using-scribe/SKILL.md no longer mentions "
f"{needle} — the Systems tagging reflex must be stated by its "
f"owner, not only in tool descriptions (#2562)."
)
def test_the_snippet_recording_triggers_are_stated_by_their_owner():
"""The pattern-library recording model must be stated, by name.
#2664's behavioral finding: recording guidance as a trailing clause of the
reuse bullet converted zero times outside snippet-minded sessions. The
2026-08-16 ruling (decision #2686) then replaced the reactive model
entirely: every shape is recorded at FIRST build — no "will it recur?"
judgment — and second-copy consolidation is only the backstop. All three
elements must stay stated: the tool, the first-build trigger, and the
backstop. Since milestone 410 the owner is the reusing-code skill.
"""
owner = " ".join((ROOT / "plugin" / "skills" / "reusing-code" / "SKILL.md").read_text().split())
for needle in ("create_snippet", "first build", "second copy"):
assert needle in owner, (
f"plugin/skills/reusing-code/SKILL.md no longer states the "
f"snippet-recording model ({needle!r}) — record-every-shape-at-"
f"first-build with second-copy consolidation as the backstop must "
f"be stated by its owner (#2664, decision #2686)."
)
# The "displaced from _INSTRUCTIONS" topics (#2562) used to be listed here with
# their own delivered-surface check. They are folded into the one topic registry
# in tests/test_guidance_ownership.py (milestone 410), which covers every topic
# of the ownership map and defines "delivered surface" once.
def test_no_surface_names_the_push_without_stating_the_ask():
"""The exact shape #2497 took.
Mentioning the SessionStart hook is fine and often useful. Mentioning it
*instead of* the pull is the defect: it reads as "this is handled", and the
surface that says so is the one an agent has least reason to doubt.
"""
offenders = [
label for label, text in _all_surfaces()
if "SessionStart" in text and ASK not in text
]
assert not offenders, (
f"these surfaces describe the SessionStart push but never state how to "
f"ask: {offenders}. The push is a delivery optimisation, not the "
f"bridge — it can be absent without saying so, and since milestone 394 "
f"it carries no rules at all. Name it if it helps, but say how to ask "
f"({ASK}) regardless."
)
# ── force: a surface that says rules bind must say what does not ────────
#
# Added with the preference kind (milestone 399). Before it, "rules bind" was