docs(6209): test prose in the present tense — "used to", "previously", "before this" restated as the failure shape

Twenty-eight test files described the code they guard by what it did
before a fix. Each is now the property and the failure it prevents, in the
present tense: the backfill snapshot, the per-dial calibration query, the
rule scope, the lesson trigger filter, the subshell global, the trash owner
scope, the version readout fallback, and others. A docstring line that
narrated the backup suite's own history is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 21:46:57 -04:00
co-authored by Claude Opus 5.5
parent 914f31359c
commit 4c150cbc2f
28 changed files with 96 additions and 105 deletions
+7 -7
View File
@@ -1,12 +1,12 @@
"""A backfill repairs to current truth; it does not replay a snapshot.
The bug these pin: every backfill used to SELECT the text alongside the id and
then iterate that snapshot, sleeping between records. A run over a real corpus
takes minutes, so anything edited inside that window was re-embedded by its own
write path and then OVERWRITTEN with the pre-edit text the scan had captured —
and stamped at the current chunker version, so the next boot considered it
current and never repaired it. The record stayed findable by what it used to
say, silently, until someone happened to edit it again.
The failure these pin: a backfill that SELECTs the text alongside the id and
then iterates that snapshot, sleeping between records. A run over a real corpus
takes minutes, so anything edited inside that window is re-embedded by its own
write path and then OVERWRITTEN with the pre-edit text the scan captured — and
stamped at the current chunker version, so the next boot considers it current
and never repairs it. The record stays findable by what it used to say,
silently, until someone happens to edit it again.
That window is exactly "forward work continues while a long backfill runs",
which is the normal condition, not an unlucky one.
+2 -2
View File
@@ -195,8 +195,8 @@ async def test_a_dial_is_read_per_dial_not_from_the_newest_two_rows():
"The newest two rows" and "the newest row of each dial" differ the moment
one dial moves more often than the other — which is the normal case, since
floors get walked and budgets rarely do. Before this was one query per dial,
a surface with three floor changes and one budget change reported the budget
floors get walked and budgets rarely do. Without a query per dial, a
surface with three floor changes and one budget change reports the budget
as untouched: a WRONG calibration answer rather than a missing one, and
wrong in the direction a reader would not think to check.
"""
+1 -1
View File
@@ -171,7 +171,7 @@ def _assert_nothing_thin_and_nothing_lost(title: str, body: str) -> list[str]:
def test_a_heading_over_one_long_paragraph_is_not_a_chunk_of_its_own():
"""A work log whose entry is one long paragraph: the split used to flush
"""A work log whose entry is one long paragraph: the split must not flush
the heading as its own piece before cutting the paragraph."""
entry = " ".join(["The deploy was verified against the live instance."] * 40)
_assert_nothing_thin_and_nothing_lost(
+4 -4
View File
@@ -8,10 +8,10 @@ choice is the only moment a correction is cheap. The tool docstring IS the
agent-facing contract (product guidance lives there and nowhere else), so a docstring that only documents parameters answers "how do I call
this" while leaving "should I be calling this at all" unasked.
The gap was lopsided before this: `create_rule` and `start_planning` both
carried a real disambiguator, and `create_note` — far and away the
highest-volume surface — carried none. Guidance sat in the rarest tool and
was missing from the most common one.
The gap tends to be lopsided: `create_rule` and `start_planning` are where a
disambiguator gets written first, while `create_note` — far and away the
highest-volume surface — is where it matters most. Guidance that sits only in
the rarest tools misses the most common one.
WHAT THIS PINS, AND WHAT IT DOES NOT
-1
View File
@@ -14,7 +14,6 @@ resolves is indistinguishable from work still in progress. The practice names
clients, a bare `await` on a stream — supplying the deadline is part of
using it, not a hardening pass for later.
Before this guard, no request in the app carried one.
"""
from __future__ import annotations
@@ -29,7 +29,7 @@ RESTORED_USERNAME = "backup_roundtrip_restored"
@pytest_asyncio.fixture
async def source():
"""A snippet, a process, and an issue that arose from a work task — the
shapes whose identity the backup used to drop — exported as backup rows.
shapes whose identity a backup can drop — exported as backup rows.
Deleted children-first: `arose_from_id` is a real FK, so removing the
origin while the issue still points at it is asking the database a
+3 -3
View File
@@ -1,8 +1,8 @@
"""Real-Postgres tests for WHERE a rule reaches.
A rule lives in a rulebook topic (global) or on one project. Retrieval used to
ignore that and search every rule the user owned, so each project's rules were
injected into every other project's sessions. What a mock cannot show is the
A rule lives in a rulebook topic (global) or on one project. Retrieval that
ignores the home and searches every rule the user owns injects each project's
rules into every other project's sessions. What a mock cannot show is the
join doing the scoping: these seed real rules with hand-made vectors and stub
only the embedder, so every rule is an equally good match and the home alone
decides what comes back.
+4 -4
View File
@@ -121,11 +121,11 @@ async def test_the_repeat_is_counted_not_reported_as_a_result():
@pytest.mark.asyncio
async def test_a_call_where_everything_was_already_seen_is_readable():
"""The fact that could not be expressed before.
"""A call where every hit was already seen is not a call nothing cleared.
Previously this call logged zero results with no suppression count, so it
was indistinguishable from a bar nothing cleared — and the operator tuning
that bar would have been reading the wrong number.
Logged as zero results with no suppression count, it would be
indistinguishable from a bar nothing cleared — and the operator tuning
that bar would be reading the wrong number.
"""
rec = MagicMock()
hits = [(0.80, fake_note(id=11, title="Pool sizing decision", user_id=1))]
+8 -8
View File
@@ -15,13 +15,13 @@ worth having wherever a record is stored composed and edited in parts: not
"does the strip remove the marker" — it did — but "does an edit that changes
nothing change nothing".
WHAT WENT WRONG. The derivation used to filter out body LINES matching the
trigger marker's pattern, which is `re.M` and therefore matches one line.
`when_to_apply` is free text and is routinely several paragraphs; the
composer writes all of it after the marker, so paragraphs 2..n were ordinary
lines that the filter had no reason to touch. They came back as "the
insight", the full trigger was prepended again, and the stored lesson gained
(n-1) paragraphs per update — silently, compounding, and in the half of the
WHAT GOES WRONG. A derivation that filters out body LINES matching the
trigger marker's pattern, which is `re.M` and therefore matches one line,
leaves the rest of the trigger behind. `when_to_apply` is free text and is
routinely several paragraphs; the composer writes all of it after the marker,
so paragraphs 2..n are ordinary lines that the filter has no reason to touch.
They come back as "the insight", the full trigger is prepended again, and the
stored lesson gains (n-1) paragraphs per update — silently, compounding, and in the half of the
document that does the retrieving.
On a three-paragraph trigger the body grows by the length of paragraphs 2 and
@@ -180,7 +180,7 @@ def test_the_insight_is_derived_from_the_row_not_from_the_body_alone():
"""Why the function takes a note.
The body on its own does not say where the trigger ends — that is the
information the old shape did not have and guessed at. The mirror in
information a line-wise filter does not have and guesses at. The mirror in
`data` is where it comes from, so a caller holding only the body string
cannot be served, and this pins that the argument is the record."""
note = _stored(MANY_PARAGRAPHS, INSIGHT, [])
+4 -5
View File
@@ -16,11 +16,10 @@ a real promise rather than a shape coincidence.
THE CONTRACT: a `limit` without an `offset` is a truncation with no
continuation. The caller is told there are 250 results and handed 50, with no
way to ask for the rest. Both tools that had this were capped over a service
that already accepted an offset — `snippets_svc.list_snippets(offset=0)` was
simply not exposed, and `list_processes` passed a hardcoded `offset=0` into
`query_knowledge`. The capability existed one layer down in both cases; only
the door was missing.
way to ask for the rest. The usual cause is a tool capped over a service that
already accepts an offset — the service's `offset` simply not exposed, or a
hardcoded `offset=0` passed into `query_knowledge`. The capability exists one
layer down; only the door is missing.
As in `test_mcp_auth`, the CANDIDATES are derived and the DECISION is explicit.
Deriving the exemption too would make the contract follow a naming convention,
+3 -3
View File
@@ -363,9 +363,9 @@ async def test_enter_project_fires_the_coverage_seed_on_the_owner(
@pytest.mark.asyncio
async def test_enter_project_hands_back_the_design_system_when_the_project_has_one():
"""The handshake is where an agent learns what binds it, and a design
system binds the same way a rule does. Before this it was reachable only by
an agent that already knew to call resolve_design_system — so the standards
were present in the store and absent from the work."""
system binds the same way a rule does. Reachable only by an agent that
already knows to call resolve_design_system, the standards would be present
in the store and absent from the work."""
p = fake_project(id=5, design_system_id=9)
design = {"id": 9, "title": "App kit", "guidance": [{"title": "House"}],
"token_count": 95, "token_groups": ["surface"],
+2 -2
View File
@@ -168,8 +168,8 @@ async def test_fable_search_content_type_filters_at_service_layer():
async def test_each_specific_kind_reaches_the_engine_filter_it_names(
content_type, expected
):
"""`note_type` and `task_kind` were parameters of the search all along —
what was missing was a way to ask for them from the agent's door. Asking
"""`note_type` and `task_kind` are parameters of the search, and the
agent's door passes them through. Asking
for a snippet must narrow to snippets, not merely to non-tasks."""
_user_id_ctx.set(7)
mock_search = AsyncMock(return_value=[])
+1 -1
View File
@@ -134,7 +134,7 @@ async def _enter(*stubs):
async def test_enter_project_stays_small_however_large_the_project():
"""The ceiling is the point: a long-lived project's handshake must not
grow with its history. 200 milestones with 5k-character plans, 60 project
rules and 40 Systems would be well over 1M characters in the old shape."""
rules and 40 Systems would be well over 1M characters carried whole."""
project = fake_project(id=5, design_system_id=9, goal=GOAL,
description="Background. " * 300)
rules = {
+2 -2
View File
@@ -2,8 +2,8 @@
Covers the payload shaping and the two contracts that make this safe to leave in
the hot path: telemetry never raises, and telemetry never blocks. Plus the thing
the feature exists for — that the write-path PLACE arm is now recorded, since
before this it surfaced snippets while leaving no trace anywhere.
the feature exists for — that the write-path PLACE arm is recorded, so a
snippet it surfaces leaves a trace.
"""
from unittest.mock import AsyncMock, MagicMock, patch
+1 -1
View File
@@ -160,7 +160,7 @@ async def test_update_preference_forwards_what_taught_the_change():
def test_the_rule_door_still_asks_before_writing():
"""Half one of the asymmetry. If this fails, the gate was tidied away and
preferences are no longer the exception — they are just the same thing.
preferences are not the exception — they are just the same thing.
"""
doc = _doc(MODULE, "create_rule").lower()
asks = ("approve", "propose", "ask", "question")
+2 -2
View File
@@ -70,8 +70,8 @@ def test_the_reply_carries_a_length_discipline():
def test_the_reply_gets_a_second_pass_for_what_can_go():
"""Cutting is a separate act from writing, and needs saying separately.
The pre-existing check asked whether anything was MISSING, which a bloated
reply passes.
A check that asks only whether anything is MISSING is one a bloated reply
passes.
"""
text = _flat()
assert "what can go" in text
+2 -3
View File
@@ -134,9 +134,8 @@ async def test_an_unknown_surface_is_refused_before_anything_is_written():
@pytest.mark.asyncio
@pytest.mark.parametrize("dial", ["threshold", "limit", "", "FLOOR"])
async def test_an_unknown_dial_is_refused(dial):
"""The near-misses are the old vocabulary — `threshold` and `limit` are what
these were called before this step, so they are exactly what a stale caller
will send, and a silent no-op there would be indistinguishable from a
"""The near-misses are the generic vocabulary — `threshold` and `limit` are
what a caller that has not read the dial names will send, and a silent no-op there would be indistinguishable from a
change that did not take."""
session, ctx = _patches()
with ctx[0], ctx[1], ctx[2], ctx[3], pytest.raises(ValueError):
+3 -3
View File
@@ -446,9 +446,9 @@ async def test_the_prompt_arm_retrieves_against_what_the_operator_SAID():
Both other arms are keyed on an act — a file write, a command. A rule that
governs what to SAY has no act in front of it: extract intent from loose
phrasing, raise a conflict before acting, end a finding with an offer all
bind on a response. Before this, the operator's message reached only
`semantic_search_notes`, so no rule had ever been retrieved against a
thing the operator actually said.
bind on a response. If the operator's message reached only
`semantic_search_notes`, no rule would ever be retrieved against a thing
the operator actually said.
"""
rec = MagicMock()
search = AsyncMock(return_value=[(0.79, fake_rule(
+6 -6
View File
@@ -119,13 +119,13 @@ def test_an_unusable_marker_is_refused_rather_than_sent(tmp_path, body):
def test_the_reason_survives_the_command_substitution_that_fetches_it(tmp_path):
"""Regression on a bug this nearly shipped with.
"""The reason cannot be a global the function sets.
The reason used to be a global the function set, which every caller reads
through `$( )` — a subshell, so the assignment died with it and the caller
saw an UNSET variable. The hooks all run `set -u`, where reading one aborts
the script: a directory with a misaddressed marker would have cost the
session its entire SessionStart context, to fetch a warning about a file.
Every caller reads the function through `$( )` — a subshell, so a global
assignment dies with it and the caller sees an UNSET variable. The hooks all
run `set -u`, where reading one aborts the script: a directory with a
misaddressed marker would cost the session its entire SessionStart context,
to fetch a warning about a file.
So the reason comes back through stdout with the id, and this asserts on
the shape the caller actually uses: read in a subshell, split, and used
+6 -6
View File
@@ -3,13 +3,13 @@
Ordered straight off a `*_embeddings` table, the planner walks the HNSW index:
it takes ~`hnsw.ef_search` (40) nearest chunks from every owner and project
and filters them afterwards, so an in-scope record behind 40 nearer ones the
caller cannot see is silently dropped. The fix is one shape, `_rank_scoped`,
and these pin that every search goes through it — the rule search was fixed
alone first, and the other three kept the fault because nothing said they
were the same query.
caller cannot see is silently dropped. The remedy is one shape, `_rank_scoped`,
and these pin that every search goes through it — fixed in one search alone,
the others keep the fault, because nothing says they are the same query.
The integration lane's crowd tests show the behaviour on a real index; these
are the guard that fails on the old shape whatever plan Postgres picks.
are the guard that fails on a rank-then-scope query whatever plan Postgres
picks.
"""
from __future__ import annotations
@@ -54,7 +54,7 @@ def test_every_semantic_search_ranks_through_the_scoped_shape():
calls = _calls(fn)
assert "_rank_scoped" in calls, f"{name} does not rank through _rank_scoped"
assert "_scoped_chunks" in calls, f"{name} does not build its scope as chunks"
# Its own ORDER BY is the old shape: a distance ordered on the
# Its own ORDER BY is the rank-then-scope shape: a distance ordered on the
# embedding table, which the index serves before the scope applies.
assert "order_by" not in calls, f"{name} orders by distance itself"
+11 -16
View File
@@ -7,9 +7,7 @@ need only a stand-in and a builder.
The full FK-remapping round-trip needs real Postgres and lives in the
`test_integration_backup_*_roundtrip.py` modules, which drive the real
`restore_full_backup`. That sentence used to read "it is not written yet";
four of those files exist now, the newest covering the three `code_shapes`
columns the import guard below found missing.
`restore_full_backup`.
"""
from datetime import datetime, timezone
from types import SimpleNamespace
@@ -268,18 +266,16 @@ def test_every_column_is_exported_or_declared_excluded(table):
"""THE COLUMN GUARD — _NOT_INCLUDED's shape, one level down.
The table guard below catches a whole table going missing. It cannot catch
a COLUMN going missing from a table it already considers covered, which is
how nine of them vanished from `notes` alone: note_type and task_kind, so
every snippet and process restored as a plain note and every issue and
spike as `work`; arose_from_id, so every provenance edge went; the
recurrence pair, so recurring tasks stopped recurring. Plus milestones.body
— which IS the plan — and repo_bindings.ref.
a COLUMN going missing from a table it already considers covered, and the
cost is large: without note_type and task_kind every snippet and process
restores as a plain note and every issue and spike as `work`; without
arose_from_id every provenance edge goes; without the recurrence pair
recurring tasks stop recurring.
Each arrived the same way: added to the model and the migration, both of
which fail loudly, and never to the serialiser, which fails silently.
A new column must now be exported or named in _COLUMN_EXCLUSIONS with a
reason. Forgetting is no longer expressible.
A column arrives in the model and the migration, both of which fail loudly,
and is easy to leave out of the serialiser, which fails silently. So a new
column must be exported or named in _COLUMN_EXCLUSIONS with a reason, and
forgetting is not expressible.
"""
model, helper = _column_guard_targets()[table]
[row] = helper([_stand_in(model)])
@@ -536,8 +532,7 @@ def test_every_table_is_either_backed_up_or_explicitly_excluded():
)
# And the reverse: a name in either list that no longer exists is a lie the
# guard would otherwise keep telling. This half is what caught "embeddings",
# "invitations" and "password_resets" — three entries that named nothing.
# guard would otherwise keep telling.
phantom = accounted - schema
assert not phantom, (
f"backup lists table(s) that are not in the schema: {sorted(phantom)}. "
+5 -6
View File
@@ -86,9 +86,8 @@ async def test_build_autoinject_hint_titles_only_with_margin_gate():
assert "get_note(id)" in out["context"]
assert "↳" not in out["context"]
# Telemetry fired for BOTH retrievals this path runs: the scored menu and
# the reuse-slot query competing against it. The slot's query used to be
# the one unlogged retrieval on this path — the hit it displaced was in
# retrieval_logs, and the query that displaced it must be too.
# the reuse-slot query competing against it. The hit the slot displaces is
# in retrieval_logs, so the query that displaced it must be too.
sources = [c.kwargs["source"] for c in rec.call_args_list]
assert sources == ["auto_inject", "reuse_slot", "lesson_slot"]
@@ -235,9 +234,9 @@ async def test_a_project_id_that_does_not_resolve_is_reported_not_dropped():
another instance, or names a deleted project, the session has to be told —
it cannot infer it from an absence.
This used to render nothing whatsoever: the branch hung off `if project_id`
as an `elif`, so an id that was sent and failed took the outer arm, found
no project, and fell out of the block having said nothing at all.
The trap is a branch hung off `if project_id` as an `elif`: an id that was
sent and failed takes the outer arm, finds no project, and falls out of the
block having said nothing at all.
"""
from scribe.services.plugin_context import build_session_context
with patch("scribe.services.plugin_context.projects_svc.get_project",
+4 -4
View File
@@ -929,10 +929,10 @@ async def test_a_section_is_complete_only_from_its_latest_contributor(
def test_a_call_that_returned_nothing_still_records_what_it_nearly_showed():
"""The whole point, at the payload grain.
This is the row a threshold is tuned from and the one that used to carry no
score at all: `top_score` and `min_score` are both null here, correctly, and
a reader was left unable to tell a bar rejecting 0.71s from one rejecting
0.30s. Both render as a zero-result call.
This is the row a threshold is tuned from: `top_score` and `min_score` are
both null here, correctly, and without the near-miss a reader cannot tell a
bar rejecting 0.71s from one rejecting 0.30s. Both render as a zero-result
call.
"""
p = _build_payload(
user_id=1, source="pre_tool_rule", query="git push --force",
+2 -2
View File
@@ -82,8 +82,8 @@ def test_parse_does_not_promote_a_caller_tag_to_language():
def test_caller_tag_survives_an_update_round_trip_without_a_language():
# Regression: the tag used to be read back as the language, then dropped
# from the extra-tag set on re-compose — so it silently disappeared.
# A tag read back as the language would be dropped from the extra-tag set
# on re-compose — and silently disappear.
tags = s.compose_tags("", ["auth"])
fields = s.parse_snippet_fields("n — u", s.compose_body(code="x = 1"), tags)
extra = [t for t in tags if t not in (s.SNIPPET_TAG, fields["language"])]
+2 -2
View File
@@ -124,8 +124,8 @@ async def test_purge_expired_deletes_across_models_when_positive():
def test_owner_clause_scopes_every_model():
"""Regression: every trash op must owner-scope, including topics/rules
which previously had NO owner check (IDOR across tenants)."""
"""Every trash op must owner-scope, topics and rules included — a model
with no owner check is an IDOR across tenants."""
from scribe.services.trash import _owner_clause, _MODEL_FOR
from scribe.models.note import Note
from scribe.models.rulebook import Rulebook, RulebookTopic, Rule
+4 -4
View File
@@ -1,9 +1,9 @@
"""Editing a shared record: an editor grant is enough, a viewer grant is not.
The agent and web paths used to disagree — and worse, within the agent path
`delete_snippet` honoured editor shares while `update_snippet` did not, so a
colleague's snippet could be destroyed through an agent but not improved. Both
now resolve the read scope and then require WRITE, which is what the sharing UI
The agent and web paths must agree, and so must the doors within one path: a
`delete_snippet` that honours editor shares beside an `update_snippet` that
does not lets a colleague's snippet be destroyed through an agent but not
improved. Every door resolves the read scope and then requires WRITE, which is what the sharing UI
already promises: viewer / editor / admin are distinct grants.
A record readable-but-not-writable raises PermissionError rather than reporting
+4 -4
View File
@@ -33,11 +33,11 @@ def test_something_actually_reads_the_version_endpoint():
def test_the_footer_does_not_default_to_a_plausible_version():
"""The regression this readout was built to remove.
"""A failed fetch does not render a plausible version.
`appVersion` used to start life as the literal `"dev"` and the fetch
swallowed its own failure, so an instance that could not answer rendered
exactly what a healthy local build renders. Two very different states, one
If `appVersion` starts life as the literal `"dev"` and the fetch swallows
its own failure, an instance that cannot answer renders exactly what a
healthy local build renders. Two very different states, one
string, and no way to tell them apart from the page.
Pinned as "the ref does not start at a version-shaped literal" rather than
+2 -2
View File
@@ -1342,10 +1342,10 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
# ...and the menu carries it, marked.
seven = next(line for line in out["context"].splitlines() if "#7" in line)
assert "seen" in seven
# #8 (0.80) now falls OUTSIDE the margin band, because #7 (0.91) anchors it
# #8 (0.80) falls OUTSIDE the margin band, because #7 (0.91) anchors it
# at 0.81 instead of being filtered out first. That is the point rather than
# a casualty: the band measures distance from the best answer, and the best
# answer here is #7 — suppressing it used to promote a materially weaker hit
# answer here is #7 — suppressing it would promote a materially weaker hit
# into a slot it had not earned, with nothing in the output saying so.
assert out["note_ids"] == [7]
# The stamp saw the pull and the resemblance score for this payload.