test(lease): assert promotion wiring against composed base+overlay template
ci/woodpecker/pr/ci Pipeline was successful

The lease-overlay split (a42d5e2e) moved the promotion hooks out of the
base Claude settings template; the wiring test still read the base alone
and failed on the absent UserPromptSubmit event, stopping the whole
test:framework-shell chain. The test now composes base + lease overlay
the way a launched seat does (hook event arrays concatenate, base
first) and asserts the same wiring contract against that view.

Reported-by: goals (clean-head probe on 5e154310)
This commit is contained in:
Jason Woltje
2026-08-13 12:17:10 -05:00
parent 5e15431027
commit cb960237d3
@@ -23,6 +23,7 @@ COMPLETE_PATH = TOOLS / "promote-complete.py"
OBSERVER_CLIENT_PATH = TOOLS / "receipt-observer-client.py" OBSERVER_CLIENT_PATH = TOOLS / "receipt-observer-client.py"
RECEIPT_CHALLENGE_PATH = TOOLS / "receipt_challenge.py" RECEIPT_CHALLENGE_PATH = TOOLS / "receipt_challenge.py"
CLAUDE_SETTINGS = FRAMEWORK / "runtime/claude/settings.json" CLAUDE_SETTINGS = FRAMEWORK / "runtime/claude/settings.json"
CLAUDE_LEASE_OVERLAY = FRAMEWORK / "runtime/claude/lease-overlay.json"
CLAUDE_COMMAND = FRAMEWORK / "runtime/claude/commands/mosaic-promote.md" CLAUDE_COMMAND = FRAMEWORK / "runtime/claude/commands/mosaic-promote.md"
SESSION_ID = "a" * 64 SESSION_ID = "a" * 64
CHALLENGE = "b" * 64 CHALLENGE = "b" * 64
@@ -569,8 +570,15 @@ class PromotionCompleteTest(PromotionHookFixture):
class PromotionTemplateWiringTest(unittest.TestCase): class PromotionTemplateWiringTest(unittest.TestCase):
def test_gated_claude_template_wires_begin_and_ordered_stop_chain(self) -> None: def test_gated_claude_template_wires_begin_and_ordered_stop_chain(self) -> None:
settings = json.loads(CLAUDE_SETTINGS.read_text(encoding="utf-8")) base = json.loads(CLAUDE_SETTINGS.read_text(encoding="utf-8"))
hooks = settings["hooks"] overlay = json.loads(CLAUDE_LEASE_OVERLAY.read_text(encoding="utf-8"))
# A launched seat composes base + lease overlay; hook event arrays
# concatenate with base entries first, so wiring is asserted against
# the composed view rather than either file alone.
hooks: dict[str, list] = {}
for layer in (base["hooks"], overlay["hooks"]):
for event, groups in layer.items():
hooks.setdefault(event, []).extend(groups)
submit_commands = [ submit_commands = [
hook["command"] hook["command"]
for group in hooks["UserPromptSubmit"] for group in hooks["UserPromptSubmit"]