feat(social): Social rides the module lane — first endpoint-hook consumer - #1667
Conversation
…umer MeshWeaver.Social grows into a full module: the LinkedIn connect/publish/ page-sync endpoints move out of Memex.Portal.Shared as SocialModuleAttribute (MeshEndpointProviderAttribute) contributions applied by the host's MapMeshModuleEndpoints, and the DI surface (LinkedInOptions via the options pipeline, the LinkedInPublisher typed client, both node-menu providers) rides SocialMeshModuleAttribute -> AddSocial() — one configure path for the attribute lane and explicit composition (the OgCard two-lane rule). The two OAuth redirect targets keep their pre-move anonymous semantics via explicit AllowAnonymous (the CSRF state cookie is the guard); every other route stays authenticated under the hook's group default. DI parameters on the module's handlers are explicit [FromServices] so a module endpoint never depends on host-container inference for parameter classification. Stays platform: ApiCredentialNodeType (existing credential nodes must deserialize when the module is delisted) and the LinkedIn SIGN-IN scheme (AddLinkedInAuthentication — auth schemes configure before the host builds). Activation: MeshWeaver.Social.dll in both hosts' Modules:Assemblies, the modules/ publish layout, double-ship ProjectReference kept. Pinned by SocialModuleContributionTest (both attributes, AddSocial registration shape, all 9 routes with exact auth semantics). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Test Results (shard 2)2 320 tests - 2 2 316 ✅ - 2 7m 11s ⏱️ +13s Results for commit 2408f9c. ± Comparison against base commit 0a8f869. This pull request removes 59 and adds 57 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results (shard 1)2 247 tests +5 2 144 ✅ +5 7m 40s ⏱️ +23s Results for commit 2408f9c. ± Comparison against base commit 0a8f869. This pull request removes 54 and adds 59 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
# Conflicts: # memex/Memex.Portal.Monolith/appsettings.json # memex/MeshModulesPublish.targets # memex/aspire/Memex.Portal.Distributed/appsettings.json
There was a problem hiding this comment.
Pull request overview
Migrates the LinkedIn Social feature from portal-compiled wiring to the “module lane” as the first concrete consumer of the new endpoint-contribution hook, so enabling/disabling MeshWeaver.Social.dll cleanly adds/removes its HTTP routes and UI menu actions.
Changes:
- Adds Social module assembly attributes to contribute endpoints via
MapMeshModuleEndpoints()and register Social DI via the module install path. - Updates LinkedIn connect/publish/page-sync endpoints and node-menu providers to live under
MeshWeaver.Socialand to use explicit minimal-API binding annotations. - Adds a pinning test plus documentation/What’s New updates and includes the Social module in Memex module publish + host module lists.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Monolith.Test/SocialModuleContributionTest.cs | Adds a pin test asserting Social contributes endpoints + DI registrations and that auth semantics match expectations. |
| test/MeshWeaver.Hosting.Monolith.Test/MeshWeaver.Hosting.Monolith.Test.csproj | References MeshWeaver.Social for the new monolith-hosting test. |
| src/MeshWeaver.Social/SocialPostMenuProvider.cs | Moves/adjusts post-node menu provider and gates it on LinkedIn configuration. |
| src/MeshWeaver.Social/SocialModuleAttribute.cs | Introduces assembly-level module attributes and a single AddSocial() registration surface. |
| src/MeshWeaver.Social/PlatformCredential.cs | Adds a shared constant for the ApiCredential NodeType name (registration remains host-side). |
| src/MeshWeaver.Social/MeshWeaver.Social.csproj | Adds ASP.NET framework reference and references hosting hook + Graph for menu-provider surface. |
| src/MeshWeaver.Social/LinkedInPublishService.cs | Updates docs/comment reference to the moved endpoints. |
| src/MeshWeaver.Social/LinkedInPublisher.cs | Adds a SectionName constant for options binding. |
| src/MeshWeaver.Social/LinkedInPublishEndpoints.cs | Updates namespace and makes service/body binding explicit for module-contributed endpoints. |
| src/MeshWeaver.Social/LinkedInPageSyncEndpoints.cs | Updates namespace, uses ContentAs, adds explicit binding and pins anonymous callback semantics. |
| src/MeshWeaver.Social/LinkedInCredentialMenuProvider.cs | Updates namespace, uses ContentAs, gates on options, and simplifies platform extraction. |
| src/MeshWeaver.Social/LinkedInConnectEndpoints.cs | Updates namespace, adds explicit binding and pins anonymous callback semantics. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-social-module.md | Adds a What’s New entry describing the module-lane move. |
| src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia/LinkedInPublishing.md | Updates documentation to reflect endpoints + menu actions living in the Social module. |
| src/MeshWeaver.Documentation/Data/Architecture/Modules.md | Documents Social as first endpoint-hook consumer and adds it to module inventory table. |
| memex/MeshModulesPublish.targets | Adds Social module project to module publish list. |
| memex/Memex.Portal.Shared/Social/GitHubConnectEndpoints.cs | Updates doc reference to point at the module-provided LinkedIn connect endpoints. |
| memex/Memex.Portal.Shared/MemexConfiguration.cs | Removes portal-compiled Social wiring and documents what remains platform-owned (auth scheme + NodeType registration). |
| memex/Memex.Portal.Monolith/appsettings.json | Adds MeshWeaver.Social.dll to Modules:Assemblies. |
| memex/aspire/Memex.Portal.Distributed/appsettings.json | Adds MeshWeaver.Social.dll to Modules:Assemblies. |
Suppressed comments (3)
src/MeshWeaver.Social/SocialPostMenuProvider.cs:35
INodeMenuProvider.Contextis consistently set viaNodeMenuItemsExtensions.NodeMenuContext(e.g.src/MeshWeaver.Graph/Configuration/NodeMenuItemsExtensions.cs:36) to avoid typos and keep context names centralized. This provider uses the literal string instead.
src/MeshWeaver.Social/LinkedInPageSyncEndpoints.cs:110- The org callback handler later assumes
Social:LinkedIn:ClientIdis configured (null-forgivingconfig[...]!). Adding a defensive check near the top avoids a possible exception if configuration changes between start/callback or the callback is hit directly.
src/MeshWeaver.Social/LinkedInConnectEndpoints.cs:118 - The callback handler later assumes
Social:LinkedIn:ClientIdis configured (null-forgivingconfig[...]!). Adding a defensive check near the top avoids a possible exception if configuration changes between start/callback or the callback is hit directly.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Migrates Social to the module lane as the first consumer of the endpoint-contribution hook (#1655). Stacked on #1663 (
feat/1655-endpoint-hook) — the base retargets tomainonce that merges.What moved (into
src/MeshWeaver.Social)LinkedInConnectEndpoints,LinkedInPublishEndpoints,LinkedInPageSyncEndpointsleftmemex/Memex.Portal.Shared/Social/and are now contributed by the assembly-levelSocialModuleAttribute : MeshEndpointProviderAttribute, applied by the host's existingapp.MapMeshModuleEndpoints(). Delisting the module 404s the routes wholesale.LinkedInCredentialMenuProvider+SocialPostMenuProvider. Verdict: both stay DI providers, not seededUiContributionnodes — their predicates are beyond the closed vocabulary (live credential-presence query with self-hiding, viewer-own-User check,$type/platform/publishedUrnnode-shape logic with a Publish↔Refresh swap). They register through the module'sBuilderConfigurations→AddSocial(), and now gate onSocial:LinkedIn:ClientIdat runtime (previously the host registered them conditionally — behavior preserved, registration unconditional).SocialMeshModuleAttribute : MeshNodeProviderAttributefoldsAddSocial()(the OgCard two-lane rule: fixtures call the same publicMeshBuilder.AddSocial()extension).LinkedInOptionsnow binds viaAddOptions<T>().BindConfiguration("Social:LinkedIn")with a bare-instance singleton bridge soLinkedInPublisher's ctor (and its tests) keep the plain-options shape.What stayed (deliberately)
ApiCredentialNodeType+.AddApiCredentialType()stay in Portal.Shared — the NodeType registration must survive delisting so existing credential nodes keep deserializing. The module carries only the NodeType name (PlatformCredential.ApiCredentialNodeType).AddLinkedInAuthentication(...)(theAddOAuth-based authentication scheme in the auth pipeline,MemexConfigurationservice config) stays platform — auth schemes configure before the host builds. Only the ENDPOINTS moved. This is the auth-scheme/endpoint split called out in the design.GitHubConnect/GitHubLogin/GitHubWebhook/InstanceConnectendpoints — untouched (auth-pipeline + different concern, scoped separately).Auth semantics — route by route
The hook's group defaults to
RequireAuthorization(). Every previously-authenticated route keeps its explicitRequireAuthorization(); the two routes that were registered without auth — the OAuth redirect targets/connect/linkedin/callbackand/connect/linkedin/org/callback— opt out via.AllowAnonymous()with a comment: LinkedIn's redirect must not bounce through a login challenge (which would drop thecode/statequery); the CSRF state cookie set by the authenticated start route is the guard. Exact pre-move semantics, now pinned by test.One hardening the pin test forced: DI parameters on the module's handlers are now explicit
[FromServices](+[FromBody]on the JSON publish body). Minimal-API parameter inference classifies a service parameter by asking the host's container at endpoint-materialization time — a module must not depend on host registration to keep its parameters from degrading into inferred-body binding.Activation / double-ship
MeshWeaver.Social.dlladded toModules:Assembliesof BOTH hosts (Monolith + Distributed appsettings). No deploy-side override of that list exists, so image-built portals pick it up.ProjectReferenceto MeshWeaver.Social (ships-the-bits double-ship state);memex/MeshModulesPublish.targetsgains the module (thin lane);MeshWeaver.slnxalready carried the project (verified).MapLinkedIn*calls removed fromMemexConfiguration.MeshWeaver.Social.csprojgainsFrameworkReference Microsoft.AspNetCore.App+ refs toMeshWeaver.Hosting.AspNetCore(the hook) andMeshWeaver.Graph(menu-provider surface).Docs
Doc/Architecture/Modulesinventory gains theMeshWeaver.Social.dllrow (Social:LinkedInconfig section) + a first-consumer note;Doc/DataMesh/SocialMedia/LinkedInPublishinglocation paragraph updated; What's New entry included.Verification
-warnaserrorclean, one project per invocation: MeshWeaver.Social, Memex.Portal.Shared, Memex.Portal.Monolith, Memex.Portal.Distributed, MeshWeaver.Hosting.Monolith.Test, MeshWeaver.Social.Test, Memex.Portal.Shared.Test, MeshWeaver.Documentation.Test.SocialModuleContributionTest(sibling ofModuleEndpointContributionTest): both attributes present with non-empty contributions; installing the assembly appliesAddSocial(providers scoped onINodeMenuProvider, options + typed client registered); all 9 routes map through the hook with the exact auth semantics (only the two callbacks anonymous). Passed! 10/10 with the hook + LinkedIn publish tests.MeshWeaver.Social.Test(33/33) and fullMemex.Portal.Shared.Testgreen;DocumentationLinkIntegrityTestgreen.Memex.Portal.Shared.Socialnamespace (checkedLinkedIn/LinkedInProfile/Source/*,SocialMedia/Post/Source/*, Code-node search); route URLs referenced from mesh content are unchanged.Notes / deviations
LinkedInCredentialMenuProvider's "Download past posts" item links/connect/linkedin/pull— no compiled endpoint maps that route anywhere in the repo (pre-existing; unchanged by this move). Flagging for a follow-up.ContentAs<PlatformCredential>instead of direct casts.🤖 Generated with Claude Code