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. """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 The failure these pin: a backfill that SELECTs the text alongside the id and
then iterate that snapshot, sleeping between records. A run over a real corpus then iterates 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 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 had captured — write path and then OVERWRITTEN with the pre-edit text the scan captured — and
and stamped at the current chunker version, so the next boot considered it stamped at the current chunker version, so the next boot considers it current
current and never repaired it. The record stayed findable by what it used to and never repairs it. The record stays findable by what it used to say,
say, silently, until someone happened to edit it again. silently, until someone happens to edit it again.
That window is exactly "forward work continues while a long backfill runs", That window is exactly "forward work continues while a long backfill runs",
which is the normal condition, not an unlucky one. 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 "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 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, floors get walked and budgets rarely do. Without a query per dial, a
a surface with three floor changes and one budget change reported the budget surface with three floor changes and one budget change reports the budget
as untouched: a WRONG calibration answer rather than a missing one, and as untouched: a WRONG calibration answer rather than a missing one, and
wrong in the direction a reader would not think to check. 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(): 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.""" the heading as its own piece before cutting the paragraph."""
entry = " ".join(["The deploy was verified against the live instance."] * 40) entry = " ".join(["The deploy was verified against the live instance."] * 40)
_assert_nothing_thin_and_nothing_lost( _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 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. this" while leaving "should I be calling this at all" unasked.
The gap was lopsided before this: `create_rule` and `start_planning` both The gap tends to be lopsided: `create_rule` and `start_planning` are where a
carried a real disambiguator, and `create_note` — far and away the disambiguator gets written first, while `create_note` — far and away the
highest-volume surface — carried none. Guidance sat in the rarest tool and highest-volume surface — is where it matters most. Guidance that sits only in
was missing from the most common one. the rarest tools misses the most common one.
WHAT THIS PINS, AND WHAT IT DOES NOT 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 clients, a bare `await` on a stream — supplying the deadline is part of
using it, not a hardening pass for later. using it, not a hardening pass for later.
Before this guard, no request in the app carried one.
""" """
from __future__ import annotations from __future__ import annotations
@@ -29,7 +29,7 @@ RESTORED_USERNAME = "backup_roundtrip_restored"
@pytest_asyncio.fixture @pytest_asyncio.fixture
async def source(): async def source():
"""A snippet, a process, and an issue that arose from a work task — the """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 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 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. """Real-Postgres tests for WHERE a rule reaches.
A rule lives in a rulebook topic (global) or on one project. Retrieval used to A rule lives in a rulebook topic (global) or on one project. Retrieval that
ignore that and search every rule the user owned, so each project's rules were ignores the home and searches every rule the user owns injects each project's
injected into every other project's sessions. What a mock cannot show is the 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 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 only the embedder, so every rule is an equally good match and the home alone
decides what comes back. 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 @pytest.mark.asyncio
async def test_a_call_where_everything_was_already_seen_is_readable(): 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 Logged as zero results with no suppression count, it would be
was indistinguishable from a bar nothing cleared — and the operator tuning indistinguishable from a bar nothing cleared — and the operator tuning
that bar would have been reading the wrong number. that bar would be reading the wrong number.
""" """
rec = MagicMock() rec = MagicMock()
hits = [(0.80, fake_note(id=11, title="Pool sizing decision", user_id=1))] 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 "does the strip remove the marker" — it did — but "does an edit that changes
nothing change nothing". nothing change nothing".
WHAT WENT WRONG. The derivation used to filter out body LINES matching 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. 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 leaves the rest of the trigger behind. `when_to_apply` is free text and is
composer writes all of it after the marker, so paragraphs 2..n were ordinary routinely several paragraphs; the composer writes all of it after the marker,
lines that the filter had no reason to touch. They came back as "the so paragraphs 2..n are ordinary lines that the filter has no reason to touch.
insight", the full trigger was prepended again, and the stored lesson gained They come back as "the insight", the full trigger is prepended again, and the
(n-1) paragraphs per update — silently, compounding, and in the half of the stored lesson gains (n-1) paragraphs per update — silently, compounding, and in the half of the
document that does the retrieving. document that does the retrieving.
On a three-paragraph trigger the body grows by the length of paragraphs 2 and 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. """Why the function takes a note.
The body on its own does not say where the trigger ends — that is the 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 `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.""" cannot be served, and this pins that the argument is the record."""
note = _stored(MANY_PARAGRAPHS, INSIGHT, []) 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 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 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 way to ask for the rest. The usual cause is a tool capped over a service that
that already accepted an offset — `snippets_svc.list_snippets(offset=0)` was already accepts an offset — the service's `offset` simply not exposed, or a
simply not exposed, and `list_processes` passed a hardcoded `offset=0` into hardcoded `offset=0` passed into `query_knowledge`. The capability exists one
`query_knowledge`. The capability existed one layer down in both cases; only layer down; only the door is missing.
the door was missing.
As in `test_mcp_auth`, the CANDIDATES are derived and the DECISION is explicit. 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, 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 @pytest.mark.asyncio
async def test_enter_project_hands_back_the_design_system_when_the_project_has_one(): 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 """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 system binds the same way a rule does. Reachable only by an agent that
an agent that already knew to call resolve_design_system — so the standards already knows to call resolve_design_system, the standards would be present
were present in the store and absent from the work.""" in the store and absent from the work."""
p = fake_project(id=5, design_system_id=9) p = fake_project(id=5, design_system_id=9)
design = {"id": 9, "title": "App kit", "guidance": [{"title": "House"}], design = {"id": 9, "title": "App kit", "guidance": [{"title": "House"}],
"token_count": 95, "token_groups": ["surface"], "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( async def test_each_specific_kind_reaches_the_engine_filter_it_names(
content_type, expected content_type, expected
): ):
"""`note_type` and `task_kind` were parameters of the search all along — """`note_type` and `task_kind` are parameters of the search, and the
what was missing was a way to ask for them from the agent's door. Asking agent's door passes them through. Asking
for a snippet must narrow to snippets, not merely to non-tasks.""" for a snippet must narrow to snippets, not merely to non-tasks."""
_user_id_ctx.set(7) _user_id_ctx.set(7)
mock_search = AsyncMock(return_value=[]) 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(): async def test_enter_project_stays_small_however_large_the_project():
"""The ceiling is the point: a long-lived project's handshake must not """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 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, project = fake_project(id=5, design_system_id=9, goal=GOAL,
description="Background. " * 300) description="Background. " * 300)
rules = { rules = {
+2 -2
View File
@@ -2,8 +2,8 @@
Covers the payload shaping and the two contracts that make this safe to leave in 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 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 the feature exists for — that the write-path PLACE arm is recorded, so a
before this it surfaced snippets while leaving no trace anywhere. snippet it surfaces leaves a trace.
""" """
from unittest.mock import AsyncMock, MagicMock, patch 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(): def test_the_rule_door_still_asks_before_writing():
"""Half one of the asymmetry. If this fails, the gate was tidied away and """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() doc = _doc(MODULE, "create_rule").lower()
asks = ("approve", "propose", "ask", "question") 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(): def test_the_reply_gets_a_second_pass_for_what_can_go():
"""Cutting is a separate act from writing, and needs saying separately. """Cutting is a separate act from writing, and needs saying separately.
The pre-existing check asked whether anything was MISSING, which a bloated A check that asks only whether anything is MISSING is one a bloated reply
reply passes. passes.
""" """
text = _flat() text = _flat()
assert "what can go" in text 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.asyncio
@pytest.mark.parametrize("dial", ["threshold", "limit", "", "FLOOR"]) @pytest.mark.parametrize("dial", ["threshold", "limit", "", "FLOOR"])
async def test_an_unknown_dial_is_refused(dial): async def test_an_unknown_dial_is_refused(dial):
"""The near-misses are the old vocabulary — `threshold` and `limit` are what """The near-misses are the generic vocabulary — `threshold` and `limit` are
these were called before this step, so they are exactly what a stale caller what a caller that has not read the dial names will send, and a silent no-op there would be indistinguishable from a
will send, and a silent no-op there would be indistinguishable from a
change that did not take.""" change that did not take."""
session, ctx = _patches() session, ctx = _patches()
with ctx[0], ctx[1], ctx[2], ctx[3], pytest.raises(ValueError): 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 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 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 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 bind on a response. If the operator's message reached only
`semantic_search_notes`, so no rule had ever been retrieved against a `semantic_search_notes`, no rule would ever be retrieved against a thing
thing the operator actually said. the operator actually said.
""" """
rec = MagicMock() rec = MagicMock()
search = AsyncMock(return_value=[(0.79, fake_rule( 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): 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 Every caller reads the function through `$( )` — a subshell, so a global
through `$( )` — a subshell, so the assignment died with it and the caller assignment dies with it and the caller sees an UNSET variable. The hooks all
saw an UNSET variable. The hooks all run `set -u`, where reading one aborts run `set -u`, where reading one aborts the script: a directory with a
the script: a directory with a misaddressed marker would have cost the misaddressed marker would cost the session its entire SessionStart context,
session its entire SessionStart context, to fetch a warning about a file. to fetch a warning about a file.
So the reason comes back through stdout with the id, and this asserts on 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 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: 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 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 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`, caller cannot see is silently dropped. The remedy is one shape, `_rank_scoped`,
and these pin that every search goes through it — the rule search was fixed and these pin that every search goes through it — fixed in one search alone,
alone first, and the other three kept the fault because nothing said they the others keep the fault, because nothing says they are the same query.
were the same query.
The integration lane's crowd tests show the behaviour on a real index; these 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 from __future__ import annotations
@@ -54,7 +54,7 @@ def test_every_semantic_search_ranks_through_the_scoped_shape():
calls = _calls(fn) calls = _calls(fn)
assert "_rank_scoped" in calls, f"{name} does not rank through _rank_scoped" 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" 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. # embedding table, which the index serves before the scope applies.
assert "order_by" not in calls, f"{name} orders by distance itself" 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 The full FK-remapping round-trip needs real Postgres and lives in the
`test_integration_backup_*_roundtrip.py` modules, which drive the real `test_integration_backup_*_roundtrip.py` modules, which drive the real
`restore_full_backup`. That sentence used to read "it is not written yet"; `restore_full_backup`.
four of those files exist now, the newest covering the three `code_shapes`
columns the import guard below found missing.
""" """
from datetime import datetime, timezone from datetime import datetime, timezone
from types import SimpleNamespace 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 COLUMN GUARD — _NOT_INCLUDED's shape, one level down.
The table guard below catches a whole table going missing. It cannot catch 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 a COLUMN going missing from a table it already considers covered, and the
how nine of them vanished from `notes` alone: note_type and task_kind, so cost is large: without note_type and task_kind every snippet and process
every snippet and process restored as a plain note and every issue and restores as a plain note and every issue and spike as `work`; without
spike as `work`; arose_from_id, so every provenance edge went; the arose_from_id every provenance edge goes; without the recurrence pair
recurrence pair, so recurring tasks stopped recurring. Plus milestones.body recurring tasks stop recurring.
— which IS the plan — and repo_bindings.ref.
Each arrived the same way: added to the model and the migration, both of A column arrives in the model and the migration, both of which fail loudly,
which fail loudly, and never to the serialiser, which fails silently. 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
A new column must now be exported or named in _COLUMN_EXCLUSIONS with a forgetting is not expressible.
reason. Forgetting is no longer expressible.
""" """
model, helper = _column_guard_targets()[table] model, helper = _column_guard_targets()[table]
[row] = helper([_stand_in(model)]) [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 # 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", # guard would otherwise keep telling.
# "invitations" and "password_resets" — three entries that named nothing.
phantom = accounted - schema phantom = accounted - schema
assert not phantom, ( assert not phantom, (
f"backup lists table(s) that are not in the schema: {sorted(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 "get_note(id)" in out["context"]
assert "↳" not in out["context"] assert "↳" not in out["context"]
# Telemetry fired for BOTH retrievals this path runs: the scored menu and # 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 reuse-slot query competing against it. The hit the slot displaces is
# the one unlogged retrieval on this path — the hit it displaced was in # in retrieval_logs, so the query that displaced it must be too.
# retrieval_logs, and the query that displaced it must be too.
sources = [c.kwargs["source"] for c in rec.call_args_list] sources = [c.kwargs["source"] for c in rec.call_args_list]
assert sources == ["auto_inject", "reuse_slot", "lesson_slot"] 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 — another instance, or names a deleted project, the session has to be told —
it cannot infer it from an absence. it cannot infer it from an absence.
This used to render nothing whatsoever: the branch hung off `if project_id` The trap is a branch hung off `if project_id` as an `elif`: an id that was
as an `elif`, so an id that was sent and failed took the outer arm, found sent and failed takes the outer arm, finds no project, and falls out of the
no project, and fell out of the block having said nothing at all. block having said nothing at all.
""" """
from scribe.services.plugin_context import build_session_context from scribe.services.plugin_context import build_session_context
with patch("scribe.services.plugin_context.projects_svc.get_project", 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(): def test_a_call_that_returned_nothing_still_records_what_it_nearly_showed():
"""The whole point, at the payload grain. """The whole point, at the payload grain.
This is the row a threshold is tuned from and the one that used to carry no This is the row a threshold is tuned from: `top_score` and `min_score` are
score at all: `top_score` and `min_score` are both null here, correctly, and both null here, correctly, and without the near-miss a reader cannot tell a
a reader was left unable to tell a bar rejecting 0.71s from one rejecting bar rejecting 0.71s from one rejecting 0.30s. Both render as a zero-result
0.30s. Both render as a zero-result call. call.
""" """
p = _build_payload( p = _build_payload(
user_id=1, source="pre_tool_rule", query="git push --force", 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(): 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 # A tag read back as the language would be dropped from the extra-tag set
# from the extra-tag set on re-compose — so it silently disappeared. # on re-compose — and silently disappear.
tags = s.compose_tags("", ["auth"]) tags = s.compose_tags("", ["auth"])
fields = s.parse_snippet_fields("n — u", s.compose_body(code="x = 1"), tags) 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"])] 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(): def test_owner_clause_scopes_every_model():
"""Regression: every trash op must owner-scope, including topics/rules """Every trash op must owner-scope, topics and rules included — a model
which previously had NO owner check (IDOR across tenants).""" with no owner check is an IDOR across tenants."""
from scribe.services.trash import _owner_clause, _MODEL_FOR from scribe.services.trash import _owner_clause, _MODEL_FOR
from scribe.models.note import Note from scribe.models.note import Note
from scribe.models.rulebook import Rulebook, RulebookTopic, Rule 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. """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 The agent and web paths must agree, and so must the doors within one path: a
`delete_snippet` honoured editor shares while `update_snippet` did not, so a `delete_snippet` that honours editor shares beside an `update_snippet` that
colleague's snippet could be destroyed through an agent but not improved. Both does not lets a colleague's snippet be destroyed through an agent but not
now resolve the read scope and then require WRITE, which is what the sharing UI 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. already promises: viewer / editor / admin are distinct grants.
A record readable-but-not-writable raises PermissionError rather than reporting 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(): 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 If `appVersion` starts life as the literal `"dev"` and the fetch swallows
swallowed its own failure, so an instance that could not answer rendered its own failure, an instance that cannot answer renders exactly what a
exactly what a healthy local build renders. Two very different states, one healthy local build renders. Two very different states, one
string, and no way to tell them apart from the page. 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 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. # ...and the menu carries it, marked.
seven = next(line for line in out["context"].splitlines() if "#7" in line) seven = next(line for line in out["context"].splitlines() if "#7" in line)
assert "seen" in seven 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 # 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 # 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. # into a slot it had not earned, with nothing in the output saying so.
assert out["note_ids"] == [7] assert out["note_ids"] == [7]
# The stamp saw the pull and the resemblance score for this payload. # The stamp saw the pull and the resemblance score for this payload.