mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(a2a): CEO can DM the Auditor and PR reviewers (#623)
* feat(a2a): CEO can DM the Auditor and PR reviewers
A mid-flight PR reviewer or Auditor that's stuck was unreachable — the CEO
had no way to DM them. Both roles now carry dm/read_a2a, so the CEO can open
a 1:1 and they can reply in-thread through the existing CEO-reply path.
Scoped deliberately: the Auditor stays a silent observer to its peers — it
gains no peer-initiation surface (can_a2a_direct routes it through
_check_auditor_a2a, which refuses every initiation target; it can only reply
inside a CEO-opened DM). PR reviewers keep their owning-PM scope. Intake and
Secretary stay excluded — they have their own dedicated chat pages.
NO_COMMS_ROLES drops to {prompter, secretary}; the panel's EXCLUDE_NON_DM_ROLES
matches. KB/docs updated so the 'auditor/pr_reviewer have no dm' claim isn't
left stale.
* test(a2a): smoke guard checks _NO_COMMS_ROLES, not a hardcoded 'auditor'
The dm() runtime guard no longer names the auditor (it now carries dm to
reply to the CEO); it refuses the canonical _NO_COMMS_ROLES set. Assert on
that set so the smoke test tracks the guard, not a stale role name.
* chore(foundation): regenerate verb tables for auditor/pr_reviewer dm+read_a2a
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -99,23 +99,32 @@ def test_can_a2a_direct_to_ceo_message_explains_reply_only() -> None:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"target_slug",
|
||||
["auditor", "pr-reviewer-1", "intake-1", "secretary-1"],
|
||||
["intake-1", "secretary-1"],
|
||||
)
|
||||
def test_can_a2a_direct_ceo_to_no_comms_role_denied(target_slug: str) -> None:
|
||||
"""The CEO's asymmetric reach still can't target a role with no dm/
|
||||
read_a2a on its manifest (auditor, pr_reviewer, prompter, secretary) —
|
||||
nothing on the other end could ever read or answer the DM. The panel's
|
||||
New-DM dialog already filters these client-side (EXCLUDE_NON_DM_ROLES);
|
||||
this is the server-side backstop for a direct API/A2A-service call."""
|
||||
read_a2a on its manifest (prompter, secretary — human-only, own chat
|
||||
pages) — nothing on the other end could ever read or answer the DM."""
|
||||
allowed, reason = can_a2a_direct("ceo", target_slug)
|
||||
assert allowed is False
|
||||
assert reason is not None
|
||||
assert "comms" in reason.lower()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target_slug", ["auditor", "pr-reviewer-1"])
|
||||
def test_can_a2a_direct_ceo_to_auditor_or_pr_reviewer_allowed(target_slug: str) -> None:
|
||||
"""The CEO can now DM a mid-flight auditor or PR reviewer — both carry
|
||||
dm/read_a2a so they can read and reply in-thread, even though neither
|
||||
gains a peer-initiation surface (auditor stays silent via
|
||||
can_a2a_direct; the PR reviewer stays scoped to its owning PM)."""
|
||||
allowed, reason = can_a2a_direct("ceo", target_slug)
|
||||
assert allowed is True
|
||||
assert reason is None
|
||||
|
||||
|
||||
def test_can_a2a_direct_ceo_to_no_comms_role_reuses_canonical_set() -> None:
|
||||
"""The refusal set must be exactly foundation.policy.communications'
|
||||
NO_COMMS_ROLES — the same set services.gateway.content_actions uses to
|
||||
gate the dm() sender side — so the two never drift apart."""
|
||||
expected = {"auditor", "pr_reviewer", "prompter", "secretary"}
|
||||
expected = {"prompter", "secretary"}
|
||||
assert {role.value for role in NO_COMMS_ROLES} == expected
|
||||
|
||||
Reference in New Issue
Block a user