# Notifier Tenancy Prep — PREP-NOTIFY-TEN-48-001 Status: Implemented (2025-11-27) Owners: Notifications Service Guild Scope: Tenancy model and DAL/routes for tenant context in Notifier WebService. ## Overview Tenant scoping for the Notifier module ensures that rules, templates, incidents, and channels are isolated per tenant with proper row-level security (RLS) in MongoDB storage. ## Implementation Summary ### 1. Tenant Context Service (`src/Notifier/StellaOps.Notifier.Worker/Tenancy/`) - **TenantContext.cs**: AsyncLocal-based context propagation for tenant ID and actor - **TenantServiceExtensions.cs**: DI registration and configuration options - **ITenantAccessor**: Interface for accessing tenant from HTTP context Key pattern: ```csharp // Set tenant context for async scope using var scope = tenantContext.SetContext(tenantId, actor); await ProcessEventAsync(); // Or with extension method await tenantContext.WithTenantAsync(tenantId, actor, async () => { await ProcessNotificationAsync(); }); ``` ### 2. Incident Repository (`src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/`) New files: - **Repositories/INotifyIncidentRepository.cs**: Repository interface for incident persistence - **Repositories/NotifyIncidentRepository.cs**: MongoDB implementation with tenant filtering - **Serialization/NotifyIncidentDocumentMapper.cs**: BSON serialization for incidents Key features: - All queries include mandatory `tenantId` filter - Document IDs use `{tenantId}:{resourceId}` composite pattern for RLS - Correlation key lookup scoped to tenant - Soft delete support with `deletedAt` field ### 3. MongoDB Indexes (tenant-scoped) Added in `EnsureNotifyIndexesMigration.cs`: ```javascript // incidents collection { tenantId: 1, status: 1, lastOccurrence: -1 } // Status filtering { tenantId: 1, correlationKey: 1, status: 1 } // Correlation lookup ``` ### 4. Existing Tenancy Infrastructure The following was already in place: - All models have `TenantId` property (NotifyRule, NotifyChannel, NotifyTemplate, etc.) - Repository interfaces take `tenantId` as parameter - Endpoints extract tenant from `X-StellaOps-Tenant` header - MongoDB document IDs use tenant-prefixed composite keys ## Configuration ```json { "Notifier": { "Tenant": { "TenantIdHeader": "X-StellaOps-Tenant", "ActorHeader": "X-StellaOps-Actor", "RequireTenant": true, "DefaultActor": "system", "ExcludedPaths": ["/health", "/ready", "/metrics", "/openapi"] } } } ``` ## Usage Examples ### HTTP API ```http GET /api/v2/rules HTTP/1.1 X-StellaOps-Tenant: tenant-123 X-StellaOps-Actor: user@example.com ``` ### Worker Processing ```csharp public class NotificationProcessor { private readonly ITenantContext _tenantContext; public async Task ProcessAsync(NotifyEvent @event) { using var scope = _tenantContext.SetContext(@event.TenantId, "worker"); // All subsequent operations are scoped to tenant var rules = await _rules.ListAsync(@event.TenantId); // ... } } ``` ## Handoff Notes - Incident storage moved from in-memory to MongoDB with full tenant isolation - Worker should use `ITenantContext.SetContext()` before processing events - All new repositories MUST include tenant filtering in queries - Test tenant isolation with multi-tenant integration tests ## Related Files - `src/Notifier/StellaOps.Notifier.Worker/Tenancy/TenantContext.cs` - `src/Notifier/StellaOps.Notifier.Worker/Tenancy/TenantServiceExtensions.cs` - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/Repositories/INotifyIncidentRepository.cs` - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/Repositories/NotifyIncidentRepository.cs` - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/Serialization/NotifyIncidentDocumentMapper.cs` - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/Options/NotifyMongoOptions.cs` (added IncidentsCollection) - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/Migrations/EnsureNotifyIndexesMigration.cs` (added incident indexes) - `src/Notify/__Libraries/StellaOps.Notify.Storage.Mongo/ServiceCollectionExtensions.cs` (added INotifyIncidentRepository registration)