Add a dev memo-forwarding fixture and deduplicated intake notifications.
Pose, replay, and conflict stay on an injected transport with local retry evidence. Notification receipts keep only memo id, question, class, and submitter, and a resolve conflict stays readable. Assistant: grok Assistant-Session: 01a10329-31cb-78d3-b7a3-789e1fb83139
This commit is contained in:
parent
42ab047e87
commit
49eeddbfed
10 changed files with 1612 additions and 1 deletions
67
api/services/memo_notification.py
Normal file
67
api/services/memo_notification.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""Deduplicated informed-decision notification receipts.
|
||||
|
||||
The signed-in overview is the review entry point. A receipt stores and
|
||||
announces only memo id, question, class, and submitter. Replays of the same
|
||||
delivery id return the retained receipt; a different body conflicts and leaves
|
||||
the original readable.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from api.models.agent_message import AgentMessage
|
||||
from api.models.memo_notification import MemoNotificationReceipt
|
||||
from api.services.informed_decision_contract import Notification
|
||||
from api.services.repository_aliases import canonicalize_repository_slug
|
||||
|
||||
NOTIFICATION_SENDER = "informed-decision"
|
||||
NOTIFICATION_RECIPIENT = "bernd"
|
||||
|
||||
|
||||
class NotificationConflict(Exception):
|
||||
def __init__(self, receipt: MemoNotificationReceipt) -> None:
|
||||
self.receipt = receipt
|
||||
|
||||
|
||||
async def accept_notification(
|
||||
session: AsyncSession, delivery_id: str, notice: Notification
|
||||
) -> tuple[MemoNotificationReceipt, bool]:
|
||||
existing = await session.get(MemoNotificationReceipt, delivery_id)
|
||||
if existing is not None:
|
||||
if existing.body_digest != notice.body_digest:
|
||||
raise NotificationConflict(existing)
|
||||
return existing, True
|
||||
|
||||
message = AgentMessage(
|
||||
from_agent=await canonicalize_repository_slug(session, NOTIFICATION_SENDER),
|
||||
to_agent=await canonicalize_repository_slug(session, NOTIFICATION_RECIPIENT),
|
||||
subject=notice.message_subject(),
|
||||
body=notice.message_text(),
|
||||
kind="message",
|
||||
)
|
||||
session.add(message)
|
||||
await session.flush()
|
||||
receipt = MemoNotificationReceipt(
|
||||
delivery_id=delivery_id,
|
||||
body_digest=notice.body_digest,
|
||||
memo_id=notice.memo_id,
|
||||
question=notice.question,
|
||||
memo_class=notice.memo_class,
|
||||
submitter=notice.submitter,
|
||||
message_id=message.id,
|
||||
authoritative_review="signed-in-overview",
|
||||
)
|
||||
session.add(receipt)
|
||||
try:
|
||||
await session.commit()
|
||||
except IntegrityError:
|
||||
await session.rollback()
|
||||
existing = await session.get(MemoNotificationReceipt, delivery_id)
|
||||
if existing is None:
|
||||
raise
|
||||
if existing.body_digest != notice.body_digest:
|
||||
raise NotificationConflict(existing)
|
||||
return existing, True
|
||||
await session.refresh(receipt)
|
||||
return receipt, False
|
||||
Loading…
Add table
Add a link
Reference in a new issue