From d41e72d26cbabe8f1a2f89d2d212a43865eb9f55 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 21 Oct 2026 21:24:50 -0400 Subject: [PATCH] docs(6209): twelve more test files say which behaviour they pin shape ledger, MCP projects/search/rulebooks tools, backup, chunking, plugin context, derived mirror, rule band, shape form gate, plugin version mint and note usage tests drop issue, milestone and rule numbers, dates and one-install figures from docstrings, comments and messages. Three shape-form-gate test names that told the discovery story now name the property: a registry module's helpers are not service functions, the divergence gate silences only a family contradiction, and callable helpers beside a callable canon still prompt. The rulebook tool-count comment that logged each milestone's additions is replaced by the reason it is a count. Co-Authored-By: Claude Opus 5.5 --- tests/test_chunking.py | 45 +++++++------- tests/test_derived_mirror_generic_door.py | 34 +++++----- tests/test_mcp_tool_projects.py | 34 +++++----- tests/test_mcp_tool_rulebooks.py | 31 +++------- tests/test_mcp_tool_search.py | 34 +++++----- tests/test_note_usage.py | 42 ++++++------- tests/test_plugin_version_mint.py | 31 ++++------ tests/test_rule_hint_band.py | 43 ++++++------- tests/test_services_backup.py | 39 +++++------- tests/test_services_plugin_context.py | 45 +++++++------- tests/test_shape_form_gate.py | 75 +++++++++++------------ tests/test_shape_ledger.py | 35 +++++------ 12 files changed, 223 insertions(+), 265 deletions(-) diff --git a/tests/test_chunking.py b/tests/test_chunking.py index 828910b9..650b28a1 100644 --- a/tests/test_chunking.py +++ b/tests/test_chunking.py @@ -1,9 +1,9 @@ -"""chunk_document — the document shape a record is embedded as (#280). +"""chunk_document — the document shape a record is embedded as. -The model window is 512 tokens and fastembed truncates silently, so before -chunking, everything past ~400 words of a record was PERMANENTLY invisible to -semantic search. These tests pin the two halves of the fix's contract: short -records keep the exact historical shape (the corpus's sharpest vectors are +The model window is 512 tokens and fastembed truncates silently, so without +chunking everything past ~400 words of a record would be invisible to +semantic search. These tests pin the two halves of the contract: short +records keep the single-document shape (the corpus's sharpest vectors are untouched), and long records lose NOTHING — every line of the body lands in some chunk, each chunk inside the window budget, each carrying the title as its topical anchor. @@ -29,7 +29,7 @@ def _long_section(tag: str, paragraphs: int = 6, sentence: str = None) -> str: def test_a_short_record_yields_exactly_the_historical_shape(): """Snippets and reference notes are the sharpest records in the corpus - (#2485) precisely because of this shape — chunking must not touch them.""" + precisely because of this shape — chunking must not touch them.""" assert chunk_document("A title", "A short body") == [ embedding_text("A title", "A short body") ] @@ -54,8 +54,8 @@ def test_a_record_exactly_at_budget_stays_whole(): def test_every_line_of_a_long_body_lands_in_some_chunk(): - """The point of #280. Before chunking, a 2,000-word dev-log's last three - quarters could not influence retrieval at all. Nothing may be dropped.""" + """The point of chunking: every part of a 2,000-word dev-log can + influence retrieval. Nothing may be dropped.""" sections = [ f"## Topic {i}\n\n{_long_section(f'topic-{i}')}" for i in range(8) ] @@ -139,12 +139,11 @@ def test_a_monster_single_paragraph_is_hard_split_not_dropped(): assert total_words == 2000 -# --- no chunk is a heading alone (#4784) ------------------------------------- +# --- no chunk is a heading alone --------------------------------------------- # -# Found by judging live auto-inject menus: "## Work log — 2026-08-21", -# "## Findings worth carrying forward" and "Two dev→main PRs this session." -# were each a whole chunk, and took ranks 1–3 on vague queries — text about -# nothing embeds close to everything. Each case below is one of those shapes. +# A heading or a one-line preamble as a whole chunk ranks at the top of vague +# queries — text about nothing embeds close to everything. Each case below is +# one of those shapes. _HEADING = re.compile(r"^#{1,6}\s") @@ -220,7 +219,7 @@ def test_a_continuation_piece_repeats_the_heading_it_sits_under(): assert "## Child" in chunk -# --- the read path: best chunk wins (#280 step 4) ---------------------------- +# --- the read path: best chunk wins ------------------------------------------ async def test_search_collapses_chunk_rows_to_best_chunk_per_note(): @@ -233,7 +232,7 @@ async def test_search_collapses_chunk_rows_to_best_chunk_per_note(): note_a, note_b = MagicMock(id=1), MagicMock(id=2) # Rows are (Note, distance, chunk_index, chunk_text) — the chunk columns - # ride along so the collapse can report WHICH passage won (#4243). + # ride along so the collapse can report WHICH passage won. rows = [ (note_a, 0.10, 3, "the passage that actually matched"), (note_b, 0.20, 0, "b's best"), @@ -259,19 +258,19 @@ async def test_search_collapses_chunk_rows_to_best_chunk_per_note(): # And the winning chunk is reported, not merely used for scoring. Without # this a caller can only preview the head of the body — a span this query - # has already ranked lower than the one that won (#4243). + # has already ranked lower than the one that won. assert report["best_chunk"][1] == { "index": 3, "text": "the passage that actually matched", } assert report["best_chunk"][2]["index"] == 0 -# --- the write path: one row per chunk (#280 step 3) ------------------------- +# --- the write path: one row per chunk --------------------------------------- def _session_ctx(log_rows=()): """A session stand-in. `log_rows` answers the work-log read a task's - document now needs (#4251) — answered explicitly rather than left to + document needs — answered explicitly rather than left to autovivify, because `list(result.all())` on a bare MagicMock raises and is swallowed, which would quietly make every one of these a no-log test.""" from unittest.mock import AsyncMock, MagicMock @@ -318,9 +317,9 @@ async def test_upsert_stores_one_versioned_row_per_chunk(): async def test_a_tasks_work_logs_reach_the_rows_it_is_stored_as(): - """The wiring half of #4251: the shaper is pure and tested next door, so - what is pinned here is that the WRITER actually asks for the logs and - embeds what comes back. A log that is written, readable (#4241) and absent + """The wiring half: the shaper is pure and tested next door, so what is + pinned here is that the WRITER actually asks for the logs and embeds what + comes back. A log that is written, readable and absent from the index is the same half-surface one layer down.""" import datetime from unittest.mock import AsyncMock, patch @@ -380,7 +379,7 @@ async def test_backfill_reembeds_notes_with_a_stale_chunker_version(): session, ctx = _session_ctx() # The opening scan, in order: ids already at the current version, every # note id, vectors older than their note, tasks logged since their vectors. - # IDS ONLY — the text is re-read per note at embed time (#4262). + # IDS ONLY — the text is re-read per note at embed time. session.execute = AsyncMock(side_effect=[ _ids(1), # note 1 is current _ids(1, 2), # the corpus @@ -402,7 +401,7 @@ async def test_backfill_reembeds_notes_with_a_stale_chunker_version(): def test_a_row_is_current_only_in_the_live_models_space(): - """#4132: the column is a width, not an identity, so a same-width model + """The column is a width, not an identity, so a same-width model swap writes a second geometry with no error. The backfill's "current" predicate has to name BOTH halves of the stamp, on every embedding table.""" from scribe.models.embedding import ( diff --git a/tests/test_derived_mirror_generic_door.py b/tests/test_derived_mirror_generic_door.py index 95af1c5c..e64758ab 100644 --- a/tests/test_derived_mirror_generic_door.py +++ b/tests/test_derived_mirror_generic_door.py @@ -12,19 +12,19 @@ The failure is silent and the wrong way round, because every read PREFERS the mirror — deliberately, since parsing markdown to answer what an index can answer is how a hot path rots. - - A SNIPPET went on reporting its old repo/path/symbol to the location + - A SNIPPET goes on reporting its old repo/path/symbol to the location reverse lookup and to prior-art recall while displaying its new body: a record surfaced with full authority and wrong, which the drift-check - docstring calls worse than having no record at all (#3128). + docstring calls worse than having no record at all. - A LESSON is worse. Its mirror holds the TRIGGER, and the trigger is the entire retrieval story — a stale one keeps the lesson firing for the situation it used to name while it displays the one it now names. -#3128's fix was correct and did not generalise: it tested one constant, so -milestone 385's lesson arrived with the same design and none of the protection. -The registry tests below assert the PROPERTY — every kind with a derived mirror -is registered — so a third kind fails here rather than shipping quiet (#3734). +A fix that tests one constant does not generalise: the next kind arrives with +the same design and none of the protection. The registry tests below assert +the PROPERTY — every kind with a derived mirror is registered — so a third +kind fails here rather than shipping quiet. """ import inspect @@ -114,30 +114,30 @@ async def test_a_title_change_reaches_the_mirror_too(): def test_every_kind_with_a_derived_mirror_is_registered(): - """The load-bearing one. Before #3734 this was a single `if` naming - snippets, and the kind added next was simply not covered.""" + """The load-bearing one. A single `if` naming one kind leaves the next + kind uncovered.""" from scribe.services.lessons import LESSON_NOTE_TYPE from scribe.services.notes import _mirror_recomposers from scribe.services.snippets import SNIPPET_NOTE_TYPE table = _mirror_recomposers() - assert SNIPPET_NOTE_TYPE in table, "the #3128 fix was lost" + assert SNIPPET_NOTE_TYPE in table, "snippets lost their mirror recomposer" assert LESSON_NOTE_TYPE in table, ( "a lesson's `data` holds its trigger and `lesson_trigger` prefers it, " "so a body edit through the generic door would leave the lesson being " - "retrieved for a situation it no longer names (#3734)" + "retrieved for a situation it no longer names" ) def test_the_dispatch_is_a_lookup_not_a_named_kind(): - """Asserted on structure (rule 167). A lookup extends in one line in one + """Asserted on structure. A lookup extends in one line in one place; naming a kind inline is the shape that left lessons uncovered.""" from scribe.services import notes as notes_module src = inspect.getsource(notes_module.update_note) assert "_mirror_recomposers()" in src assert "SNIPPET_NOTE_TYPE" not in src, ( - "update_note names one kind again — that is the shape #3734 replaced" + "update_note names one kind inline — the dispatch must be a lookup" ) @@ -184,7 +184,7 @@ async def test_a_body_write_moves_a_lessons_trigger_with_it(): async def test_a_subject_containing_an_em_dash_still_splits(): """Why `untrigger_title` is given the trigger instead of splitting on the separator: a subject may legitimately contain one. The title here is an - UN-MIGRATED one, still carrying its trigger (milestone 427), because that + UN-MIGRATED one, still carrying its trigger, because that is the row the inverse still has to read correctly.""" from scribe.services.embeddings import trigger_title from scribe.services.lessons import TRIGGER_KEY @@ -259,8 +259,8 @@ async def test_a_lesson_title_change_reaches_the_mirror(): ], ) def test_untrigger_title_inverts_trigger_title(subject, trigger): - """#3207's shape: the join had three copies before it was consolidated, so - its inverse lives beside it rather than in whichever caller wanted it.""" + """The join's inverse lives beside it rather than in whichever caller + wanted it.""" from scribe.services.embeddings import trigger_title, untrigger_title title = trigger_title(subject, trigger) @@ -289,12 +289,12 @@ def test_the_trigger_separator_is_spelled_in_exactly_one_place(): ] assert not offenders, ( f"the trigger separator is spelled inline in {offenders} — use " - f"TRIGGER_SEP, trigger_title or untrigger_title (#3207)" + f"TRIGGER_SEP, trigger_title or untrigger_title" ) def test_the_mirror_guards_can_fail(): - """Rule 167: shown turning red once.""" + """Shown turning red once.""" from scribe.services.lessons import TRIGGER_KEY, recompose_data bare = fake_lesson(title="just a title", body="no composed lines", data=None) diff --git a/tests/test_mcp_tool_projects.py b/tests/test_mcp_tool_projects.py index c3bd8634..9f765240 100644 --- a/tests/test_mcp_tool_projects.py +++ b/tests/test_mcp_tool_projects.py @@ -17,7 +17,7 @@ pytestmark = pytest.mark.usefixtures("_bind_user") @pytest.fixture(autouse=True) def _no_systems(): """enter_project now surfaces the project's Systems as the tagging - vocabulary (#2546). These are tool-layer unit tests with no database, so + vocabulary. These are tool-layer unit tests with no database, so the lookup is stubbed to the common case — a project with none. The populated shape is asserted in its own test below. """ @@ -40,7 +40,7 @@ def _no_platforms(): @pytest.fixture(autouse=True) def _no_coverage(): - """enter_project also reads the pattern-coverage cache (#2692) — same + """enter_project also reads the pattern-coverage cache — same deal: no database here, stub the common case (nothing computed). The populated line is asserted in tests/test_pattern_coverage.py. """ @@ -51,7 +51,7 @@ def _no_coverage(): @pytest.fixture(autouse=True) def _no_family(): - """enter_project reads the family-canon readout (milestone 463 step 6) — + """enter_project reads the family-canon readout — no database here, so the common case is stubbed: nothing to answer. The populated key is asserted in its own test below.""" with patch("scribe.mcp.tools.projects.family_adoption_svc.family_readout", @@ -61,7 +61,7 @@ def _no_family(): @pytest.fixture(autouse=True) def _no_background_seed(): - """enter_project now fire-and-forgets a coverage self-seed (#2802). These + """enter_project fire-and-forgets a coverage self-seed. These are no-database unit tests, so the spawn is stubbed out; the firing shape has its own test below.""" with patch("scribe.mcp.tools.projects.spawn") as mock: @@ -71,7 +71,7 @@ def _no_background_seed(): @pytest.fixture(autouse=True) def _no_bootstrap(): """With _no_systems stubbing an empty vocabulary, every test here reaches - the zero-Systems branch, whose bootstrap ask (#2683) counts the project's + the zero-Systems branch, whose bootstrap ask counts the project's records — a database read. Stub the common case (young project, no ask); the firing shape has its own test below. """ @@ -117,7 +117,7 @@ async def test_get_project_enriches_with_milestone_summary(): async def test_get_project_includes_applicable_rules_and_project_rules(): """The augmented get_project response includes applicable_rules and project_rules pulled from services/rulebooks.get_applicable_rules, and - nothing about subscriptions (milestone 414). + nothing about subscriptions. """ p = fake_project(id=3, title="Fabled Assistant") milestone_summary = [] @@ -191,7 +191,7 @@ async def test_update_project_raises_when_not_found(): @pytest.mark.asyncio async def test_enter_project_composes_full_context(): """enter_project pulls project + rule titles + milestone summary + open - tasks in one composed call, each in its brief handshake form (#4045).""" + tasks in one composed call, each in its brief handshake form.""" p = fake_project(id=5, title="P") applicable_payload = { "rules": [{"id": 1, "title": "r1", "statement": "s", @@ -226,7 +226,7 @@ async def test_enter_project_composes_full_context(): assert out["project"] == {"id": 5, "title": "P", "status": "active", "goal": ""} assert out["milestone_summary"] == milestone_summary # Rules arrive in full by retrieval; the handshake lists the project's own - # by id and title (#4045), and nothing about subscriptions (milestone 414). + # by id and title, and nothing about subscriptions. assert out["project_rules"] == [{"id": 99, "title": "pr1"}] for gone in ("applicable_rules", "applicable_rules_truncated", "subscribed_rulebooks", "suppressed_rules", "suppressed_topics", "recent_notes"): @@ -244,16 +244,14 @@ async def test_enter_project_composes_full_context(): # create-the-System instruction acts on. assert out["systems"] == [] # Young project, no bootstrap ask -> the key is ABSENT, not null: it exists - # to be acted on, and a key that usually says null gets skipped (#2483). + # to be acted on, and a key that usually says null gets skipped. assert "systems_bootstrap" not in out @pytest.mark.asyncio async def test_enter_project_surfaces_the_systems_vocabulary(): """The tagging instruction is only executable if the vocabulary is in - front of the agent when it writes. It never was, and tagging stopped three - days after the feature landed — one System, nothing tagged since July 28 - (#2546's audit). Trimmed to id/name/first-line: it rides on every session + front of the agent when it writes. Trimmed to id/name/first-line: it rides on every session start, and the full charter is get_system's job.""" p = fake_project(id=5) sys1 = MagicMock() @@ -298,7 +296,7 @@ def _enter_project_stubs(p): @pytest.mark.asyncio async def test_enter_project_carries_the_bootstrap_ask_when_it_fires(): - """The arrival-moment half of #2683: a mature zero-Systems project greets + """The arrival-moment half of the bootstrap ask: a mature zero-Systems project greets the session with the concrete bootstrap ask, before it is deep in a task — the moment minting a starter vocabulary is cheapest.""" import contextlib @@ -341,7 +339,7 @@ async def test_enter_project_never_asks_bootstrap_once_a_vocabulary_exists( async def test_enter_project_fires_the_coverage_seed_on_the_owner( _no_background_seed, ): - """The arrival self-seed (#2802): entering spawns refresh_if_stale on the + """The arrival self-seed: entering spawns refresh_if_stale on the project OWNER's id with the cache read the enter already did — fire and forget, so a missing ledger seeds itself without the UI button, and the enter stays fast.""" @@ -393,7 +391,7 @@ async def test_enter_project_hands_back_the_design_system_when_the_project_has_o assert out["design_system"]["token_count"] == 95 assert out["design_system"]["inherits_from"] == ["House"] - # Summary only: the guidance is one call away (#4045). + # Summary only: the guidance is one call away. assert "guidance" not in out["design_system"] assert out["design_system"]["guidance_call"] == "get_design_system(9) → resolved_guidance" assert ctx.await_args.args == (7, 9) # caller's id, the project's system @@ -418,7 +416,7 @@ def test_enter_project_registered_in_register(): assert "enter_project" in mcp.names -# --- milestone 297: the inception doors --------------------------------------- +# --- the inception doors ----------------------------------------------------- @pytest.mark.asyncio @@ -491,7 +489,7 @@ def test_inception_routes_and_tool_are_registered(): tool = mcp._tool_manager.get_tool("create_project") for name in ("design_system_id", "seed_systems"): assert name in tool.parameters.get("properties", {}), name - # Rules left inception with subscriptions (milestone 414). + # Inception carries no rulebook subscriptions. assert "subscribe_rulebooks" not in tool.parameters.get("properties", {}) @@ -500,7 +498,7 @@ def test_inception_routes_and_tool_are_registered(): async def test_enter_project_carries_the_family_line_only_when_it_asks_something(_no_family): """The `family` key is attached when the project has family canon to answer, and absent otherwise — a key that usually says null trains - readers to skip it (#2483).""" + readers to skip it.""" line = {"line": "family canon on this project's platforms: 2 unassessed", "unassessed": 2, "owed": 0, "needs_recheck": 0, "calls": {"unassessed": 'list_family_adoptions(project_id=5, status="unassessed")'}} diff --git a/tests/test_mcp_tool_rulebooks.py b/tests/test_mcp_tool_rulebooks.py index 8110cc36..51cba5b8 100644 --- a/tests/test_mcp_tool_rulebooks.py +++ b/tests/test_mcp_tool_rulebooks.py @@ -49,8 +49,7 @@ async def test_get_rulebook_raises_when_not_found(): await get_rulebook(rulebook_id=999) -# _plain_detail moved to tests/helpers on its second copy (#2825's own -# reason for existing): two stubs for one seam drift apart quietly, and a +# _plain_detail lives in tests/helpers: two stubs for one seam drift apart quietly, and a # test stubbing it slightly differently asserts something slightly different # than it appears to. @@ -199,16 +198,7 @@ def test_register_attaches_every_tool(): mcp = FakeMCP() register(mcp) - # 26 through milestone 307, +2 for the staleness sweep (milestone 312), - # +1 for a rule's edit history (milestone 323), +2 for preferences - # (milestone 399). - # 28 since milestone 394 took list_always_on_rules and the two - # always-on exclusion tools with the tier they served. - # 22 since milestone 414 retired subscriptions and suppressions: the two - # subscribe tools and the four suppress/unsuppress tools. 23 with move_rule - # (milestone 414 step 3), the way a rule changes home. 24 with - # rule_outcome (milestone 419 step 1), the way a rule says what it - # actually changed. + # Pinned as a count so a tool added or dropped is a deliberate edit here. assert len(mcp.names) == 24 # spot-check a few names assert "list_rulebooks" in mcp.names @@ -221,12 +211,12 @@ def test_register_attaches_every_tool(): assert "update_preference" in mcp.names assert "create_project_rule" in mcp.names assert "move_rule" in mcp.names - # milestone 312: the sweep, and the stamp that answers it + # the sweep, and the stamp that answers it assert "rules_due_for_verification" in mcp.names assert "mark_rule_verified" in mcp.names - # milestone 323: what a rule used to say + # what a rule used to say assert "rule_history" in mcp.names - # milestone 414: a rule's home is its scope; nothing subscribes or mutes. + # a rule's home is its scope; nothing subscribes or mutes. for gone in ("subscribe_project_to_rulebook", "unsubscribe_project_from_rulebook", "suppress_rule_for_project", "unsuppress_rule_for_project", "suppress_topic_for_project", "unsuppress_topic_for_project"): @@ -292,7 +282,7 @@ async def test_create_project_rule_uses_explicit_title_when_given(): assert kwargs["title"] == "no auto-docstrings" -# ── Typed edges between rules (milestone 307) ─────────────────────────── +# ── Typed edges between rules ─────────────────────────────────────────── @pytest.mark.asyncio async def test_relate_rules_forwards_the_kind_and_the_why(): @@ -336,7 +326,7 @@ async def test_unrelate_rules_raises_when_the_edge_is_gone(): await unrelate_rules(relation_id=99) -# ── rule_history (milestone 323 step 3) ──────────────────────────────── +# ── rule_history ─────────────────────────────────────────────────────── def _fake_version(**over): """A RuleVersion-shaped stand-in. A real model instance rather than a @@ -397,9 +387,8 @@ async def test_rule_history_opens_one_version_in_full(): @pytest.mark.asyncio async def test_rule_history_says_an_empty_history_is_ordinary(): - """Most rules have never been reworded, and nothing was written before - milestone 323. Without this line an empty list reads as a lost history or - a broken tool.""" + """Most rules have never been reworded. Without this line an empty list + reads as a lost history or a broken tool.""" with patch( "scribe.mcp.tools.rulebooks.rulebooks_svc.list_rule_versions", AsyncMock(return_value=[]), @@ -482,7 +471,7 @@ async def test_move_rule_on_someone_elses_rule_is_not_found(): await move_rule(rule_id=94, project_id=3) -# ── rule_outcome: what a rule actually changed (#4212, milestone 419) ───── +# ── rule_outcome: what a rule actually changed ─────────────────────────── @pytest.mark.asyncio async def test_rule_outcome_records_an_application(): diff --git a/tests/test_mcp_tool_search.py b/tests/test_mcp_tool_search.py index 2fff7a76..004f7967 100644 --- a/tests/test_mcp_tool_search.py +++ b/tests/test_mcp_tool_search.py @@ -52,7 +52,7 @@ async def test_fable_search_returns_repackaged_results(): @pytest.mark.asyncio async def test_search_shows_the_passage_that_matched_not_the_opening(): - """The point of #4243. A record can rank on its sixth paragraph; showing + """A record can rank on its sixth paragraph; showing its first is showing the caller a span the search already judged less relevant, and letting them decide from it.""" _user_id_ctx.set(7) @@ -131,7 +131,7 @@ async def test_fable_search_content_type_filters_at_service_layer(): specific kinds arrived — it is BROAD here (any non-task, snippets and lessons included) while the web facet of the same name is narrow. Pinning it stops a later tidy-up from silently removing snippets from every caller - that already asks this way (#4250).""" + that already asks this way.""" _user_id_ctx.set(7) mock_search = AsyncMock(return_value=[]) with patch("scribe.mcp.tools.search.semantic_search_notes", mock_search): @@ -149,7 +149,7 @@ async def test_fable_search_content_type_filters_at_service_layer(): assert mock_search.call_args.kwargs["is_task"] is None -# --- the specific kinds: the engine already supported them (#4250) ----------- +# --- the specific kinds the engine supports ---------------------------------- @pytest.mark.asyncio @@ -181,9 +181,8 @@ async def test_each_specific_kind_reaches_the_engine_filter_it_names( def test_the_vocabulary_is_derived_from_the_facet_table_not_recopied(): - """#3161's property, held at this door too: adding a kind to `_FACETS` is - one edit. A hand-kept list here is exactly how the agent's search came to - offer two kinds while the web's offered nine.""" + """Adding a kind to `_FACETS` is one edit, at this door too. A hand-kept + list here lets the agent's search offer fewer kinds than the web's.""" from scribe.services.knowledge import FACET_TYPES, content_type_filters for facet in FACET_TYPES: @@ -191,7 +190,7 @@ def test_the_vocabulary_is_derived_from_the_facet_table_not_recopied(): assert "is_task" in filters, facet # The guard can fail: a name absent from the table is refused, so this is - # membership in the table and not "every string works" (#167). + # membership in the table and not "every string works". with pytest.raises(ValueError): content_type_filters("a-kind-that-is-not-in-the-facet-table") @@ -215,7 +214,7 @@ async def test_an_unknown_content_type_is_refused_rather_than_returning_nothing( @pytest.mark.asyncio async def test_the_docstring_names_every_kind_the_tool_accepts(): - """The docstring IS the agent-facing contract (#2846) — a filter an agent + """The docstring IS the agent-facing contract — a filter an agent has not been told about is unreachable however well it is wired.""" from scribe.mcp.tools.search import search as search_tool from scribe.services.knowledge import FACET_TYPES @@ -229,12 +228,12 @@ async def test_the_docstring_names_every_kind_the_tool_accepts(): @pytest.mark.asyncio async def test_an_explicit_search_reaches_a_lesson_from_any_project(): - """The wiring half of milestone 385 step 3. + """An explicit search reaches lessons in every project. A lesson records an insight that transfers, so a project filter that hid it would hide it precisely on the project that has not learned it yet. This is the EXPLICIT search — the operator asked — so it opts in; the unasked-for - injection arms decide their own budget separately (step 5). + injection arms decide their own budget separately. Asserted on the kwarg rather than on results, because what can regress here is the wiring: the service grew the capability and a call site that never @@ -269,7 +268,7 @@ async def test_fable_search_limit_is_clamped(): (0, {"everywhere": True}), ]) async def test_rule_search_scopes_to_the_project_it_is_given(project_id, scope): - """With a project: global rules plus that project's (milestone 414). + """With a project: global rules plus that project's. Without one: every rule, because an unscoped "is there a rule about this" is asking the whole rulebook — unlike a hook, which speaks unasked.""" _user_id_ctx.set(7) @@ -293,14 +292,13 @@ def test_a_milestone_is_embedded_by_what_it_is_for_then_its_plan(): @pytest.mark.asyncio async def test_milestone_search_is_its_own_shape_and_scopes_to_the_project(): - """milestone 415: 'is there already a plan for this?' has a tool.""" + """'Is there already a plan for this?' has a tool.""" from unittest.mock import MagicMock _user_id_ctx.set(7) # `body` is a REAL string, not left to MagicMock's attribute autovivication: # the result now reads it, and a mock body would make `matched` a mock - # object and `body_length` zero while the assertion still looked green - # (lesson #2833). + # object and `body_length` zero while the assertion still looked green. ms = MagicMock(id=339, title="M3 — Metadata", description="works, editions", status="active", project_id=30, body="Step 4 — the metadata editions table.") @@ -323,7 +321,7 @@ async def test_milestone_search_is_its_own_shape_and_scopes_to_the_project(): assert out["results"] == [{ "id": 339, "title": "M3 — Metadata", "description": "works, editions", # The plan body stays out; the passage that matched comes along, and - # says which of the two it is (#4243). + # says which of the two it is. "matched": "Step 4 — the metadata editions table.", "matched_is": "matched_passage", "body_length": len("Step 4 — the metadata editions table."), @@ -332,7 +330,7 @@ async def test_milestone_search_is_its_own_shape_and_scopes_to_the_project(): }] -# --- systems: the charter as an answer, not a filter (#4251) ----------------- +# --- systems: the charter as an answer, not a filter ------------------------- @pytest.mark.asyncio @@ -340,7 +338,7 @@ async def test_system_search_is_its_own_shape_and_carries_the_matched_passage(): """A System's charter runs to several hundred words and a result shows its NAME — so a match on the paragraph that actually decides where a record belongs would be previewed by two words that cannot. The passage comes - along, marked as the passage (#4243).""" + along, marked as the passage.""" _user_id_ctx.set(7) charter = ( "Opening sentence about the area in general. " @@ -349,7 +347,7 @@ async def test_system_search_is_its_own_shape_and_carries_the_matched_passage(): ) fake = MagicMock(id=3, project_id=2, description=charter) # `name` is MagicMock's own constructor kwarg — passed in, it names the - # mock and leaves `.name` a mock object, which is note #2833's whole point. + # mock and leaves `.name` a mock object. fake.name = "Retrieval & recall" async def _system_search(uid, q, **kwargs): diff --git a/tests/test_note_usage.py b/tests/test_note_usage.py index 4bcc5176..777a050e 100644 --- a/tests/test_note_usage.py +++ b/tests/test_note_usage.py @@ -1,4 +1,4 @@ -"""Tests for the usage signal (#2085) — was a surfaced record ever pulled? +"""Tests for the usage signal — was a surfaced record ever pulled? 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 @@ -90,8 +90,8 @@ async def test_usage_for_notes_splits_counts_by_event(): from datetime import datetime, timezone ts = datetime(2026, 7, 28, tzinfo=timezone.utc) - # Rows are (note_id, event, count, last_at, ambient) since #2477 split the - # readout. Ranked and ambient surfacings arrive as separate groups. + # Rows are (note_id, event, count, last_at, ambient): ranked and ambient + # surfacings arrive as separate groups. rows = [ (3, "surfaced", 9, ts, False), (3, "surfaced", 40, ts, True), @@ -111,7 +111,7 @@ async def test_usage_for_notes_splits_counts_by_event(): out = await usage_for_notes([3]) # The dead-weight reading ("surfaced often, never pulled") is only valid # over surfacings that were CHOICES. 40 enter_project appearances must not - # make a record look popular — they sit in ambient_count (#2477). + # make a record look popular — they sit in ambient_count. assert out[3]["surfaced_count"] == 9 assert out[3]["ambient_count"] == 40 assert out[3]["pull_count"] == 2 @@ -120,7 +120,7 @@ async def test_usage_for_notes_splits_counts_by_event(): async def test_usage_for_notes_counts_what_happened_away_from_home(): """Surfaced or opened on a project other than the record's own is the - evidence a record transferred (#3735). It is a SUBSET of the totals, read + evidence a record transferred. It is a SUBSET of the totals, read from the second aggregate, and it must land on the right counter.""" from datetime import datetime, timezone @@ -167,7 +167,7 @@ async def test_no_ids_short_circuits_without_a_query(): @pytest.mark.parametrize( "lookups, expected_source", [ - # A hit AT the exact file is the sync class (#2708); a hit from the + # A hit AT the exact file is the sync class; a hit from the # directory query is the reuse-shaped place arm. Both are un-scored, # and both must leave a usage trace under their own name. ([(1, "hit"), (2, "empty")], "write_path_sync"), @@ -177,9 +177,8 @@ async def test_no_ids_short_circuits_without_a_query(): ) async def test_unscored_location_arms_are_recorded(lookups, expected_source): """The location arms carry no score, so they have no home in retrieval_logs - — they surfaced snippets while leaving no trace anywhere. That was the - blocker #2082 recorded against this task; this is the assertion that it's - closed, per class.""" + — without a usage row they would surface snippets while leaving no trace + anywhere. Asserted per class.""" from scribe.services import plugin_context here = [{"id": 42, "title": "helper", "user_id": 1, "note_type": "snippet"}] @@ -237,15 +236,13 @@ async def test_auto_inject_records_what_survived_the_margin_gate(): def test_every_getter_that_can_be_surfaced_also_records_a_pull(): - """Rule #33 contract check — and the one that would have caught #2245. + """A contract check between the two halves of the usage pair. `surfaced` and `pulled` only mean something as a PAIR: the rate between them - is what #1038 and #2085 gate on. That pair is only closed if the tool which - OPENS a record reports it. `get_note` did, `get_snippet` did, `get_task` did - NOT — and auto-inject ranks kind-blind over a corpus that is overwhelmingly - tasks and issues, so the gap sat exactly where the volume is: every surfaced - task counted as never-pulled, dragging measured pull-through toward zero for - the menu's own dominant kind. + is what pull-through is read from. That pair is only closed if every tool + which OPENS a record reports it — and auto-inject ranks kind-blind over a + corpus that is mostly tasks and issues, so one getter that does not report + counts the menu's dominant kind as never-pulled. Asserted by source inspection rather than by calling the tools, because the failure is a MISSING call — which no behavioural test of the tool's return @@ -272,12 +269,11 @@ def test_every_getter_that_can_be_surfaced_also_records_a_pull(): # --- persistence (integration) -------------------------------------------- # Everything above mocks _schedule or the session — deliberately, for the hot -# path. But that left the two functions that actually touch the database -# (_insert_events and usage_for_notes' real SQL) running against real Postgres -# nowhere, which is how the deployed instance reported zero for every counter -# while surfacing demonstrably fired (#2663): all-green mocked units over a -# dead real path, the #2109 shape. These two run in the CI integration lane -# and split the chain so a failure names its half. +# path. Without these, the two functions that actually touch the database +# (_insert_events and usage_for_notes' real SQL) would run against real +# Postgres nowhere — all-green mocked units over a dead real path. These two +# run in the CI integration lane and split the chain so a failure names its +# half. async def _purge(note_id: int) -> None: @@ -309,7 +305,7 @@ async def test_insert_and_readout_roundtrip_on_real_postgres(_dispose_engine): "source": "mcp_get_snippet"}, ]) out = await usage_for_notes([nid]) - # write_path_place is a ranked choice; enter_project is ambient (#2477). + # write_path_place is a ranked choice; enter_project is ambient. assert out[nid]["surfaced_count"] == 1 assert out[nid]["ambient_count"] == 1 assert out[nid]["pull_count"] == 1 diff --git a/tests/test_plugin_version_mint.py b/tests/test_plugin_version_mint.py index 75a643e8..2769bd80 100644 --- a/tests/test_plugin_version_mint.py +++ b/tests/test_plugin_version_mint.py @@ -1,6 +1,6 @@ """The plugin's version is MINTED, and CI is the control that it moved. -WHAT THIS IS ABOUT (milestone 334 step 3). `plugin/` ships straight from this +WHAT THIS IS ABOUT. `plugin/` ships straight from this git repo — no build step, so no moment at which CI could stamp a version in. The value is therefore minted by a script before the commit, and CI's job is not to produce it but to prove it moved when it had to. @@ -11,15 +11,15 @@ from different clocks, on purpose: server image name from COMMIT time, ordering key from BUILD time plugin one value, from MINT time -#3127 §2 prescribes commit time so two lanes building one source report one +The server uses commit time so two lanes building one source report one string. The plugin has one lane and no build, so that reason does not reach it. "Let's make these consistent" is the obvious tidy-up and it breaks whichever artifact loses — which is why the difference is pinned here rather than only explained in a comment. The trade mint time makes — you cannot recompute the value from history, only -verify it moved — is acceptable ONLY because #3325 established that the -installer's refresh test is `===` with no ordering anywhere. Where a +verify it moved — is acceptable ONLY because the installer's refresh test +is `===` with no ordering anywhere. Where a comparator orders, an unreproducible version would be unverifiable too. """ import ast @@ -57,7 +57,7 @@ def fake_manifest(version: str = "2026.09.01.2252") -> str: @pytest.mark.parametrize("when,expected", [ - # THE midnight case, which #3127 checklist 10 names by hand. An unpadded + # THE midnight case. An unpadded # `%-H%M` renders this hour as `0` and silently shortens the string. (datetime(2026, 1, 5, 0, 0, tzinfo=timezone.utc), "2026.01.05.0000"), (datetime(2026, 1, 5, 0, 9, tzinfo=timezone.utc), "2026.01.05.0009"), @@ -150,7 +150,7 @@ def test_the_rewrite_refuses_TWO_version_lines(): def test_the_mint_script_is_version_relevant(): - """#3127 §3's asymmetry. A change to how the version is COMPUTED is + """The deriver's asymmetry. A change to how the version is COMPUTED is compared against nothing — leave the deriver out of the set and a format change never forces a re-mint, so the manifest keeps a value in the old format indefinitely and nothing says so.""" @@ -189,7 +189,7 @@ def run_check(here: str, there: str | None, changed_paths: list[str], monkeypatc def test_content_changed_and_the_version_did_not_FAILS(monkeypatch): - """#2209, exactly. The headline, and the only reason the check exists.""" + """The headline, and the only reason the check exists.""" failures = run_check( "2026.09.01.2252", "2026.09.01.2252", ["plugin/hooks/scribe_session_context.sh"], monkeypatch, @@ -209,7 +209,7 @@ def test_content_changed_and_the_version_moved_PASSES(monkeypatch): def test_a_version_that_is_not_the_canonical_shape_FAILS(monkeypatch): """`K4` returns the manifest string verbatim, so a malformed value is not rejected by the installer — it either sorts as an ordinary string or, when - unreadable, forces a reinstall every session. Neither is loud (#3325).""" + unreadable, forces a reinstall every session. Neither is loud.""" failures = run_check("0.1.48", "0.1.47", [], monkeypatch) assert len(failures) == 1 assert "not YYYY.MM.DD.HHMM" in failures[0] @@ -218,7 +218,7 @@ def test_a_version_that_is_not_the_canonical_shape_FAILS(monkeypatch): @pytest.mark.parametrize("bad", ["2026.9.1.2252", "2026.09.01.252", "2026.09.01"]) def test_an_UNPADDED_or_short_version_FAILS(bad, monkeypatch): """The padding is the contract, not cosmetics — one shape for every version - in the family (#3127 checklist 10).""" + in the family.""" assert run_check(bad, "2026.08.30.1200", [], monkeypatch) != [] @@ -250,7 +250,7 @@ def test_a_version_that_moved_with_no_content_change_is_NOT_a_failure(monkeypatc def test_a_failed_diff_FAILS_rather_than_passing_quietly(monkeypatch): """A check that cannot run must not report the same thing as a check that - passed — #2663's lesson, and the reason this file's siblings exist. + passed. `rev-parse` is stubbed to SUCCEED so only the diff fails. Failing every git call would trip the base-branch guard first and this would pass while @@ -280,13 +280,8 @@ def test_the_shipped_manifest_carries_a_minted_version(): def test_the_session_context_hook_still_reads_the_version_field(): - """The marker #2220 asked for. The value's SHAPE changed, not the field or - its reader — if this had to move, the derivation went somewhere it should - not have. - - The READER changed once, in #4107: `jq -r '.version'` became - `scribe_json_pick ... '.version'` when the hooks stopped depending on jq. - What this pins is unchanged — that the hook still reads `.version` out of - the manifest rather than deriving the string some other way.""" + """The hook reads `.version` out of the manifest rather than deriving the + string some other way — if this has to move, the derivation went + somewhere it should not have.""" hook = (check_plugin.HOOKS_DIR / "scribe_session_context.sh").read_text() assert re.search(r"scribe_json_pick[^\n]*'\.version'", hook) diff --git a/tests/test_rule_hint_band.py b/tests/test_rule_hint_band.py index dc0d0b45..9530b716 100644 --- a/tests/test_rule_hint_band.py +++ b/tests/test_rule_hint_band.py @@ -1,22 +1,20 @@ -"""An act surfaces a SET of rules, and rank decides how loudly (#3851). +"""An act surfaces a SET of rules, and rank decides how loudly. WHY THIS EXISTS -`RULEHINT_LIMIT` was 1. That was right while retrieval merely SUPPLEMENTED a -33-rule resident set — one salient rule beside everything already loaded. It -stops being right the moment milestone 394 removes residency, because then -this arm is the whole delivery, and `git push origin dev` is governed by -rules 1, 2, 9 and 140 at once, each of which alone permits the mistake the -others catch. +This arm is the whole delivery of rules to an act, and one command can be +governed by several rules at once — a `git push` by where to push, what not +to push, who polls CI and what to wait for — each of which alone permits the +mistake the others catch. One slot cannot serve that. Two instruments, and they answer different questions: - `_rule_band` decides HOW MANY. A fixed k fills its slots whether or not anything deserves them; a band keeps only what scored close to the top, so one clearly-relevant rule still shows one. - - `compact` decides HOW LOUD. Measured at #3851: a full line is ~143 tokens - once the trigger is rendered, so five of them cost ~646 before every Bash - call. Top-full-plus-references costs ~198. + - `compact` decides HOW LOUD. A full line is ~143 tokens once the trigger + is rendered, so five of them cost ~646 before every Bash call. + Top-full-plus-references costs ~198. WHAT THIS PINS @@ -96,7 +94,7 @@ def test_a_flat_cluster_surfaces_together(): def test_the_band_is_computed_from_scores_not_from_the_ledger(): - """A seen top hit still anchors the band (#3750 x #3851). + """A seen top hit still anchors the band. `_rule_band` never learns what the session has seen — dedup happens after it, in the arms. Pinned here because the tempting "reorder so a fresh rule @@ -124,7 +122,7 @@ def test_the_leading_line_carries_the_trigger(seen): def test_a_later_line_cites_its_rule_without_quoting_the_trigger(seen): """Rank > 0 is a reference: identity and pointer, no trigger. - The trigger is the expensive half — 300-400 characters after #3855 — and + The trigger is the expensive half — often 300-400 characters — and the leading line has already demonstrated the shape. Both assertions matter: dropping the trigger is the saving, and keeping `get_rule(id)` is what makes the saving safe, because a cited rule the reader cannot pull is @@ -140,12 +138,11 @@ def test_a_later_line_cites_its_rule_without_quoting_the_trigger(seen): def test_shortening_a_line_does_not_decide_what_it_says_about_holding(): - """A reference still tells a repeat from a first surfacing (#3750 x #3851). + """A reference still tells a repeat from a first surfacing. - This is the regression the first cut of #3851 actually shipped: the - compact branch dropped the tail along with the trigger, so a rule the - session had already been told read exactly like one it had not. #3750's - whole argument is that the two are different claims — a repeat is rendered + A compact branch that drops the tail along with the trigger makes a rule + the session had already been told read exactly like one it had not. The + two are different claims — a repeat is rendered precisely because the session may no longer HOLD what it was told — and the tail is the entire difference a reader can act on. @@ -153,10 +150,8 @@ def test_shortening_a_line_does_not_decide_what_it_says_about_holding(): a fact about its rank; what the session holds is a fact about the ledger; and neither may be allowed to answer the other's question. - THREE STATES SINCE #4100, so this checks three. The axis grew and the test - grew with it — pinning only two would leave the compact branch free to - collapse the new middle state into either neighbour, which is the same - regression this was written for with one more place to hide. + THREE STATES, so this checks three. Pinning only two would leave the + compact branch free to collapse the middle state into either neighbour. """ rule = fake_rule(id=4, title="dev is home", when_to_apply=_TRIGGER) fresh = _rule_hint_line(rule, where="here", seen=False, compact=True) @@ -182,8 +177,8 @@ def test_a_compact_line_is_materially_shorter_than_a_full_one(): if a later edit puts the trigger back into the compact branch. Measured against a REALISTIC trigger, because that is where the saving - lives: the rules this arm carries run 300-400 characters of trigger after - #3855, and a toy one-line trigger would make this pass on a compact branch + lives: the rules this arm carries run 300-400 characters of trigger, and + a toy one-line trigger would make this pass on a compact branch that had stopped saving anything. """ long_trigger = ( @@ -200,7 +195,7 @@ def test_a_compact_line_is_materially_shorter_than_a_full_one(): def test_a_preference_keeps_its_noun_when_compact(): - """Force survives the shortening (#3849). + """Force survives the shortening. `kind` and `compact` are independent axes. A preference rendered as a reference must still not read as a rule — the noun is the whole of the diff --git a/tests/test_services_backup.py b/tests/test_services_backup.py index cc280d40..2ee25d5a 100644 --- a/tests/test_services_backup.py +++ b/tests/test_services_backup.py @@ -51,7 +51,7 @@ def _exportable_note(**over): def test_note_rows_carry_the_verification_trio(): """Operator judgment — somebody checked this fact, and this is when — - which nothing downstream can recompute (milestone 317).""" + which nothing downstream can recompute.""" [row] = backup._note_rows([_exportable_note( verify_with="curl the AMO docs", expires_when="AMO starts allowing re-signing", @@ -75,10 +75,10 @@ def test_a_never_checked_note_exports_a_null_stamp_and_restores_as_one(): def test_the_record_type_and_kind_survive_the_export(): - """#3182's headline. Without these two columns a restore reported success - and handed back a corpus where all 90 snippets and 3 processes were plain - notes and all 435 issues and the spike were `work` — the entire vocabulary - milestone 312 and #3128 were about, gone, with nothing to notice it by.""" + """Without these two columns a restore reports success and hands back a + corpus where every snippet and process is a plain note and every issue + and spike is `work` — the record vocabulary gone, with nothing to notice + it by.""" [snippet] = backup._note_rows([_exportable_note(note_type="snippet")]) [issue] = backup._note_rows([_exportable_note(task_kind="issue", status="done")]) assert snippet["note_type"] == "snippet" @@ -105,7 +105,7 @@ def test_provenance_and_lifecycle_travel(): def test_a_milestone_carries_its_plan(): """A milestone IS the plan (0066); `body` is its design and intent and `description` is only the one-line summary. Dropping it restored every - plan as a title with no reasoning behind it (#3182).""" + plan as a title with no reasoning behind it.""" m = SimpleNamespace( id=1, user_id=7, project_id=2, title="t", description="d", body="## Goal\n\nthe actual plan", status="active", order_index=0, @@ -117,7 +117,7 @@ def test_a_milestone_carries_its_plan(): def test_a_repo_binding_carries_the_branch_its_ledger_follows(): - """#2873. Without `ref` a restored binding silently falls back to the + """Without `ref` a restored binding silently falls back to the default branch and the shape ledger starts accounting for a different tree — a wrong answer that looks like a working one.""" b = SimpleNamespace(user_id=7, project_id=2, repo_key="Scribe", ref="dev") @@ -265,7 +265,7 @@ def _stand_in(model): @pytest.mark.parametrize("table", sorted(_column_guard_targets())) def test_every_column_is_exported_or_declared_excluded(table): - """THE COLUMN GUARD (#3182) — _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 a COLUMN going missing from a table it already considers covered, which is @@ -379,7 +379,7 @@ def _everything_maps(row: dict) -> "backup._Maps": @pytest.mark.parametrize("table", sorted(_import_guard_targets())) def test_every_exported_column_is_imported_or_declared_excluded(table): - """THE COLUMN GUARD, THE OTHER WAY (#4197). + """THE COLUMN GUARD, THE OTHER WAY. The export guard above makes a dropped column unexpressible on the way OUT. Nothing watched the way back IN, and that is the worse half: an @@ -435,9 +435,6 @@ def test_an_unmappable_project_degrades_a_usage_event_and_skips_a_shape_event(): the project is optional by design, null already means "not reported", and dropping the row would lose a real pull and deflate the very pull-through the table exists to report. - - Until #4197 this was asserted by reading the source of `_restore_v2` with - `inspect.getsource`, because there was no unit to call. Now there is. """ maps = backup._Maps() maps.notes[5] = 55 @@ -467,8 +464,7 @@ def test_a_shapes_divergence_pointer_is_remapped_not_carried(): """`diverges_from` is a FK to notes.id, like `snippet_id`. Carrying the source id would point at whatever snippet took that number in the destination — wrong rather than missing, and nothing downstream could - tell. It was dropped entirely until #4197; restoring it raw would have - been the worse fix.""" + tell. Restoring it raw would be worse than dropping it.""" maps = backup._Maps() maps.projects[1] = 11 maps.notes[4] = 44 @@ -507,14 +503,14 @@ def test_the_column_guard_covers_every_table_with_a_row_helper(): def test_not_included_lists_the_known_gaps(): # The deferred tables must be surfaced explicitly, not silently dropped. # forge_connections is excluded as CREDENTIALS (api_keys reasoning): a - # backup that carries forge tokens is a token-exfiltration file (#2778). + # backup that carries forge tokens is a token-exfiltration file. for table in ("groups", "project_shares", "note_shares", "api_keys", "note_embeddings", "retrieval_logs", "forge_connections"): assert table in backup._NOT_INCLUDED def test_every_table_is_either_backed_up_or_explicitly_excluded(): - """THE GUARD (#2293), and the only shape of test that catches an ABSENCE. + """THE GUARD, and the only shape of test that catches an ABSENCE. A new table gets a model and a migration — both fail loudly if wrong — and then silently never gets a backup section. No error, no warning, and a @@ -599,11 +595,11 @@ async def test_export_full_backup_contains_every_declared_section(): "system_usage_events", # v23: proposals and judgments about a rule's moments. "rule_moment_judgments", - # v25: family canon (milestone 463). + # v25: family canon. "platforms", "project_platforms", "family_ideas", "family_idea_platforms", "family_idea_references", "family_adoptions", "family_decisions", - # v26: the Profile page (#5879). + # v26: the Profile page. "user_profiles"): assert key in out, f"missing export section: {key}" assert out[key] == [] @@ -625,7 +621,7 @@ def test_supersession_rows_serialise_the_pair(): def test_rule_rows_carry_the_verification_fields(): """A rule's check must survive a backup. - `verify_with`/`expires_when`/`verified_at` (milestone 312) say whether a + `verify_with`/`expires_when`/`verified_at` say whether a rule is a fact that can go false and when it was last confirmed. A backup that drops them restores a rulebook that has forgotten which of its rules can rot — the exact blindness the fields were added to end. @@ -647,9 +643,8 @@ def test_rule_rows_carry_the_verification_fields(): assert out["verify_with"] == "cat some/file" assert out["expires_when"] == "the file grows a shell" assert out["verified_at"] == checked.isoformat() - # Provenance was exported from 0088 onward but silently dropped on the way - # back IN until milestone 312. Export side asserted here; the restore side - # remaps it through note_id_map. + # Provenance is exported here; the restore side remaps it through + # note_id_map. assert out["arose_from_id"] == 99 diff --git a/tests/test_services_plugin_context.py b/tests/test_services_plugin_context.py index 0d440870..943e437e 100644 --- a/tests/test_services_plugin_context.py +++ b/tests/test_services_plugin_context.py @@ -88,7 +88,7 @@ async def test_build_autoinject_hint_titles_only_with_margin_gate(): # 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, the query that displaced it was not (#2463). + # retrieval_logs, and 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"] @@ -127,7 +127,7 @@ async def test_build_session_context_includes_project_when_scoped(): # No design system on the project -> no design block at all. An install with # none is the ordinary case, not a degraded one. assert "## Design system" not in out["context"] - # Live state only (decision #4027): how to work with Scribe is the + # Live state only: how to work with Scribe is the # using-scribe skill's to say. A restated reflex here is a copy that drifts. assert 'content_type="rule"' not in out["context"] assert "Reflex:" not in out["context"] @@ -140,8 +140,8 @@ def test_a_short_goal_is_shown_whole(): def test_a_long_goal_is_cut_at_a_word_and_says_where_the_rest_is(): - """#4036: a raw slice ended mid-word with nothing marking the cut, so a - reader took half a sentence for the whole goal.""" + """A raw slice ends mid-word with nothing marking the cut, and a reader + takes half a sentence for the whole goal.""" from scribe.services.plugin_context import _GOAL_CHARS, _goal_line goal = "make the record reach the next session\nso a solution is recalled " * 10 line = _goal_line(goal, 2) @@ -190,7 +190,7 @@ async def test_build_session_context_pushes_the_projects_design_system(): assert "resolve_design_system(9)" in ctx assert "get_design_system_stylesheet(9)" in ctx # The prose pointer names the call that has the MERGED guidance, not - # enter_project, which carries only the summary now (#4045). + # enter_project, which carries only the summary. assert "`get_design_system(9)` → `resolved_guidance`" in ctx @@ -230,8 +230,8 @@ async def test_build_session_context_unbound_repo_emits_bind_hint(): @pytest.mark.asyncio async def test_a_project_id_that_does_not_resolve_is_reported_not_dropped(): - """A `.scribe` marker names a project directly (#4085), so for the first - time a caller arrives holding a pointer it BELIEVES in. If the id is for + """A `.scribe` marker names a project directly, so a caller arrives + holding a pointer it BELIEVES in. If the id is for another instance, or names a deleted project, the session has to be told — it cannot infer it from an absence. @@ -306,20 +306,18 @@ async def test_build_process_manifest_truncates_long_preview(): @pytest.mark.asyncio async def test_build_session_context_caps_length(): - """The cap still binds, and now has to be provoked rather than tripped. + """The cap binds, and has to be provoked rather than tripped. - This used to hand the block 200 long rule titles, because the preload made - overflow the easy case. Milestone 394 removed that block, so nothing this - function assembles on its own is big enough any more — which is a reason - to drive the cap directly, not a reason to drop it. The project and design + Nothing this function assembles on its own is ordinarily big enough to + overflow — which is a reason to drive the cap directly, not a reason to + drop it. The project and design blocks are still unbounded in principle, and the hook passes this text through verbatim. Patching the cap rather than manufacturing 9,000 characters keeps the test about the TRUNCATION PATH — that it cuts, and that it says it cut — which is the part a reader depends on. The unbound-repo hint supplies the text: - since milestone 410 the block carries live state only, and a bare session - is a one-liner. + the block carries live state only, and a bare session is a one-liner. """ from scribe.services import plugin_context as pc @@ -333,7 +331,7 @@ async def test_build_session_context_caps_length(): ) -# --- the reuse slot (#2246) -------------------------------------------------- +# --- the reuse slot --------------------------------------------------------- _CFG = {"enabled": True, "threshold": 0.55, "top_k": 3} @@ -348,7 +346,7 @@ def _asked_for_reuse(calls: list[dict]) -> bool: async def _autoinject(main_hits, reuse_hits, cfg=None, lesson_hits=None): """Run build_autoinject_hint with each semantic query stubbed by the kinds it asks for: the unscoped pool, the reserved reuse query, and the reserved - lesson query (milestone 385 step 5). + lesson query. ROUTED ON THE REQUESTED KINDS, not on call order, and that is the point of the helper. Order-keyed stubbing was fine while there was one reserved @@ -458,7 +456,7 @@ async def test_a_process_counts_as_reuse_too(): assert not _asked_for_reuse(calls2) -# --- write-path widened beyond snippets (#2246, the mirror half) ------------- +# --- write-path prior art beyond snippets ------------------------------------ @pytest.mark.asyncio async def test_write_path_semantic_arm_asks_for_experience_not_just_snippets(): @@ -517,7 +515,7 @@ async def test_write_path_labels_a_non_snippet_hit_with_its_kind(): ) ctx = out["context"] - # The issue says what it is — and, since #4154, where it stands: a piece of + # The issue says what it is — and where it stands: a piece of # prior art offered as "already tried" reads differently when it is still open. assert "· issue (todo)]" in ctx assert '[similar 0.72] "debounce helper"' in ctx # the snippet does not @@ -525,19 +523,18 @@ async def test_write_path_labels_a_non_snippet_hit_with_its_kind(): assert "get_task(id)" in ctx and "get_snippet(id)" in ctx -# ── The config stand-in cannot fall behind the real one (#4214) ─────────── +# ── The config stand-in cannot fall behind the real one ─────────────────── @pytest.mark.asyncio async def test_the_config_stand_in_carries_every_key_the_real_one_does(): - """THE GUARD `writepath_cfg`'s DOCSTRING ALREADY CLAIMED AND DID NOT HAVE. + """THE GUARD `writepath_cfg`'s DOCSTRING CLAIMS. Three arms read their numbers out of that dict inside a fail-open `except`, so a missing key does not raise where anyone can see it — the arm silently becomes a no-op, which is indistinguishable from the arm working and finding nothing. The helper derives its SURFACE keys from the - registry to prevent exactly that, and then #4214 added a key that is - deliberately not a surface: the whole derivation missed it, ten tests went - red at once, and the diagnosis cost a CI round. + registry to prevent exactly that, and a key that is deliberately not a + surface slips past the derivation unless the key sets are compared. Asserting the KEY SETS match, not the values: the stand-in exists to let a test set different numbers. @@ -559,7 +556,7 @@ async def test_the_config_stand_in_carries_every_key_the_real_one_does(): ) -# ─── the passage that matched travels onto the menu (#4243, #4250) ─────────── +# ─── the passage that matched travels onto the menu ────────────────────────── @pytest.mark.asyncio diff --git a/tests/test_shape_form_gate.py b/tests/test_shape_form_gate.py index 0590e1f9..875335a7 100644 --- a/tests/test_shape_form_gate.py +++ b/tests/test_shape_form_gate.py @@ -1,16 +1,15 @@ -"""A canon may only be urged on a shape that could plausibly BE it (#4204). +"""A canon may only be urged on a shape that could plausibly BE it. -WHAT WAS WRONG. `dominant_canon` is a base rate: it answers "what is most -common in this directory" and never "is this that". With `kind` carrying only -`css | sym`, a frozen dataclass, a module constant, a sync predicate, a class -and an async service function were all siblings, so the prior was the entire -argument — it told a registry module of pure helpers to build from the -`async_session` service canon. Upstream, the auto-stamp had no resemblance -floor at all (`elif sid in resembles`), so 0.69 asserted as confidently as -0.95, and one payload-level score was applied to every symbol in the file. +WHY. `dominant_canon` is a base rate: it answers "what is most common in this +directory" and never "is this that". With `kind` carrying only `css | sym`, a +frozen dataclass, a module constant, a sync predicate, a class and an async +service function are all siblings, so the prior is the entire argument — it +would tell a registry module of pure helpers to build from an +`async_session` service canon. An auto-stamp without a resemblance floor +asserts 0.69 as confidently as 0.95. The two halves compound: loose stamping manufactures the density that the -divergence check then reads as authority. Both are fixed by the same +divergence check then reads as authority. Both are held by the same primitive, and it is tested here on REAL signatures taken from the ledger — Python, Go, TypeScript and Vue — rather than invented ones, because a classifier that only works on the examples its author imagined is the failure @@ -75,9 +74,9 @@ def test_css_is_its_own_form_regardless_of_signature() -> None: assert shape_form("", "css") == "css" -def test_the_five_shapes_that_started_this_are_not_service_functions() -> None: - """The concrete case. #2860 is an `async def` service unit; none of these - is one, and all five were told to build from it.""" +def test_a_registry_modules_helpers_are_not_service_functions() -> None: + """The concrete case. The canon is an `async def` service unit; none of + these is one.""" canon = shape_form("async def create_note(user_id: int, ...):", "sym") assert canon == "async-fn" for sig in ( @@ -90,27 +89,25 @@ def test_the_five_shapes_that_started_this_are_not_service_functions() -> None: assert not forms_agree(shape_form(sig, "sym"), canon), sig -def test_how_many_of_the_five_the_divergence_gate_actually_silences() -> None: +def test_the_divergence_gate_silences_only_a_family_contradiction() -> None: """HONEST ACCOUNTING, and it is deliberately an assertion rather than a comment so it fails the day the answer changes. Divergence gates at FAMILY level and only on contradiction, because a form mismatch is the PREMISE of a divergence prompt, not an objection to it — - see `shape_family`. So of #4204's five false prompts this silences one: - `Point` is a type and the canon is a callable. The four `def` helpers are - callables like the canon and still ask. + see `shape_family`. So of these five this silences one: `Point` is a type + and the canon is a callable. The four `def` helpers are callables like the + canon and still ask. That is not a shortcoming of the gate, it is the limit of the signature: - at this level those four are indistinguishable from #2793's acceptance - case, where a sync `confirmDanger` beside an async confirm helper SHOULD - be flagged. + at this level those four are indistinguishable from the divergence + readout's acceptance case, where a sync `confirmDanger` beside an async + confirm helper SHOULD be flagged. - CORRECTED 2026-09-21 (#4208). This used to say separating them needs - "widen `kind` or a comparison of meaning", offering the two as - alternatives. Widening `kind` does not separate them: it buys the silence - by bucketing `fn` apart from `async-fn`, which takes the async canon out - of a sync candidate's denominator and silences #2793's acceptance case by - the identical mechanism, one layer down. Whatever separates these four + Widening `kind` does not separate them: it buys the silence by bucketing + `fn` apart from `async-fn`, which takes the async canon out of a sync + candidate's denominator and silences the acceptance case by the identical + mechanism, one layer down. Whatever separates these four has to distinguish a helper that does the canon's JOB from one that does not, and no signature carries that. A comparison of meaning is the only lever, not one of two.""" @@ -130,10 +127,10 @@ def test_how_many_of_the_five_the_divergence_gate_actually_silences() -> None: def test_a_sync_function_beside_an_async_canon_is_still_a_fair_question() -> None: - """#2793's acceptance case, which the first version of this change broke: - gating divergence on `forms_agree` silenced a hand-rolled sync confirm in - a directory where an async confirm helper is canon — the exact prompt the - milestone exists to produce.""" + """The divergence readout's acceptance case: gating divergence on + `forms_agree` would silence a hand-rolled sync confirm in a directory + where an async confirm helper is canon — the exact prompt the readout + exists to produce.""" canon = shape_form("async function onTrash() {", "sym") mine = shape_form("function confirmDanger() {", "sym") assert canon == "async-fn" and mine == "fn" @@ -185,8 +182,8 @@ def test_a_canon_whose_instances_agree_asserts_that_form() -> None: def test_a_canon_whose_instances_disagree_asserts_nothing() -> None: - """Portal's #3283 exactly: one snippet recorded as canon for a class, a - plain def, an async def and a dozen tests. That spread is the SYMPTOM of + """One snippet recorded as canon for a class, a plain def, an async def + and a dozen tests. That spread is the SYMPTOM of loose stamping, so reading it as "no opinion" makes the two fixes cooperate — a poisoned canon falls silent instead of flagging others.""" rows = [_Row("class A:"), _Row("def b():"), _Row("async def c():")] @@ -334,7 +331,7 @@ def test_the_resemblance_floor_is_above_the_retrieval_floors() -> None: assert _RESEMBLE_MIN >= 0.80 -# ── The denominator, not the gate (#4208) ───────────────────────────────── +# ── The denominator, not the gate ───────────────────────────────────────── # # `dominant_canon` is a base rate, and a base rate is only a statement about # something if its denominator is a population somebody asked a question @@ -369,7 +366,7 @@ def test_a_callable_is_not_counted_against_a_dataclass() -> None: def test_the_async_canon_stays_in_a_sync_candidates_denominator() -> None: - """THE ACCEPTANCE CASE OF #2793, and the reason this narrows by family + """THE DIVERGENCE READOUT'S ACCEPTANCE CASE, and the reason this narrows by family rather than by form. A hand-rolled sync `confirmDanger` in a directory where an async confirm @@ -421,13 +418,13 @@ def test_a_family_passed_where_a_form_belongs_would_be_a_silent_no_op() -> None: assert by_form != by_family -def test_the_four_survivors_still_prompt() -> None: +def test_callable_helpers_beside_a_callable_canon_still_prompt() -> None: """HONEST ACCOUNTING, matching the sibling test above. - The narrowed denominator does not silence #4204's four `def` helpers, and - was never going to: they are callables, the canon is a callable, so - nothing is excluded from their count. What changes is that the count is - now over comparable things. Asserted so the claim cannot quietly rot into + The narrowed denominator does not silence the four `def` helpers, and + cannot: they are callables, the canon is a callable, so nothing is + excluded from their count. What it buys is that the count is over + comparable things. Asserted so the claim cannot quietly rot into "this fixed it". """ rows = [_Row(SERVICE) for _ in range(6)] + [_Row("class Point:", snippet_id=9)] diff --git a/tests/test_shape_ledger.py b/tests/test_shape_ledger.py index 61a3ef32..085097a0 100644 --- a/tests/test_shape_ledger.py +++ b/tests/test_shape_ledger.py @@ -1,9 +1,8 @@ -"""The shape ledger's schema contract (#2787, milestone 294, note 2786). +"""The shape ledger's schema contract. -Step 1 pins the model: identity, the classification vocabulary, and the -token-free serialisation. The sync pass (step 2) and the classification -surface (step 3) grow their tests here; DB-backed behavior lands in the -integration lane once there is behavior to exercise. +The model: identity, the classification vocabulary, and the token-free +serialisation; then the pure halves of the sync pass and the classification +surface. DB-backed behaviour is in the integration lane. """ import pytest @@ -47,7 +46,7 @@ def test_snippet_reference_survives_snippet_deletion_as_null(): def test_status_queries_have_an_index(): - """list_shapes(status=unclassified) is THE todo query (step 3) — it must + """list_shapes(status=unclassified) is THE todo query — it must not degrade into a project-wide scan as ledgers reach thousands of rows.""" names = {ix.name for ix in CodeShape.__table__.indexes} assert "ix_code_shapes_project_status" in names @@ -181,7 +180,7 @@ def test_signature_similarity_blanks_the_names(): def test_a_model_with_a_different_mixin_list_is_proposed_against_the_model_canon(): - """The band 0.8 cut (#4306). Measured over judged instances: a model class + """The band 0.8 cut. Measured over judged instances: a model class whose mixins differ from the canon's scores ~0.795 against it — a true instance, and the proposal is read before it is confirmed, so the floor must admit it.""" @@ -324,7 +323,7 @@ def test_derive_groups_copy_before_name_with_floors(): ("d.css", "css", "btn", "s1"), ("e.css", "css", "btn", "s2"), ("f.css", "css", "btn", "s3"), ("g.py", "sym", "main", "s4"), ("h.py", "sym", "main", "s5"), # only 2 files → no name group ("i.py", "sym", "one", "s6"), - # CSS (note 2917): identical bodies under different names are NOT a + # CSS: identical bodies under different names are NOT a # copy — two meanings sharing the style system's look; the same # class in two files IS a family (the name floor is 2 for css). ("j.css", "css", "muted", "same"), ("k.css", "css", "pin-auto", "same"), @@ -344,7 +343,7 @@ def test_derive_groups_copy_before_name_with_floors(): def test_derive_new_summary_counts_copies_first_seen_since_the_previous_refresh(): - """#2899: the arrival-moment drift signal — derive-grouped rows created + """The arrival-moment drift signal — derive-grouped rows created after the previous refresh's stamp, newest first, judged rows and a first seed (since=None) never count.""" from datetime import datetime, timedelta, timezone @@ -376,8 +375,8 @@ def test_derive_new_summary_counts_copies_first_seen_since_the_previous_refresh( def test_proposal_summary_ranks_body_identical_groups_first_and_sees_scoped_rows(): - """#2872: dup groups (the real copies) outrank name groups (usually - convention), wider spread first; #2869: scoped rows are in the readout.""" + """Dup groups (the real copies) outrank name groups (usually + convention), wider spread first; scoped rows are in the readout.""" from scribe.models.code_shape import CodeShape from scribe.services.shape_ledger import proposal_summary @@ -396,7 +395,7 @@ def test_proposal_summary_ranks_body_identical_groups_first_and_sees_scoped_rows row("v/J.vue", "closed-msg", "dup:abc", status="exempt"), ] out = proposal_summary(rows) - # Milestone 302: with consumer paths in hand, each css group says what + # With consumer paths in hand, each css group says what # renders it — distinct files across the members; absent otherwise. assert "consumers" not in out["derive_groups"][0] for i, r in enumerate(rows): # unsaved rows have no id; give them one @@ -429,7 +428,7 @@ def test_proposer_tools_are_mounted(): assert mcp._tool_manager.get_tool("confirm_shape_proposals") is not None tool = mcp._tool_manager.get_tool("list_shapes") assert "proposal" in tool.parameters.get("properties", {}) - # #2868: the audit surfaces — compact pages and the sweep form. + # The audit surfaces — compact pages and the sweep form. assert "compact" in tool.parameters.get("properties", {}) rule = mcp._tool_manager.get_tool("classify_shapes_by_rule") assert rule is not None @@ -437,7 +436,7 @@ def test_proposer_tools_are_mounted(): assert name in rule.parameters.get("properties", {}), name -# --- #2868: the bulk surfaces (pure) ----------------------------------------- +# --- the bulk surfaces (pure) ----------------------------------------------- def test_compact_row_carries_identity_standing_and_the_proposers_word_only(): @@ -461,7 +460,7 @@ def test_compact_row_carries_identity_standing_and_the_proposers_word_only(): def test_resolve_consumers_prefers_the_own_file_and_fans_out_for_shared_names(): - """Milestone 302: a class named in a template resolves to that file's + """A class named in a template resolves to that file's OWN row when it defines the class (a scoped rule, consumed by its own markup); otherwise to every other definition of the name — one shared sheet, or all of several (the map fans out rather than guessing).""" @@ -490,7 +489,7 @@ def test_resolve_consumers_prefers_the_own_file_and_fans_out_for_shared_names(): def test_resolve_consumers_credits_every_row_a_prefix_could_have_built(): - """#2970: `status-${s}` names a class the map cannot pin down, so the + """`status-${s}` names a class the map cannot pin down, so the prefix reference `status-*` credits every row whose symbol starts with the head — each under the same own-file-else-fan-out rule. Crediting all of them is the honest reading: the alternative is calling live rules @@ -533,7 +532,7 @@ def test_consumer_edges_table_cascades_with_the_shape(): def test_uses_edges_table_and_validation(): - """#2870: consumption is its own relation — a table that cascades with + """Consumption is its own relation — a table that cascades with both ends, and `uses` on a classification must be a list of ids.""" from scribe.models import Base from scribe.models.code_shape import USE_BASES, CodeShapeUse @@ -562,7 +561,7 @@ def test_reference_canons_names_every_used_canon_not_just_the_best(): def test_reason_codes_are_a_fixed_catalogue_and_validated(): - """#2874: an optional index beside the prose reason; unknown codes are a + """An optional index beside the prose reason; unknown codes are a structural error (the batch applies nothing).""" from scribe.models.code_shape import REASON_CODES from scribe.services.shape_ledger import validate_classifications