docs(6209): coverage, guidance-ownership, shape, warnings tests and shared fixtures
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m12s
CI & Build / Python tests (push) Successful in 1m56s
CI & Build / Build & push image (push) Successful in 13s

test_pattern_coverage, test_guidance_ownership, test_integration_shape_classify,
test_instruction_surfaces_agree, test_retrieval_warnings, helpers.py and
conftest.py state the property each test or fixture holds. The issue and
milestone numbers, consolidation dates and one-install measurements that
recorded how each was found are dropped; fixture dates and ids stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 21:21:52 -04:00
co-authored by Claude Opus 5.5
parent 8518afb54d
commit 91c96a8b8a
7 changed files with 199 additions and 256 deletions
+30 -31
View File
@@ -1,4 +1,4 @@
"""Real-Postgres integration tests for shape classification (#2789).
"""Real-Postgres integration tests for shape classification.
What mocks can't prove: the all-or-nothing batch against real rows, the
write-ACL gate, the todo query's filters, and the consumer map riding
@@ -96,7 +96,7 @@ async def test_classify_applies_judgments_and_reports_unmatched(seeded):
@pytest.mark.integration
async def test_a_bad_batch_applies_nothing(seeded):
"""All-or-nothing (#2709's lesson): a caller must never learn later that
"""All-or-nothing: a caller must never learn later that
half a batch silently happened."""
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
with pytest.raises(ValueError) as err:
@@ -131,7 +131,7 @@ async def test_classification_is_write_gated_and_listing_read_gated(seeded):
@pytest.mark.integration
async def test_rule_form_sweeps_unclassified_rows_only_and_applies_whole(seeded):
"""#2868: one judgment over a directory + glob; judged rows are left
"""One judgment over a directory + glob; judged rows are left
alone unless include_judged; the same gates as the row form."""
owner, other, pid, sid = seeded["owner"], seeded["other"], seeded["pid"], seeded["snippet"]
await classify_shapes(owner, pid, [
@@ -171,7 +171,7 @@ async def test_rule_form_sweeps_unclassified_rows_only_and_applies_whole(seeded)
@pytest.mark.integration
async def test_sync_stamps_scoped_rows_and_unstamps_when_they_become_reachable(seeded):
"""#2869: by-construction one-offs arrive `scoped` (mechanical), count as
"""By-construction one-offs arrive `scoped` (mechanical), count as
accounted, are reached by the sweep's default, and go back to the todo
if a later tree makes them ordinary. A judgment overrides the stamp."""
from scribe.services.coverage import ArchiveShape
@@ -203,7 +203,7 @@ async def test_sync_stamps_scoped_rows_and_unstamps_when_they_become_reachable(s
@pytest.mark.integration
async def test_uses_edges_are_the_consumer_map(seeded):
"""#2870: a shape keeps ONE snippet_id (what it is) and any number of
"""A shape keeps ONE snippet_id (what it is) and any number of
uses edges (what it calls); the snippet's consumer map lists them,
list_shapes(uses=N) finds them, and a sweep can write them."""
from scribe.services import snippets as snippets_svc
@@ -296,7 +296,7 @@ async def test_get_snippet_carries_the_structured_consumer_map(seeded):
@pytest.mark.integration
async def test_sync_refiles_rows_whose_snippet_was_purged(seeded):
"""The SET NULL companion (#2787): a judgment whose target is hard-deleted
"""The SET NULL companion: a judgment whose target is hard-deleted
rejoins the todo on the next sync instead of dangling target-less."""
owner, pid, sid = seeded["owner"], seeded["pid"], seeded["snippet"]
await classify_shapes(owner, pid, [
@@ -318,12 +318,12 @@ async def test_sync_refiles_rows_whose_snippet_was_purged(seeded):
assert row.snippet_id is None
# --- #2791: the write-path feed lands hook evidence as rows -------------------
# --- the write-path feed lands hook evidence as rows --------------------------
@pytest.mark.integration
async def test_write_path_evidence_is_a_proposal_never_a_verdict(seeded):
"""Milestone 439. Pulled + referenced → every named shape of the
"""Pulled + referenced → every named shape of the
snippet's kind CARRIES the snippet as a proposal (basis "reference"),
and its status is untouched: the agent that wrote it says what it is.
A judgment is never touched, not even by a proposal. The outsider
@@ -465,7 +465,7 @@ async def test_recent_pulls_reads_the_usage_stream(seeded):
assert sid + 2000 not in pulls
# --- #2792: the mechanical proposer against real rows -------------------------
# --- the mechanical proposer against real rows -------------------------------
def _quiet_semantic():
@@ -594,7 +594,7 @@ async def test_derive_groups_land_on_rows_and_in_the_summary(seeded):
summary = proposal_summary(await live_rows(pid))
assert summary["proposed"] == 1
# #2872: the body-identical group (a real copy) outranks the bigger name
# The body-identical group (a real copy) outranks the bigger name
# group (usually convention), even at size 2 vs 3.
order = [g["group"] for g in summary["derive_groups"]]
assert order[0].startswith("dup:") and order[1] == "name:css:card"
@@ -605,7 +605,7 @@ async def test_derive_groups_land_on_rows_and_in_the_summary(seeded):
# One of the css copies gets judged → the group shrinks on the next pass
# but stays a family: a class in two files is already a recipe living in
# two places (css name floor 2, note 2917). Judge the second and it's gone.
# two places (css name floor 2). Judge the second and it's gone.
await classify_shapes(owner, pid, [
{"path": "b/z.css", "symbol": "card", "status": "exempt", "reason": "print sheet"},
])
@@ -623,8 +623,8 @@ async def test_derive_groups_land_on_rows_and_in_the_summary(seeded):
@pytest.mark.integration
async def test_write_time_derive_names_the_family_or_the_canon_for_a_name(seeded):
"""#2900: against real rows — a name in a family → the family (other
files, count; for CSS a NAME family, never a body one — note 2917); a
"""Against real rows — a name in a family → the family (other files,
count; for CSS a NAME family, never a body one); a
name whose canonical row lives elsewhere → that canon; a judged row at
the path, the canon's own file, or an unknown name → silence."""
from scribe.services.shape_ledger import apply_derive_groups, write_time_derive
@@ -667,7 +667,7 @@ async def test_write_time_derive_names_the_family_or_the_canon_for_a_name(seeded
@pytest.mark.integration
async def test_derive_new_names_the_copy_that_joined_a_family_since_the_stamp(seeded):
"""#2899: the first sync seeds one `slug`; a later sync adds an identical
"""The first sync seeds one `slug`; a later sync adds an identical
copy. Against the stamp between them, derive_new counts ONLY the
newcomer — the drift since the last refresh, not the whole family."""
from datetime import datetime, timezone
@@ -700,7 +700,7 @@ async def test_derive_new_names_the_copy_that_joined_a_family_since_the_stamp(se
@pytest.mark.integration
async def test_transition_and_prefix_references_clear_the_unused_css_flag(seeded):
"""#2970 end to end: the two class forms a template never spells out —
"""End to end: the two class forms a template never spells out —
a transition `name=` and a concatenated name — travel from the real
extractor through resolution into the flag, so rules they reach stop
being reported as unused. Only a rule nothing can reach stays listed."""
@@ -752,7 +752,7 @@ async def test_transition_and_prefix_references_clear_the_unused_css_flag(seeded
@pytest.mark.integration
async def test_consumer_map_syncs_edges_from_template_references(seeded):
"""Milestone 302: the consumer edges follow the archive — own-file
"""The consumer edges follow the archive — own-file
resolution for a scoped class, fan-out to the shared sheet for a class a
template does not define, counts refreshed and stale edges removed on
the next sync, and a vanished row's edges gone with it."""
@@ -812,7 +812,7 @@ async def test_consumer_map_syncs_edges_from_template_references(seeded):
assert rows[("v/B.vue", "error-msg")] not in edges
# --- #2793: the divergence readout against real rows -------------------------
# --- the divergence readout against real rows -------------------------------
@pytest.mark.integration
@@ -833,7 +833,7 @@ async def test_a_second_confirm_dialog_is_detected_and_named(seeded):
from scribe.services import snippets as snippets_svc
owner, pid = seeded["owner"], seeded["pid"]
# The confirm helper is TS canon: since #2871 a sym basis only proposes
# The confirm helper is TS canon: a sym basis only proposes
# within the shape's language family, so the fixture's Python snippet
# says nothing about these Vue bodies — the canon must be one of theirs.
canon = await snippets_svc.create_snippet(
@@ -906,10 +906,9 @@ async def test_a_second_confirm_dialog_is_detected_and_named(seeded):
@pytest.mark.integration
async def test_weak_stamps_elect_no_canon_and_their_flags_are_withdrawn(seeded):
"""#4608, the poisoned-ledger case as it stood on two projects: a
directory's "dominant canon" was elected entirely by hook stamps written
at 0.68-0.72, before the 0.80 floor, so every new shape there was told it
diverged from a YAML CI snippet. The same fixture as the acceptance case
"""The poisoned-ledger case: a directory's "dominant canon" elected
entirely by hook stamps written below the 0.80 floor would tell every new
shape there it diverged from an unrelated snippet. The same fixture as the acceptance case
above, with one difference — the four instances are weak hook stamps
rather than an audit — and the prompt must fall silent: at refresh (the
standing flag is withdrawn) and at write time. The stamps themselves are
@@ -984,14 +983,14 @@ async def test_weak_stamps_elect_no_canon_and_their_flags_are_withdrawn(seeded):
@pytest.mark.integration
async def test_a_semantic_miss_does_not_silence_the_prompt(seeded):
"""#4208, reversed on measurement: a semantic miss is not evidence.
"""A semantic miss is not evidence.
THE FIXTURE IS THE ACCEPTANCE CASE ABOVE, DELIBERATELY. A hand-rolled
`confirmDanger` beside an async confirm canon is structurally identical to
a registry helper beside an async service canon, and the arm's miss was
meant to tell them apart. Measured live, true instances of a canon rarely
clear the arm's floor either, so a miss fires on both — silencing on
it silenced #2793's own case. Here the arm has read the body, found
meant to tell them apart. True instances of a canon rarely clear the arm's
floor either, so a miss fires on both — silencing on it would silence the
case the readout exists for. Here the arm has read the body, found
nothing, and the prompt is still RAISED.
Asserted on the row, not a flag count: `since` sits a second early to
@@ -1087,9 +1086,9 @@ async def _two_generations(seeded, tag: str):
@pytest.mark.integration
async def test_the_capped_semantic_pass_reaches_the_new_shape_first(seeded):
"""Measured live: after the #4208 version bump, 1,533 rows competed for 150
semantic checks in row order and the six new shapes — the only ones the
divergence check acts on — were all left unread and flagged. With room for
"""After a version bump, every row competes for a capped number of
semantic checks; in row order the new shapes — the only ones the
divergence check acts on — are left unread and flagged. With room for
ONE check, the new shape must be the one that gets it."""
from unittest.mock import AsyncMock, patch
@@ -1155,7 +1154,7 @@ async def test_history_records_what_was_used_when_and_drift_asks_for_a_recheck(s
assert await shape_history(other, pid, "src/app.py") == {}
# --- milestone 439: the writer judges what it wrote, before the sync has seen it
# --- the writer judges what it wrote, before the sync has seen it
@pytest.mark.integration
async def test_a_shape_written_this_turn_is_judged_before_the_sync_sees_it(seeded):
@@ -1235,7 +1234,7 @@ async def test_unjudged_is_what_nobody_read(seeded):
@pytest.mark.integration
async def test_a_whole_file_snippet_makes_its_file_row_canonical(seeded):
"""Milestone 439: a snippet recorded at a component's path, with no
"""A snippet recorded at a component's path, with no
symbol, is the canon for that file row — the claim a whole-file record
makes and a definition row never could. It still covers no definition
inside the file."""