mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(docs): refuse doc_type=user_facing with roboco-website guidance (#301)
Phase 2 of the docs-site split: DocsService.write_doc only ever wrote into docs/<team>/... team buckets, which are excluded from the published site — so an agent reaching for write_doc to publish a user-facing page failed silently into an unpublished bucket. doc_type="user_facing" is now a recognized DocType member that DocsService refuses up front with guidance naming the roboco-website project and the 3-edit pattern (MDX + route wrapper + nav.ts entry), instead of the generic "Unknown doc_type" error. The roboco_docs_write MCP tool docstring and input-schema description are updated so documenter LLMs see the scope boundary before calling it. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -92,6 +92,33 @@ async def test_write_doc_validation_error(docs_client: AsyncClient) -> None:
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_doc_user_facing_refused_is_400_not_422(
|
||||
docs_client: AsyncClient,
|
||||
) -> None:
|
||||
"""doc_type='user_facing' is a recognized DocType enum member, so Pydantic
|
||||
accepts it at the HTTP boundary (no 422) and the service's actionable
|
||||
refusal (400 with roboco-website guidance) is what the agent sees."""
|
||||
with patch("roboco.api.routes.docs.get_docs_service") as mock_get:
|
||||
mock_service = AsyncMock()
|
||||
mock_service.write_doc = AsyncMock(
|
||||
side_effect=ValidationError("...roboco-website project...")
|
||||
)
|
||||
mock_get.return_value = mock_service
|
||||
response = await docs_client.post(
|
||||
"/api/docs/write",
|
||||
json={
|
||||
"task_id": str(uuid4()),
|
||||
"filename": "test.md",
|
||||
"doc_type": "user_facing",
|
||||
"title": "Test",
|
||||
"content": "Some content",
|
||||
},
|
||||
headers=_HDR,
|
||||
)
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_doc_unauthorized(docs_client: AsyncClient) -> None:
|
||||
"""Service raises UnauthorizedError → 403."""
|
||||
|
||||
@@ -160,6 +160,29 @@ async def test_write_doc_invalid_doc_type(docs_setup: dict) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_doc_user_facing_refused(docs_setup: dict) -> None:
|
||||
"""doc_type='user_facing' is a recognized value (not a generic 'Unknown
|
||||
doc_type') but is structurally refused: this store's buckets are all
|
||||
excluded from the published site. The guidance names the roboco-website
|
||||
project and the 3-edit pattern instead of silently landing an
|
||||
unpublished write (docs-site-split Phase 2)."""
|
||||
svc = docs_setup["svc"]
|
||||
with pytest.raises(ValidationError, match="roboco-website") as exc_info:
|
||||
await svc.write_doc(
|
||||
agent_id="be-doc",
|
||||
req=WriteDocInput(
|
||||
task_id=docs_setup["task_id"],
|
||||
filename="x.md",
|
||||
doc_type="user_facing",
|
||||
title="Title",
|
||||
content="Content",
|
||||
),
|
||||
)
|
||||
assert "Unknown doc_type" not in str(exc_info.value)
|
||||
assert "docs.roboco.tech" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_doc_path_traversal_in_filename(docs_setup: dict) -> None:
|
||||
svc = docs_setup["svc"]
|
||||
|
||||
Reference in New Issue
Block a user