Permit offer WebSocket notification specs, builders, and the narrow
NotificationSender interface that decouples offer/revoke send sites
from BackendWebsocketTransport.
Six RemoteNotificationActionSpecs cover the consentful-permits
lifecycle events the server pushes to affected accounts:
- permit_offer_received → recipient's sockets when an offer is created
- permit_offer_retracted → recipient's sockets when a grantor retracts
- permit_offer_accepted → grantor's sockets when the recipient accepts
- permit_offer_declined → grantor's sockets when the recipient declines
- permit_offer_supersede → grantor's sockets when a sibling accept,
a revoke of the resulting permit, or destruction of the parent scope
row obsoletes their pending offer
- permit_revoke → revokee's sockets when one of their active permits
is revoked (companion to the permit_revoke audit event)
Payloads are flat and normalized — PermitOfferJson for the offer-lifecycle
notifications (decline reason rides on offer.decline_reason, not a
sibling field), and {permit_id, role, scope_id, reason?} for permit_revoke. The
revokee/grantor/recipient account id travels via the send target (the
NotificationSender.send_to_account argument), not in the payload.
The specs surface as EventSpecs via create_action_event_spec — callers
append PERMIT_OFFER_NOTIFICATION_SPECS to their event_specs on
create_app_server so the surface reflects them and DEV-mode broadcast
validation catches payload drift.