StudioNotificationService¶
Target
This page describes intended behaviour or structure. No implementation is claimed unless a section is explicitly marked Current implementation or Evidence.
| Bounded context | BC-29 Experience Composition |
| Platform / plane | Factory Studio / Experience |
| Deployment granularity | module of host · host StudioBff |
| Status | target · ConnectSoft.Factory.Studio.Notifications |
| Built from | connectsoft-microservice · libraries: connectsoft-extensions-saas-abstractions, connectsoft-extensions-messaging-model, connectsoft-extensions-persistence-model-nhibernate, connectsoft-extensions-service-model-signal-r |
| Decisions | ADR-0014 |
| Traceability | FR-05 FR-10 FUX-02 FUX-04 NFR-01 NFR-10 XL-06 |
Purpose and responsibilities¶
StudioNotificationService turns platform events into something a person sees. Approvals waiting, alerts, releases ready, promotions and rollouts, incidents, learning proposals, exports and reconciliations arrive as events; the FanOut worker resolves who may see each subject through the owning platform's permission check — never from the event alone — applies the users' preferences and quiet hours (action-required and critical ignore them in-app), creates notifications with a summary and a reference, pushes them through the realtime gateway, and delivers email and chat through connectors with receipts. Digests group the rest hourly or daily. Alerts use the Notify gRPC call for a guaranteed in-app notice.
Context and boundaries¶
flowchart LR
S["StudioNotificationService"]
AlertRuleService["AlertRuleService"] -->|grpc| S
StudioRealtimeGateway["StudioRealtimeGateway"] -->|event| S
S -->|grpc| ControlPlaneApi["ControlPlaneApi"]
S -->|grpc| ConnectorRegistryService["ConnectorRegistryService"]
S -. "NotificationCreated" .-> E_NotificationCreated(("event"))
Content is a summary with a reference; opening a notification fetches the subject through the BFF under the user's permissions at that moment, so a revoked permission cannot be bypassed by an old notification.
Domain model¶
Notification — One notification for one user: kind, subject reference, scope, importance, read and dismissed state, and the deliveries made per channel with receipts. (catalog entry)
| Field | Type | Required | Description |
|---|---|---|---|
notificationId |
guid |
yes | |
tenantId |
string |
yes | |
userId |
string |
yes | |
kind |
enum:NotificationKind |
yes | |
subjectRef |
string |
yes | |
projectId |
string |
no | |
importance |
string |
yes | info |
summary |
string |
yes | |
readAt |
datetime |
no | |
dismissedAt |
datetime |
no | |
deliveries |
json |
yes | |
createdAt |
datetime |
yes |
Invariants:
- A notification is created only for users whose scope and role allow seeing the subject; the fan-out resolves recipients through the owning platform's permission check, never from the event alone.
- Content is a summary with a reference; the Studio fetches the subject under the user's permissions when opened.
Events: NotificationCreated · Tables: Notifications · Repository: NotificationRepository
NotificationPreference — Per-user channel preferences, quiet hours and digest settings per notification kind. (catalog entry)
| Field | Type | Required | Description |
|---|---|---|---|
userId |
string |
yes | |
tenantId |
string |
yes | |
channelsByKind |
json |
yes | |
quietHours |
json |
no | |
digest |
string |
yes | none |
Invariants:
- Action-required and critical notifications ignore quiet hours for the in-app channel.
Events: — · Tables: NotificationPreferences · Repository: NotificationPreferenceRepository
erDiagram
Notification {
guid notificationId PK
string tenantId
string userId
enum_NotificationKind kind
string subjectRef
string projectId
string importance
string summary
datetime readAt
datetime dismissedAt
json deliveries
datetime createdAt
}
NotificationPreference {
string userId PK
string tenantId
json channelsByKind
json quietHours
string digest
}
Entities and value objects¶
None modelled
No entities or value objects are modelled for this item yet.
Data model and storage¶
| Store | Role | Engine / profile | Tables |
|---|---|---|---|
| Studio store | primary | azure-sql / nhibernate-sqlserver | Notifications, NotificationPreferences |
| Table | Owner | Columns | Indexes |
|---|---|---|---|
dbo.Notifications |
Notification | 12 | 1 |
dbo.NotificationPreferences |
NotificationPreference | 5 | 0 |
Full DDL: appendix.
APIs¶
| Method | Path | Operation | Scope | Side effects | Idempotency | Gateway | Summary |
|---|---|---|---|---|---|---|---|
GET |
/studio/notifications |
ListNotifications | factory.studio.use |
read | natural | GW-02 | The user's notifications with unread counts by importance. |
POST |
/studio/notifications/{notificationId}/read |
MarkNotificationRead | factory.studio.use |
shared-write | natural | GW-02 | Mark read or dismissed. |
PUT |
/studio/notifications/preferences |
SetNotificationPreferences | factory.studio.use |
shared-write | natural | GW-02 | Channels per kind, quiet hours, digest. |
| gRPC service | Method | Streaming | Visibility | Side effects | Summary |
|---|---|---|---|---|---|
Notifications |
Notify |
none | internal | shared-write | Create notifications for the recipients of a subject (used by platforms that need a guaranteed in-app notice, for example alerts). |
Messages¶
| Event | Topic | Scope / v | Aggregate | Consumers | Summary |
|---|---|---|---|---|---|
| NotificationCreated | studio.notifications.v1.notification-created |
integration / 1 | Notification | StudioRealtimeGateway | A notification exists for a user (the realtime gateway pushes it). |
NotificationCreated payload:
| Field | Type | Required | Description |
|---|---|---|---|
notificationId |
guid |
yes | |
tenantId |
string |
yes | |
userId |
string |
yes | |
kind |
enum:NotificationKind |
yes | |
importance |
string |
yes |
| Command | Handled by | Result events | Summary |
|---|---|---|---|
MarkNotificationRead |
Notification | — | |
SetNotificationPreferences |
NotificationPreference | — |
| Event | Producer | Handler | Purpose |
|---|---|---|---|
| AlertFired | AlertRuleService | NotifyOperators |
Alerts reach operators in scope. |
| ReleaseReady | ReleaseService | NotifyProject |
A release is ready to promote. |
| PromotionCompleted | EnvironmentPromotionService | NotifyProject |
A promotion completed. |
| RolloutPlanned | FleetRolloutService | NotifyOperators |
A rollout awaits approval or started. |
| RolloutHalted | FleetRolloutService | NotifyOperators |
A rollout halted. |
| IncidentOpened | FleetIncidentService | NotifyOnCall |
An incident opened. |
| LearningProposalRaised | FeedbackService | NotifyMaintainers |
A proposal awaits a maintainer. |
| ReconciliationProposed | RuntimeEnvironmentService | NotifyOperators |
A reconciliation awaits an Operator. |
| ExportCompleted | RetentionResidencyService | NotifyRequester |
An export is ready. |
Workers and jobs¶
| Worker | Trigger | Output events | Retry | Idempotency key | Summary |
|---|---|---|---|---|---|
| FanOut | event | NotificationCreated | exponential | eventId + userId |
Resolves recipients per event through the owning platform's permission check and the users' preferences, creates notifications and dispatches email and chat deliveries through connectors. |
| Digester | schedule 0 * * * * |
— | fixed | userId + window |
Builds hourly or daily digests for users who chose them. |
Actors and grains¶
None modelled
No grains are modelled for this item yet.
Sagas and long-running flows¶
None modelled
No sagas are modelled for this item yet.
flowchart LR
EV[AlertFired · ReleaseReady · IncidentOpened · ProposalRaised · …] --> FO[FanOut]
FO --> PERM[owning platform: who may see this subject in this scope?]
PERM --> PREF[preferences · quiet hours · digest]
PREF --> N["(Notification per user)"]
N --> RT[StudioRealtimeGateway push]
N --> CX[email · chat via connectors · receipts]
N --> DG[Digester hourly · daily]
Options and configuration¶
| Section | Key | Type | Default | Reloadable | Secret | Summary |
|---|---|---|---|---|---|---|
Studio:Notifications |
RetentionDays |
int | 90 |
yes | no | Days notifications are kept. |
Metrics and observability¶
| Metric | Instrument | Unit | Dimensions | Summary |
|---|---|---|---|---|
connectsoft.factory.studio.notifications |
counter | {notification} | tenant, kind, channel | Notifications created by kind and channel. |
connectsoft.factory.studio.notification_latency |
histogram | s | kind | Event to in-app notification. |
Security¶
| Authentication | oauth2-jwt |
| Scopes | factory.studio.use (Own notifications and preferences.) |
| Roles | Requester, Reviewer, AssetMaintainer, Operator, WorkspaceAdmin, PlatformAdmin, Observer |
| Tenant isolation | per-request-claim |
| Data classification | internal |
| Max side-effect class | shared-write |
| Secrets | — |
Dependencies¶
Outbound
| Target | Kind | Purpose | Failure mode | Required |
|---|---|---|---|---|
| ControlPlaneApi | grpc | Recipient resolution by scope and role. | fail-fast | yes |
| ConnectorRegistryService | grpc | Email and chat deliveries. | degrade | no |
Inbound
| Target | Kind | Purpose | Failure mode | Required |
|---|---|---|---|---|
| AlertRuleService | grpc | Guaranteed in-app alerts. | — | yes |
| StudioRealtimeGateway | event | Push of new notifications. | — | yes |
Failure and recovery¶
| Scenario | Detection | Recovery | Data impact | RTO (min) |
|---|---|---|---|---|
| Recipient resolution unavailable | Control plane errors | events queue; no notification is created without a permission check | delayed notifications | 30 |
NFR targets¶
| Target | Class value (pilot-interactive) | Override |
|---|---|---|
| latency_p95_ms | 2000 | — |
| availability_pct | 99.5 | — |
| throughput_rps | 50 | — |
| rpo_minutes | 15 | — |
| rto_minutes | 240 | — |
| cost_class | low | — |
Deployment¶
| Granularity | module of host · host StudioBff |
| Topologies | saas-default, hybrid, on-prem |
| Runtime | —; replicas —–— |
| Ports | — |
Current implementation¶
Nothing: the prototypes show a notification tray with static items.
Later capabilities¶
Notification rules authored by users (watch this run, this product); mobile push through a connector; team inboxes for on-call rotations.
Decisions and traceability¶
| Family | Ids |
|---|---|
| FR | FR-05, FR-10 |
| FUX | FUX-02, FUX-04 |
| NFR | NFR-01, NFR-10 |
| XL | XL-06 |