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.journal import JournalService
from roboco.services.messaging import MessagingService from roboco.services.messaging import MessagingService
from roboco.services.notification import NotificationService from roboco.services.notification import NotificationService
from roboco.services.notification_delivery import NotificationDeliveryService
from roboco.services.permissions import AgentContext, PermissionService from roboco.services.permissions import AgentContext, PermissionService
from roboco.services.repositories import resolve_agent_identity, resolve_agent_uuid from roboco.services.repositories import resolve_agent_identity, resolve_agent_uuid
from roboco.services.task import TaskService from roboco.services.task import TaskService
@@ -528,6 +529,7 @@ async def get_content_actions(
journal=JournalService(db_session), journal=JournalService(db_session),
workspace=WorkspaceService(db_session), workspace=WorkspaceService(db_session),
notifications=NotificationService(), notifications=NotificationService(),
notification_delivery=NotificationDeliveryService(db_session),
) )
) )
+7 -3
View File
@@ -200,6 +200,10 @@ class ContentActionsDeps:
journal: Any journal: Any
workspace: Any workspace: Any
notifications: 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) _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 the verb is documented to soft-block on unread notifications, but
previously there was no way for the agent to read or acknowledge them. 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, agent_id=agent_id,
unread_only=unread_only, unread_only=unread_only,
pending_ack_only=pending_ack_only, pending_ack_only=pending_ack_only,
@@ -824,7 +828,7 @@ class ContentActions:
) -> Envelope: ) -> Envelope:
"""Read one notification (also marks it read).""" """Read one notification (also marks it read)."""
try: 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, notification_id=notification_id,
agent_id=agent_id, agent_id=agent_id,
) )
@@ -892,7 +896,7 @@ class ContentActions:
Returns ``not_authorized`` if the caller isn't a recipient. Returns ``not_authorized`` if the caller isn't a recipient.
""" """
try: try:
n = await self.notifications.acknowledge( n = await self._deps.notification_delivery.acknowledge(
notification_id=notification_id, notification_id=notification_id,
agent_id=agent_id, agent_id=agent_id,
ack_type="received", ack_type="received",
@@ -35,6 +35,7 @@ def _make_deps(**overrides: AsyncMock) -> ContentActionsDeps:
journal = overrides.get("journal", AsyncMock()) journal = overrides.get("journal", AsyncMock())
workspace = overrides.get("workspace", AsyncMock()) workspace = overrides.get("workspace", AsyncMock())
notifications = overrides.get("notifications", AsyncMock()) notifications = overrides.get("notifications", AsyncMock())
notification_delivery = overrides.get("notification_delivery", AsyncMock())
return ContentActionsDeps( return ContentActionsDeps(
task=task, task=task,
git=git, git=git,
@@ -43,6 +44,7 @@ def _make_deps(**overrides: AsyncMock) -> ContentActionsDeps:
journal=journal, journal=journal,
workspace=workspace, workspace=workspace,
notifications=notifications, notifications=notifications,
notification_delivery=notification_delivery,
) )