From 0039e2a7ee68b7271bf0db5457f268ac0fbf7426 Mon Sep 17 00:00:00 2001 From: Renn F Date: Mon, 15 Jun 2026 02:40:39 +0200 Subject: [PATCH] test(manifest): guard board roles keep read_messages in do_tools The PO read_messages gap (board agent soft-blocked on i_am_idle, unable to clear unread A2A) was deploy-staleness: a PO spawned from an old manifest predating the read_messages grant in _BOARD_DO. Current code is correct (verified: live product-owner/head-marketing manifests carry it). Add a regression guard so the grant can't silently drop from _BOARD_DO for board roles. --- tests/unit/runtime/test_spawn_manifest.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/unit/runtime/test_spawn_manifest.py b/tests/unit/runtime/test_spawn_manifest.py index ee793ad9..d8f95ddd 100644 --- a/tests/unit/runtime/test_spawn_manifest.py +++ b/tests/unit/runtime/test_spawn_manifest.py @@ -69,6 +69,27 @@ class TestBuildForRole: ) ) + @pytest.mark.parametrize("role", ["product_owner", "head_marketing"]) + def test_board_roles_carry_read_messages(self, role: str) -> None: + """Board roles must keep read_messages so they can clear unread A2A and + i_am_idle (clean shutdown). A board agent spawned from a manifest that + omits it gets soft-blocked on i_am_idle forever — guard against the grant + silently dropping from _BOARD_DO. + """ + m = build_for_role( + SpawnInputs( + agent_id=uuid4(), + role=role, + team="board", + workspace_path=Path("/tmp/x"), + agent_model="claude-opus-4-6", + ) + ) + assert "read_messages" in m.do_tools + # The full A2A/notification-receiver set must be present together. + for tool in ("notify_list", "notify_get", "notify_ack"): + assert tool in m.do_tools + class TestWriteManifest: def test_writes_json(self, tmp_path: Path) -> None: