mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(docs): grant head_marketing read-only documentation access
The spawn manifest mounts the roboco-docs MCP for head_marketing, but the service READ_ROLES omitted the role, so list/read 403'd against a tool the agent was handed. Add head_marketing to READ_ROLES so the manifest and permissions agree (read-only; not added to WRITE_ROLES).
This commit is contained in:
@@ -75,7 +75,10 @@ TYPE_SUBFOLDERS: dict[str, str] = {
|
|||||||
# Roles that can write documentation
|
# Roles that can write documentation
|
||||||
WRITE_ROLES: frozenset[str] = frozenset({"documenter", "cell_pm"})
|
WRITE_ROLES: frozenset[str] = frozenset({"documenter", "cell_pm"})
|
||||||
|
|
||||||
# Roles that can read documentation
|
# Roles that can read documentation. The Board (head_marketing) gets
|
||||||
|
# read-only access for oversight — the spawn manifest mounts the roboco-docs
|
||||||
|
# MCP for it, so this set must agree or list/read 403 against a tool the
|
||||||
|
# agent was handed.
|
||||||
READ_ROLES: frozenset[str] = frozenset(
|
READ_ROLES: frozenset[str] = frozenset(
|
||||||
{
|
{
|
||||||
"documenter",
|
"documenter",
|
||||||
@@ -85,6 +88,7 @@ READ_ROLES: frozenset[str] = frozenset(
|
|||||||
"qa",
|
"qa",
|
||||||
"auditor",
|
"auditor",
|
||||||
"ceo",
|
"ceo",
|
||||||
|
"head_marketing",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -421,6 +421,22 @@ async def test_read_doc_unauthorized(docs_setup: dict) -> None:
|
|||||||
await svc.read_doc(agent_id="ghost-agent", path="x.md")
|
await svc.read_doc(agent_id="ghost-agent", path="x.md")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_read_doc_head_marketing_authorized(
|
||||||
|
docs_setup: dict, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""Head of Marketing has read-only docs access (Board oversight)."""
|
||||||
|
svc = docs_setup["svc"]
|
||||||
|
target = tmp_path / "board" / "design" / "brand.md"
|
||||||
|
target.parent.mkdir(parents=True)
|
||||||
|
target.write_text("# Brand", encoding="utf-8")
|
||||||
|
with patch("roboco.services.docs.DOCS_BASE_PATH", tmp_path):
|
||||||
|
content, _ = await svc.read_doc(
|
||||||
|
agent_id="head-marketing", path="board/design/brand.md"
|
||||||
|
)
|
||||||
|
assert content == "# Brand"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_read_doc_path_traversal(docs_setup: dict) -> None:
|
async def test_read_doc_path_traversal(docs_setup: dict) -> None:
|
||||||
svc = docs_setup["svc"]
|
svc = docs_setup["svc"]
|
||||||
@@ -464,6 +480,17 @@ async def test_list_docs_unauthorized(docs_setup: dict) -> None:
|
|||||||
await svc.list_docs(agent_id="ghost-agent")
|
await svc.list_docs(agent_id="ghost-agent")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_docs_head_marketing_authorized(
|
||||||
|
docs_setup: dict, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""Head of Marketing can list docs (read-only Board oversight)."""
|
||||||
|
svc = docs_setup["svc"]
|
||||||
|
with patch("roboco.services.docs.DOCS_BASE_PATH", tmp_path):
|
||||||
|
docs = await svc.list_docs(agent_id="head-marketing")
|
||||||
|
assert docs == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_list_docs_by_task_id(docs_setup: dict) -> None:
|
async def test_list_docs_by_task_id(docs_setup: dict) -> None:
|
||||||
"""Pass task_id, list from task.documents."""
|
"""Pass task_id, list from task.documents."""
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ def test_qa_block_has_no_delegate_or_note_before_claim_noise() -> None:
|
|||||||
assert "shell" in block.lower()
|
assert "shell" in block.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_head_marketing_block_lists_docs_server() -> None:
|
||||||
|
# Head of Marketing is handed the roboco-docs MCP at spawn for read-only
|
||||||
|
# oversight; the briefing must surface it (and the service READ_ROLES must
|
||||||
|
# agree, or list/read 403 against a tool the agent was given).
|
||||||
|
block = _orch()._build_verb_server_block("head_marketing")
|
||||||
|
assert "roboco-docs" in block
|
||||||
|
assert "roboco_docs_read" in block
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_role_returns_empty() -> None:
|
def test_unknown_role_returns_empty() -> None:
|
||||||
assert _orch()._build_verb_server_block("nonexistent") == ""
|
assert _orch()._build_verb_server_block("nonexistent") == ""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user