Skip to content

move(core): split Hosting.AspNetCore — the portal host middleware leaves for MeshWeaver.Plugins (delete half) - #7310

Merged
rbuergi merged 6 commits into
mainfrom
move/aspnetcore-portal-split
Oct 28, 2026
Merged

rbuergi merged 6 commits into
mainfrom
move/aspnetcore-portal-split

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

This is the DELETE half of splitting MeshWeaver.Hosting.AspNetCore. Its ADDITION half is Systemorph/MeshWeaver.Plugins#4344. It is separate from the test-container and voice-gateway pair (#7306).

Stacked on #7297. The base is chore/core-drops-modules-publish-targets, because core's memex/aspire/Memex.Portal.ServiceDefaults uses UserDirectoryReadiness, and #7297 deletes that project. This PR stays a draft until three things have landed, in this order:

  1. Plugins#4333 merges.
  2. chore(platform): core drops memex/ and the dead Roslynator pins (delete half) #7297 merges, and GitHub retargets this PR to main.
  3. Plugins#4344 merges.

Pairs-with: Systemorph/MeshWeaver.Plugins#4344

The rule this follows: published bytes must still link

A [TypeForwardedTo] can only point at an assembly the forwarding assembly references, and core never references MeshWeaver.Plugins. So a type may leave core for Plugins only if no published plugin or satellite binary binds it by assembly name. I measured that rather than inferring it:

  • Plugins modules: I built all 42 registry module projects of MeshWeaver.Plugins (main, with fix(#4296): a negated path is a FILTER, never the anchor it excludes #4333) and read every TypeRef they hold into MeshWeaver.Hosting.AspNetCore from their metadata.
    • Bound root types: MeshEndpointProviderAttribute (Courses, Mail.Gmail, Mail.MicrosoftGraph, Mcp, Teams, WhatsApp), McpConfiguration (Mcp) and SessionHubResolver (Mcp).
    • Bound Portal types: PortalApplication (AppleMessages, Blazor.Chat, Courses, Mail.Gmail, Mail.MicrosoftGraph, Teams, WhatsApp), PortalErrorSink (Blazor.Chat), and UserIdentityCache / UserIdentityLookup (Mail.MicrosoftGraph).
  • Satellites: a REST code search of the Systemorph org for MeshWeaver.Hosting.AspNetCore found one satellite, MeshWeaver.SocialMedia. Its MeshWeaver.Social module and suite use only the root namespace (the endpoint hook and MapMeshModuleEndpoints). A search for Markdown.Export outside core and Plugins found nothing compiled.
  • In-mesh sources: no Plugins or core content tree uses the Portal namespace.

What stays

  • The endpoint-contribution hook: MeshEndpointProviderAttribute, ModuleEndpointDataSource, MeshModuleEndpointExtensions, MeshModuleEndpointMetadata and MeshModuleStaticAssetExtensions with its records.
  • McpConfiguration and SessionHubResolver.
  • Every module-bound Portal type above, plus what their signatures name: ICircuitContextAccessor / CircuitContextAccessor, PortalErrorReporting, and UserDirectoryReading. That record is split out of UserDirectoryReadiness.cs into its own file, because UserIdentityCache.Reading returns it.
  • MeshWeaver.Markdown.Export.Contract stays, because PortalApplication needs it. It did not lose its only core consumer.

What leaves

These are host code, and no published binary binds any of them:

  • UserContextMiddleware, ValidatedTokenCache, MeshUserProjection and VUserHelper
  • UserDirectoryReadiness
  • RequestNamingExceptionHandling and RequestName
  • NonfileRouteConstraint and NonfileRouteConstraintExtensions
  • ArticleExtensions
  • Authentication/AuthenticationOptions, ExternalProviderConfig and AuthenticationProviders

They are now at Plugins src/MeshWeaver.Hosting.AspNetCore.PortalHost, under the NEW namespace MeshWeaver.Hosting.AspNetCore.PortalHost. Keeping the old namespace failed on the Plugins module-bundle build, which compiles against the sealed image and not a core checkout. It reported CS0436 against this assembly's still-exported names. The full account is in Plugins#4344 and in CarvingProjectsOutOfCore. The ContentCollections reference leaves with ArticleExtensions.

scripts/type-forwards.allow gets one entry per departed type, 13 in total. Each states that no shipped module holds the TypeRef, which the measurement above backs, so no epoch is declared and no break is claimed.

Local gates

  • Core: MeshWeaver.Hosting.AspNetCore and MeshWeaver.Documentation.Test build Release -warnaserror with 0 warnings and 0 errors.
  • Doc tests: DocumentationLinkIntegrityTest, PackageFamiliesMoveTogetherGuard, ArchitectureTopicMapTest and NoLiteralNulInSourceGuard pass 13 of 13 (trx).
  • Type forwards: check-type-forwards.py against the base reported exactly these 13 departures before the allow entries, and reports OK after them.
  • Satellite against this branch alone: MeshWeaver.SocialMedia at origin/main (41d0dd5) builds against this branch with no other repository.
    • MeshWeaver.Social builds Release -warnaserror with 0 warnings and 0 errors.
    • MeshWeaver.Social.Test builds with 0 errors. Its 34 xUnit1069 warnings are demoted by that repository's own props.
    • SocialModuleContributionTest passes 3 of 3 (trx).
  • Plugins#4344 against this branch: 34 projects build with 0 errors. Its module-workspace build, reproduced locally against a pre-delete sealed image, goes from RED (6 × CS0436) to GREEN (52 of 52 projects). Details are in that PR.

Not verified

  • The ladder lanes (platform-compat and platform-ladder-compat) were not run locally. They will measure the deployed bundles against this build in CI.
  • A module that another estate built from a private fork is outside the measurement.

Removal owed once this lands

MeshWeaver.Plugins keeps ONE transitional allow key for this assembly's old type:
MeshWeaver.Hosting.AspNetCore.Portal.NonfileRouteConstraint.ExcludedPrefixes, in src/MeshWeaver.PathResolution.Test/NoStaticCollectionsTest.cs and src/Memex.Hosts.Test/NoStaticCollectionsTest.cs (added back by Plugins#4344). The guard ignores keys whose type is absent, so the key becomes inert as soon as this delete is in the pinned platform set. Delete both lines in the first Plugins PR after that pin moves. The Plugins copy is a FrozenSet and needs no key.

Recycles: none. Nothing a per-node hub serves changes.

🤖 Generated with Claude Code

rbuergi and others added 4 commits October 27, 2026 09:42
…te half)

memex/MeshModulesPublish.targets and memex/aspire/Memex.Portal.ServiceDefaults are MeshWeaver.Plugins'
now (Plugins#4333); memex/Directory.Build.props served only them. Gone with them: the slnx entries,
MeshWeaver.Hosting.Test's ServiceDefaults reference and AnArbitraryUrlFetcherGivingUpIsAWarningTest
(moved to Memex.Portal.Shared.Test), .aspire/settings.json and two .gitignore lines that pointed at
memex/aspire paths core no longer has, and 'memex' from every source guard's scanned roots.

Roslynator: five PackageVersion pins no core project restores, whose only use was a
PackageReference Update= block in MeshWeaver.Layout.csproj that matched no Include.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r MeshWeaver.Plugins (split, delete half)

MeshWeaver.Hosting.AspNetCore keeps the endpoint-contribution hook (a satellite compiles its module
against it) and the Portal/ types published Plugins module bytes bind — PortalApplication,
PortalErrorSink, UserIdentityCache/UserIdentityLookup — with what their signatures name
(ICircuitContextAccessor, UserDirectoryReading, split out of UserDirectoryReadiness.cs). Measured
from the TypeRefs of all 42 registry module DLLs: no forwarder can point from core into Plugins,
so a bound type cannot leave.

The host-only rest moves to MeshWeaver.Plugins as MeshWeaver.Hosting.AspNetCore.Portal, namespace
unchanged: UserContextMiddleware, ValidatedTokenCache, MeshUserProjection, VUserHelper,
UserDirectoryReadiness, RequestNamingExceptionHandling, NonfileRouteConstraint, ArticleExtensions,
Authentication/AuthenticationOptions. The ContentCollections reference leaves with
ArticleExtensions; Markdown.Export.Contract stays with PortalApplication.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oves to Plugins (#7310)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    25 files  ±0      25 suites  ±0   56m 4s ⏱️ -35s
12 179 tests ±0  12 177 ✅ ±0  2 💤 ±0  0 ❌ ±0 
12 205 runs  ±0  12 203 ✅ ±0  2 💤 ±0  0 ❌ ±0 

Results for commit 5081359. ± Comparison against base commit 3c37044.

This pull request removes 59 and adds 22 tests. Note that renamed tests count towards both.

   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---, expected: True)
   --- End of inner exception stack trace ---, kind: "Timeout", layer: null)
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
 ---> (Inner Exception #1) MeshWeaver.Messaging.HubDisposingException: Hub node/a is shutting down — cannot create "x". The address may reactivate (recycle / restart); retry to get the authoritative answer.
 ---> (Inner Exception #1) System.ArgumentException: Value does not fall within the expected range.<---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
…
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall as an aggregate's FIRST member", fault: System.AggregateException: One or more errors occurred. (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall as an aggregate's SECOND member", fault: System.AggregateException: One or more errors occurred. (The operation has timed out.) (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> System.TimeoutException: The operation has timed out.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---
, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall nested in an aggregate inside a wrapper", fault: System.InvalidOperationException: outer
 ---> System.AggregateException: One or more errors occurred. (Value does not fall within the expected range.) (Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.)
 ---> System.ArgumentException: Value does not fall within the expected range.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.<---

   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "a stall wrapped as InnerException", fault: System.InvalidOperationException: outer
 ---> MeshWeaver.Mesh.QueryProviderStalledException: Query provider(s) [pg] did not emit an Initial within the query fan-in's 16s bound for query 'nodeType:NodeType' (user 'system'). The merged Initial gates on EVERY provider, so this query has NO snapshot to answer with — it is reported as unavailable (retryable) rather than left hanging with no error. This is an availability failure, never a permission verdict: a consumer deciding access must fail CLOSED and say it could not establish the answer. Fix the stalled provider; never bump the consumer's timeout.
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Compiler.Pipeline.Test.SourcesWatcherStallBackoffTest ‑ The_production_classifier_recognises_a_stall_through_every_wrapping(because: "an aggregate of unrelated faults", fault: System.AggregateException: One or more errors occurred. (The operation has timed out.) (Value does not fall within the expected range.)
 ---> System.TimeoutException: The operation has timed out.
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.ArgumentException: Value does not fall within the expected range.<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.FaultClassifierTest ‑ Classifies(shape: "a bare disposed object with no context — not prova"···, exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'MemoryStream'., kind: "Fault", layer: null)
MeshWeaver.Messaging.Hub.Test.FaultClassifierTest ‑ Classifies(shape: "a cancellation whose timeout cause sits at aggrega"···, exception: System.OperationCanceledException: t
 ---> System.AggregateException: One or more errors occurred. (x) (The operation has timed out.)
 ---> System.InvalidOperationException: x
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.TimeoutException: The operation has timed out.<---

   --- End of inner exception stack trace ---, kind: "Timeout", layer: null)
MeshWeaver.Messaging.Hub.Test.FaultClassifierTest ‑ Classifies(shape: "a driver wrapping a connect timeout", exception: MeshWeaver.Messaging.Hub.Test.FaultClassifierTest+FakeDbException (0x80004005): Failed to connect
 ---> System.TimeoutException: The operation has timed out., kind: "Transient", layer: "Store")
MeshWeaver.Messaging.Hub.Test.FaultClassifierTest ‑ Classifies(shape: "a hub announcing its disposal", exception: MeshWeaver.Messaging.HubDisposingException: Hub node/a is shutting down — cannot create "stream". The address may reactivate (recycle / restart); retry to get the authoritative answer.
Object name: 'node/a'., kind: "HubTeardown", layer: null)
MeshWeaver.Messaging.Hub.Test.FaultClassifierTest ‑ Classifies(shape: "a mixed aggregate keeps the defect", exception: System.AggregateException: One or more errors occurred. (Success) (boom)
 ---> System.Net.Sockets.SocketException (0x80004005): Success
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---
, kind: "Fault", layer: null)
…

♻️ This comment has been updated with latest results.

…space; why a checkout switch failed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Removing 13 public types without forwarders depends on Plugins#4344, which returned 404, leaving replacement implementations, consumer migration, and prerequisite merge status independently unverifiable.

No open issues 🎉

What changed in this PR

This PR removes the host-only portion of MeshWeaver.Hosting.AspNetCore as the deletion half of the move to MeshWeaver.Plugins#4344. Core retains the endpoint hooks and portal types needed by published modules, preserving their existing assembly identities.

Changes:

  • Removes portal middleware, authentication options, routing helpers, and readiness infrastructure relocated to Plugins.
  • Keeps UserDirectoryReading in core as a separate file and removes the unused ContentCollections reference.
  • Records 13 type departures and updates architecture documentation to explain the split and new locations.
File Description
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​VUserHelper.cs Removes the relocated virtual-user provisioning helper.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​ValidatedTokenCache.cs Removes the relocated token-validation cache.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​UserIdentityCache.cs Updates XML comments to identify readiness code in Plugins.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​UserDirectoryReading.cs Preserves the directory-reading record in its existing namespace.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​UserDirectoryReadiness.cs Removes the relocated readiness service; retains its record separately.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​UserContextMiddleware.cs Removes the relocated request identity middleware.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​RequestNamingExceptionHandling.cs Removes the relocated request-naming and exception helpers.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​NonfileRouteConstraint.cs Removes the relocated route constraint and endpoint extensions.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​MeshUserProjection.cs Removes the relocated user-profile projection helper.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​Authentication/​AuthenticationOptions.cs Removes the relocated authentication configuration types.
src/​MeshWeaver.Hosting.AspNetCore/​Portal/​ArticleExtensions.cs Removes the relocated author-name formatting helper.
src/​MeshWeaver.Hosting.AspNetCore/​MeshWeaver.Hosting.AspNetCore.csproj Drops ContentCollections and documents retained dependencies.
src/​MeshWeaver.Documentation/​Data/​Architecture/​UserDirectoryOpensAtStart.md Updates the readiness service’s documented location.
src/​MeshWeaver.Documentation/​Data/​Architecture/​TokenValidationHotPath.md Updates middleware and cache source locations.
src/​MeshWeaver.Documentation/​Data/​Architecture/​MeshRestApiContract.md Documents the exception handler’s new namespace and project.
src/​MeshWeaver.Documentation/​Data/​Architecture/​CarvingProjectsOutOfCore.md Explains the compatibility evidence and namespace transition.
scripts/​type-forwards.allow Adds 13 PR-scoped entries for departed host types.

Review effort: Balanced

…l-split

# Conflicts:
#	src/MeshWeaver.Documentation/Data/Architecture/CarvingProjectsOutOfCore.md
@rbuergi
rbuergi changed the base branch from chore/core-drops-modules-publish-targets to main October 28, 2026 00:52
@rbuergi
rbuergi marked this pull request as ready for review October 28, 2026 00:52
@github-actions
github-actions Bot requested a balanced review from Copilot October 28, 2026 00:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Removing 13 public types without forwarders depends on a companion PR that returned 404, leaving consumer migration, merge ordering, and published-bundle compatibility unverified.

No open issues 🎉

Review effort: Balanced

@rbuergi rbuergi closed this Oct 28, 2026
@rbuergi rbuergi reopened this Oct 28, 2026
@rbuergi
rbuergi merged commit ccdc9b4 into main Oct 28, 2026
476 of 617 checks passed
@rbuergi
rbuergi deleted the move/aspnetcore-portal-split branch October 31, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants