From ebd6cb203c2f8b2d9a4ad475580eb5a87e7e1b7c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 16 Sep 2026 17:19:19 -0400 Subject: [PATCH] fix(tests): the repeat tail is the middle state now, not the opened one (#4100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI run 6943: 11 failures, two causes, both existing guards correctly catching the behaviour change rather than defects in it. TEN were `_SEEN_TAIL = "You saw it earlier this session"`. Every one of those tests drives an arm with `exclude_rule_ids` alone — the NAMING ledger — which since this change is the middle state, not the opened one. That is the whole point of the step: `seen` stopped meaning "you saw it". The constant now holds the middle tail, so each test keeps asserting exactly what it was written to assert (a repeat gets a line distinct from a first surfacing) against the wording that is now true. Added `_HELD_TAIL` and a negative assertion with it: neither call names an opened ledger, so neither may claim the session read anything. That catches `held` defaulting true, which would have every repeat assert the strongest of the three claims on no evidence — and none of the ten existing tests would have noticed. THE ELEVENTH was my own test edit being wrong, not the code. I asserted `held_rule_ids` would appear in `re.findall(r"printf '([a-z_]+)=", defs)`, but `scribe_held_query` spells it `printf '&held_rule_ids=`, and the leading `&` means that regex never matches it. The regex enumerates SCOPE keys — the alternatives that open a query — and the ledger key is appended to a query that already has one. Two different contracts that happened to share a file, so they are now asserted separately and the difference is written down. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy --- tests/test_rule_usage_wiring.py | 26 ++++++++++++++++++++++---- tests/test_write_path_trigger.py | 9 ++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/test_rule_usage_wiring.py b/tests/test_rule_usage_wiring.py index 0da18d4..6b383d6 100644 --- a/tests/test_rule_usage_wiring.py +++ b/tests/test_rule_usage_wiring.py @@ -751,9 +751,14 @@ def test_the_hook_and_the_route_agree_on_every_parameter_name(): # is read by a shared helper so its ageing and format cannot drift from the # naming ledger's, which means the key is spelled in defs rather than here. assert "scribe_held_query" in hook, "hook no longer sends the opened ledger" - assert set(re.findall(r"printf '([a-z_]+)=", defs)) == { - "repo", "project_id", "held_rule_ids", - } + assert set(re.findall(r"printf '([a-z_]+)=", defs)) == {"repo", "project_id"} + # Spelled with its own leading `&` because it is APPENDED to a query that + # already has a scope key, where the two above are alternatives that open + # one. Asserted separately for that reason: a scope key and a ledger key + # are different contracts that happen to share a file. + assert "printf '&held_rule_ids=" in defs, ( + "the opened ledger's query key is no longer spelled in the helper" + ) # Both scope keys are read by the shared _project_scope() helper, not inline. assert "_project_scope()" in handler @@ -1141,7 +1146,14 @@ _HELD = fake_rule( when_to_apply="writing any call that crosses a process boundary", ) _FRESH_TAIL = "not in this session's loaded set" -_SEEN_TAIL = "You saw it earlier this session" +# `seen` WITHOUT `held` is the middle state since #4100: an arm named this rule +# earlier, and the session never opened it. Every test below drives the arms +# with `exclude_rule_ids` alone — the naming ledger — so this is the tail they +# produce, and it is the one that used to claim "You saw it earlier this +# session" about a teaser nobody had read. +_SEEN_TAIL = "Mentioned earlier this session but not opened" +# The third state, reached only when the opened ledger names the rule too. +_HELD_TAIL = "You opened it earlier this session" @pytest.mark.parametrize(("source", "run"), _ARMS, ids=["write_path", "pre_tool", "prompt"]) @@ -1194,6 +1206,12 @@ async def test_the_two_tails_are_distinguishable_and_say_the_true_one(source, ru f"in its loaded set" ) assert fresh_ctx != seen_ctx, "the two tails collapsed into one" + # Neither call named an OPENED ledger, so neither may claim the session + # read anything (#4100). Catches `held` defaulting true, which would make + # every repeat assert the strongest of the three claims on no evidence. + assert _HELD_TAIL not in fresh_ctx and _HELD_TAIL not in seen_ctx, ( + f"{source} said the session had opened a rule it was only shown" + ) # ── the third axis: KIND (milestone 399) ──────────────────────────────── diff --git a/tests/test_write_path_trigger.py b/tests/test_write_path_trigger.py index f5d9dc5..a9e8044 100644 --- a/tests/test_write_path_trigger.py +++ b/tests/test_write_path_trigger.py @@ -942,9 +942,12 @@ def test_route_reads_every_arg_the_hook_sends(): # helper so its ageing and format cannot drift from the naming ledger's. assert "scribe_held_query" in hook, "hook no longer sends the opened ledger" defs = (HOOK.parent / "scribe_defs.sh").read_text() - assert set(re.findall(r"printf '([a-z_]+)=", defs)) == { - "repo", "project_id", "held_rule_ids", - } + assert set(re.findall(r"printf '([a-z_]+)=", defs)) == {"repo", "project_id"} + # Its own leading `&` — it is appended to a query that already carries a + # scope key, rather than being one of the alternatives that opens one. + assert "printf '&held_rule_ids=" in defs, ( + "the opened ledger's query key is no longer spelled in the helper" + ) def test_route_resolves_repo_to_a_project_not_to_a_location_filter():