feat(notifications): delivery channels ride the module lane - #1665
Conversation
The NotificationRule/NotificationChannel node types and the NotificationTriageService watcher move out of AddGraph()/Memex.Portal.Shared into a new module, src/MeshWeaver.Notifications.Channels — assembly-level MeshNodeProviderAttribute + AddNotificationChannels() sharing one configure path (the Observability shape: options via AddOptions<T>().BindConfiguration, mesh-scoped singleton + IHostedService forward). The watcher now takes the root IMessageHub from DI instead of a scoped PortalApplication (the LogIncidentControlPlane precedent, incl. StartThread on the root hub), self-skips unless Email:Enabled (was a registration-time gate), and reads content via the sanctioned ContentAs<Notification>. IMeshQueryCore stays internal; the module joins its InternalsVisibleTo whitelist — the same grant Memex.Portal.Shared held, for the same system-level all-users watch. Compiled residue deliberately kept in the platform: the NodeType const classes and NotificationService.HasRoutingRules (Graph cannot reference the module). The NotificationTriage agent stays platform content, named by string — the LogTriage split. Activation is the double-ship state: Modules:Assemblies in both hosts + Portal.Shared ProjectReference + MeshModulesPublish.targets. Pin test: NotificationChannelsModuleTest (InstallAssemblies registers the watcher with an IHostedService FORWARD, never a second construction, plus both node types on the static-node provider). Docs: Modules.md inventory row, EmailIngestionAndNotifications.md registration paragraph, Notifications.md module note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR modularizes the “notification delivery channels” lane by moving NotificationRule/NotificationChannel node types and the NotificationTriageService watcher out of Memex.Portal.Shared into a new module assembly (MeshWeaver.Notifications.Channels) that is activated via Modules:Assemblies.
Changes:
- Introduces
src/MeshWeaver.Notifications.Channelsas a module (builder extension + assembly attribute) and movesNotificationTriageServiceinto it. - Updates host/module wiring so the lane is activated via
Modules:Assemblies(plus double-ship project/publish references) and removes the oldAddGraph()/MemexConfiguration registrations. - Adds a pinning test for the boot-pack/module installation path and updates documentation to reflect the new module boundary.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/Memex.Portal.Shared.Test/NotificationChannelsModuleTest.cs | Adds a pin test for module installation registering node types + triage watcher. |
| src/MeshWeaver.Notifications.Channels/README.md | Documents the module purpose, activation, and delisting semantics. |
| src/MeshWeaver.Notifications.Channels/NotificationTriageService.cs | Moves triage watcher into module and adjusts DI/config gating + ContentAs usage. |
| src/MeshWeaver.Notifications.Channels/NotificationChannelsModuleAttribute.cs | Adds module attribute and AddNotificationChannels() registration surface (node types + hosted service forward + options binding). |
| src/MeshWeaver.Notifications.Channels/MeshWeaver.Notifications.Channels.csproj | New project for the module and its dependencies. |
| src/MeshWeaver.Mesh.Contract/Services/IMeshQueryCore.cs | Grants internals access to the new module assembly. |
| src/MeshWeaver.Graph/Configuration/GraphConfigurationExtensions.cs | Removes rule/channel type registration from core AddGraph() path (now module-owned). |
| src/MeshWeaver.Documentation/Data/Architecture/Notifications.md | Notes that rules/channels + triage watcher ship as a module. |
| src/MeshWeaver.Documentation/Data/Architecture/Modules.md | Adds module inventory entry for Notifications.Channels. |
| src/MeshWeaver.Documentation/Data/Architecture/EmailIngestionAndNotifications.md | Updates registration narrative: bell stays core; rules/channels are module-owned. |
| MeshWeaver.slnx | Adds the new module project to the solution. |
| memex/MeshModulesPublish.targets | Ensures the module ships as part of mesh module publish. |
| memex/Memex.Portal.Shared/MemexConfiguration.cs | Removes compiled-in hosted service registration; points to module activation. |
| memex/Memex.Portal.Shared/Memex.Portal.Shared.csproj | Adds a project reference for the module (double-ship transition). |
| memex/Memex.Portal.Monolith/appsettings.json | Activates the module via Modules:Assemblies. |
| memex/aspire/Memex.Portal.Distributed/appsettings.json | Activates the module via Modules:Assemblies. |
Suppressed comments (1)
src/MeshWeaver.Notifications.Channels/NotificationTriageService.cs:29
- The doc comment says "all failures are logged", but the implementation deliberately swallows some faults (e.g., the rules probe subscription ignores timeout/error). Please adjust the comment to match the actual behavior so future readers don't assume comprehensive logging/alerting.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…nnels-module # Conflicts: # memex/Memex.Portal.Monolith/appsettings.json # memex/MeshModulesPublish.targets # memex/aspire/Memex.Portal.Distributed/appsettings.json # src/MeshWeaver.Documentation/Data/Architecture/Modules.md
Test Results (shard 0)919 tests 918 ✅ 11m 33s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results (shard 5)1 371 tests 1 370 ✅ 7m 6s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results (shard 3) 11 files 11 suites 5m 53s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results (shard 4)1 566 tests 1 560 ✅ 6m 8s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results (shard 2)2 326 tests 2 322 ✅ 7m 14s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)2 247 tests 2 144 ✅ 7m 56s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
Test Results 65 files 65 suites 45m 52s ⏱️ Results for commit 531b24d. ♻️ This comment has been updated with latest results. |
…gleton factory descriptor Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The notification delivery-channels slice leaves
Memex.Portal.Sharedand rides the module lane assrc/MeshWeaver.Notifications.Channels: the user-authoredNotificationRule/NotificationChannelnode types plus theNotificationTriageServicewatcher that escalates in-app notifications per each recipient's rules. The bell,NotificationService(deterministic dispatch + email preferences),NotificationCenter*, theNotification/NotificationSettingsnode types, and the settings tab stay platform, untouched.Scoping answers (each could have falsified — none did)
IHostedServiceactually start? Yes, in both hosts.MeshHostApplicationBuilder'sServiceConfigisx => x.Invoke(Host.Services)— moduleConfigureServiceswrites straight into the HOST's service collection, andInstallAssembliesruns insideConfigureMemexMeshduringUseMeshWeaver/UseOrleansMeshServer, synchronously beforebuilder.Build()in bothMemex.Portal.MonolithandMemex.Portal.Distributed.MeshWeaver.Observabilityis the shipped precedent: itsLogIncidentControlPlaneregisters viaAddSingleton<IHostedService>(sp => …)from the module attribute and runs in production offModules:Assemblies.content/,samples/,test/,clients/) + live-mesh search found: zero test consumers, zero in-mesh code callers, one live data node (rbuergi/_NotificationChannel/Email), three agent-instruction markdown files (NotificationTriage.md,Assistant.md,ExecutiveAssistant.md— literal strings, degrade gracefully), and one platform compile-time dependency:NotificationService.HasRoutingRules(src/MeshWeaver.Graph) readsNotificationRuleNodeTypeconsts to defer its deterministic email to triage. Graph cannot reference the module, so the const classes stay in Graph as compiled residue (same shape as the Slide/Deck residue documented inAddGraph); only the registrations moved. Delisting semantics are documented in the module README/attribute: types leave create/search contexts, triage stops; a recipient with existing rule nodes falls back to in-app only.NotificationTriageServicedepend on Portal.Shared-only types? No. Its only questionable dependency wasPortalApplication(src/MeshWeaver.Blazor), used solely to obtain a hub. Replaced with the rootIMessageHubfrom DI — the exact shapeLogIncidentControlPlaneships with (includinghub.StartThreadon the root hub). One internal surface followed it:IMeshQueryCore(system-level unfiltered query; the watch spans all users' notifications) — the module joined the interface's existingInternalsVisibleTowhitelist, the same grantMemex.Portal.Sharedheld.What moved / what changed
src/MeshWeaver.Notifications.Channels/— assembly-levelNotificationChannelsModuleAttribute(MeshNodeProviderAttribute) andAddNotificationChannels()sharing one configure path (OgCard shape): registersAddNotificationRuleType()+AddNotificationChannelType(), bindsEmailOptionsviaAddOptions<T>().BindConfiguration(...), registers the triage watcher as a mesh-scoped singleton +IHostedServiceforward.NotificationTriageServicemoved frommemex/Memex.Portal.Shared/Notifications/(deleted there); theEmail:Enabledregistration-time gate became a startup self-skip (module install time has noIConfiguration); content read switched to the sanctionedContentAs<Notification>.AddGraph()no longer registers the two types (comment points at the module);MemexConfigurationno longer registers the hosted service.Modules:Assembliesin both hosts' appsettings + ProjectReference fromMemex.Portal.Shared+MeshModulesPublish.targetsentry + slnx.test/Memex.Portal.Shared.Test/NotificationChannelsModuleTest.cs(ObservabilityBootPackTest shape):InstallAssembliesregisters the watcher singleton with anIHostedServiceFORWARD (never a second construction) and both node types on the static-node provider.Modules.mdinventory row (+Emailconfig column),EmailIngestionAndNotifications.mdregistration paragraph,Notifications.mdmodule note.NotificationTriageagent stays platform content (content/ai/Agent/), named by string — the same split Observability uses forLogTriage(moving it would break the MeshWeaver.Plugins agent-parity check).Verification
dotnet build -c Release -warnaserror, one project per invocation:MeshWeaver.Notifications.Channels,Memex.Portal.Monolith,Memex.Portal.Distributed— all0 Warning(s) 0 Error(s).--no-build, fresh.trxverified):MeshWeaver.Graph.Test1183/1183 passed,Memex.Portal.Shared.Test366/366 passed (incl. the new pin test).MeshWeaver.Documentation.TestDocumentationLinkIntegrityTest— passed (rebuilt so the embedded docs carry the edits).🤖 Generated with Claude Code