fix(gateway): notify_list/get/ack call NotificationDeliveryService (not Service)

Wave 1 wired notify_list/get/ack into ContentActions but pointed them at
`self.notifications` (which is NotificationService — sender side, with
send_blocker_notification / send_qa_ready_notification / etc.). The
read methods (list_for_agent, get_for_recipient_and_mark_read,
acknowledge) live on `NotificationDeliveryService` instead.

Smoke run 2026-05-11 surfaced this immediately:
  AttributeError: 'NotificationService' object has no attribute 'list_for_agent'

Fixes:
- roboco/api/deps.py — import NotificationDeliveryService and wire it
  in as a new ContentActionsDeps field `notification_delivery`.
- roboco/services/gateway/content_actions.py — add notification_delivery
  to ContentActionsDeps (Optional with `None` default for back-compat
  with any tests that don't supply it). Point notify_list, notify_get,
  notify_ack at self._deps.notification_delivery.
- tests/unit/gateway/test_content_actions.py — _make_deps adds a default
  AsyncMock for notification_delivery so existing tests continue to pass.

Quality: ruff + mypy clean. 505 unit tests pass.
This commit is contained in:
Renn F
2026-05-11 08:41:46 +02:00
parent dc9c49e1e4
commit 62d1084a0c
3 changed files with 11 additions and 3 deletions
+2
View File
@@ -31,6 +31,7 @@ from roboco.services.git import GitService
from roboco.services.journal import JournalService
from roboco.services.messaging import MessagingService
from roboco.services.notification import NotificationService
from roboco.services.notification_delivery import NotificationDeliveryService
from roboco.services.permissions import AgentContext, PermissionService
from roboco.services.repositories import resolve_agent_identity, resolve_agent_uuid
from roboco.services.task import TaskService
@@ -528,6 +529,7 @@ async def get_content_actions(
journal=JournalService(db_session),
workspace=WorkspaceService(db_session),
notifications=NotificationService(),
notification_delivery=NotificationDeliveryService(db_session),
)
)
+7 -3
View File
@@ -200,6 +200,10 @@ class ContentActionsDeps:
journal: Any
workspace: Any
notifications: Any
# Wave 1 added inbox-read verbs (notify_list/get/ack) that live on
# `NotificationDeliveryService`, not `NotificationService`. Keeping
# them separate so the sender vs receiver concerns stay split.
notification_delivery: Any = None
_VALID_NOTIFY_PRIORITIES: frozenset[str] = frozenset(p.value for p in _comms.Priority)
@@ -788,7 +792,7 @@ class ContentActions:
the verb is documented to soft-block on unread notifications, but
previously there was no way for the agent to read or acknowledge them.
"""
items = await self.notifications.list_for_agent(
items = await self._deps.notification_delivery.list_for_agent(
agent_id=agent_id,
unread_only=unread_only,
pending_ack_only=pending_ack_only,
@@ -824,7 +828,7 @@ class ContentActions:
) -> Envelope:
"""Read one notification (also marks it read)."""
try:
n = await self.notifications.get_for_recipient_and_mark_read(
n = await self._deps.notification_delivery.get_for_recipient_and_mark_read(
notification_id=notification_id,
agent_id=agent_id,
)
@@ -892,7 +896,7 @@ class ContentActions:
Returns ``not_authorized`` if the caller isn't a recipient.
"""
try:
n = await self.notifications.acknowledge(
n = await self._deps.notification_delivery.acknowledge(
notification_id=notification_id,
agent_id=agent_id,
ack_type="received",
@@ -35,6 +35,7 @@ def _make_deps(**overrides: AsyncMock) -> ContentActionsDeps:
journal = overrides.get("journal", AsyncMock())
workspace = overrides.get("workspace", AsyncMock())
notifications = overrides.get("notifications", AsyncMock())
notification_delivery = overrides.get("notification_delivery", AsyncMock())
return ContentActionsDeps(
task=task,
git=git,
@@ -43,6 +44,7 @@ def _make_deps(**overrides: AsyncMock) -> ContentActionsDeps:
journal=journal,
workspace=workspace,
notifications=notifications,
notification_delivery=notification_delivery,
)