fix(systems): the bootstrap ask reads the catalog, so its test must supply one (#3027)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 26s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m20s
CI & Build / Build & push image (push) Successful in 38s

CI caught the seam the promotion opened: the standard names now come from an
async catalog read, and the unit test patches systems_svc wholesale — so the
read raised, the fail-open swallowed it, and the ask shipped without the names
it is supposed to carry.

Stub standard_systems in that test, and assert the wiring rather than the
vocabulary: THAT the seeded set is these eight is migration 0087's business and
belongs in the inception integration test, against a real database.

Adds the case the promotion actually created — an unreachable or empty catalog
must still produce the ask. The names are an aid to the question, not the
question; degrading to a weaker nudge is fine, going silent is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 12:34:29 -04:00
co-authored by Claude Opus 5
parent a97547fbc6
commit 879ef3053e
+28
View File
@@ -113,6 +113,15 @@ async def test_untagged_hint_escalates_in_a_mature_zero_systems_project():
patch("scribe.mcp.tools.systems.notes_svc") as notes:
svc.list_systems = AsyncMock(return_value=[])
notes.list_notes = AsyncMock(return_value=(recent, 282))
# The standard names come from the GLOBAL catalog now (milestone 307),
# not a module constant — so the ask reads them through the service.
# That the SEEDED vocabulary is these eight is migration 0087's
# business, asserted against a real database in the inception
# integration test; what belongs here is that whatever the catalog
# holds reaches the ask verbatim.
svc.standard_systems = AsyncMock(return_value=[
("CI & Release", "..."), ("Auth & Access", "..."),
])
hint = await untagged_systems_hint(1, 5)
assert "282 records" in hint
assert "Fix scrape retry backoff" in hint # the project's own evidence
@@ -126,6 +135,25 @@ async def test_untagged_hint_escalates_in_a_mature_zero_systems_project():
assert "no Systems yet" not in hint
@pytest.mark.asyncio
async def test_bootstrap_ask_still_asks_when_the_catalog_is_unreachable():
"""The standard names are an AID to the question, not the question. If the
catalog read fails (or an install has an empty one), the ask must still
carry the project's evidence and demand the same deliverable — degrading to
a weaker nudge is acceptable, going silent is not."""
from scribe.mcp.tools.systems import untagged_systems_hint
recent = [fake_note(title="Fix scrape retry backoff")]
with patch("scribe.mcp.tools.systems.systems_svc") as svc, \
patch("scribe.mcp.tools.systems.notes_svc") as notes:
svc.list_systems = AsyncMock(return_value=[])
notes.list_notes = AsyncMock(return_value=(recent, 282))
svc.standard_systems = AsyncMock(side_effect=RuntimeError("db down"))
hint = await untagged_systems_hint(1, 5)
assert hint is not None
assert "282 records" in hint and "create_system" in hint
assert "3-6" in hint and "without asking permission" in hint
@pytest.mark.asyncio
async def test_bootstrap_ask_stays_quiet_below_threshold_and_fails_open():
from scribe.mcp.tools import systems as tools