You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(modules): ownership diagnostics — the log line #6584 and #6588 would write - #7027
The fixes for #6584 and #6588 shipped, but neither defect wrote a log line while it was live. The production log readings on both issues (2026-10-21) were therefore inconclusive: a quiet log after the fix cannot tell "fixed" from "never logs". The owner's decision was to add the line each defect WOULD write, roll it, then verify on production.
Ownership diagnostics. Every line is a Warning under category MeshWeaver.ServiceProvider.ModuleScopeOwnership with the stable prefix [ModuleScopeOwnership], names its issue, and is written once per registration (so a live defect cannot flood Loki). The correct route writes nothing. No existing log level is changed.
The ownership registry.ModuleInstanceOwners holds one entry per instance, weakly (ConditionalWeakTable, so it pins no generation). There is one per mesh, registered by MeshBuilder as an instance, never static. It records what each module container built, plus ModuleServiceProxy instances over disposable interfaces. The registry and its logger are captured when a container is built and never resolved mid-resolve. A first draft resolved them through the resolve context, and that recursed through RootResolutionScope into a circular dependency, which the existing ownership tests caught.
Tests
ModuleScopeOwnershipDiagnosticsTest re-introduces each defect deliberately and shows its line fires. These are the negative controls:
The same suite shows the correct routes write nothing, on raw Autofac containers and through a full generic host with a module installed in its own load context.
Local: MeshWeaver.ServiceProvider, MeshWeaver.Mesh.Contract and MeshWeaver.Compiler.Pipeline.Test build with -c Release -warnaserror (0 warnings, 0 errors). The new suite and the existing ownership suites (AModuleForwarderOwnsNothing*, ANotOwnedForwarderKeepsItsPositionTest, AnotherModulesScopedServiceIsTheCallersTest, ACallersModuleScopeEndsWithTheGenerationTest, CallerAndModuleScopedServicesBuildInOneLockTest) pass: 18 of 18. The whole MeshWeaver.Compiler.Pipeline.Test project passes: 1566 of 1566.
Verification after the roll
The plan is posted on both issues. In short: governed Logs InstanceActions on the control MCP server, query |= "[ModuleScopeOwnership]", run against each portal. A zero counts only alongside two things in the same window and with the same selector:
evidence that the image carries this change;
a positive control ([ModuleLoad] lines of the same pods) proving the query reads that portal and was not cut.
No recycle is needed. The diagnostics live in the root and module containers, which a pod roll rebuilds.
Doc: Doc/Architecture/LiveModuleUpdate → "Ownership diagnostics: the line each defect would write".
Pairs-with: none — additive only; no public type or member removed.
Implementers: none — no interface member added.
Mirror-sync: none — no i18n catalog key added or changed.
…ould write
Neither defect wrote a log line while live, so a quiet production log could
not tell fixed from never-logs. Adds Warning-level, once-per-registration
lines under MeshWeaver.ServiceProvider.ModuleScopeOwnership, marker
[ModuleScopeOwnership]:
- a module scoped registration built in the module container's root scope
(no caller owns it) or served to a caller that does not own its scope (#6584);
- a scope owned-factory registration returning a module-owned instance, written
as that scope ends and disposes it (#6588).
Ownership is recorded in one mesh-scoped, weak ModuleInstanceOwners registry.
Negative-control tests re-introduce each defect; the correct route logs nothing.
Refs #6584, refs #6588
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…#6588 line at scope end
Review of #7027: a factory returning an instance built elsewhere re-recorded
it and took over its owner/caller (AddOrUpdate), hiding the disposal and the
cross-caller serve; and the #6588 report was claimed at resolve, keyed by owner
tag, so a scope ending before the first resolver was silent and each caller tag
repeated the line. First record now wins (TryAdd); the line is claimed when the
scope ends, once per registration, and nothing subscribes once reported.
Three negative-control tests fail without this.
Refs #6584, refs #6588
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…is attempted
Review of #7027: the line was written only after a successful build, so the
defect's throwing shape (DialogService over an uninitialised NavigationManager)
never wrote it. Reported before activation; ownership still recorded after.
Negative control AScopedServiceThatThrowsWhenBuiltForNoCaller_StillWarns fails
without this.
Refs #6584
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A later exercise can produce zero warnings while the defect remains. Once a warning is reported, subsequent occurrences are suppressed; if that first warning predates the query window, the image and log controls above can both pass without exposing it. Require complete, untruncated log coverage from each relevant pod's process start through the exercise before treating zero as a closing reading. Otherwise, mark the result inconclusive.
… own, not at the root (#6584) (#7055)
* fix(modules): query providers resolve node validators in a scope they own, not at the root (#6584)
The #7027 diagnostic fired on production for two module write guards
(InstanceActionRequestWriteGuard, ModelCeilingWriteGuard): built in the module
container's root scope, which no caller owns. The route: the root-singleton query
providers (StorageAdapterMeshQueryProvider, StaticNodeQueryProvider) took
IEnumerable<Lazy<INodeValidator>> from the root container. Every INodeValidator is
scoped, so that was a captive dependency; a module's scoped validator was forwarded
for no caller and built in the module's root scope, only for the Read filter to read
its SupportedOperations and discard it.
SingletonNodeValidators, a root singleton, begins a scope of its own and resolves the
set there: that scope is the caller, so a module's scoped validator is built in the
module scope it owns and disposed with the root. Both query providers take the set
from it.
AModuleNodeValidatorIsNeverBuiltForNoCallerTest fails against the old registration
(one build in a hub scope, one with no caller) and passes with the fix.
Refs #6584
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(modules): the validator set ends with its holder — a consumer outliving the root no longer pins the mesh hub (#6584)
CopyMoveDeadlineReleasesHubTest went red on #7055: the disposed mesh hub stayed
alive through StaticNodeQueryProvider -> Lazy<INodeValidator>[] ->
ServicePrincipalAdminGuard -> MessageHub (heap dump, gcroot). The static-node
provider stays subscribed to the module registry past the root's disposal and
now shared the array the storage provider materialises. The holder hands out a
sequence read afresh per enumeration and empty once disposed.
Refs #6584
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(modules): the validator sequence holds the array alone — no iterator, no holder (#6584)
The sequence a root-singleton consumer keeps was a C# iterator method. An
iterator enumerated on the thread that created it is its own enumerator and
keeps its last Current after the walk, so the disposed mesh hub stayed pinned
whenever the first enumeration ran on the constructing thread — red in CI
(CopyMoveDeadlineReleasesHubTest), green locally. It also reached the holder,
whose disposed scope still references every service it resolved.
LiveValidators now holds only the validator array, hands out a fresh array
enumerator per call, and is emptied on dispose. SingletonNodeValidatorsReleaseOnDisposeTest
constructs, enumerates and disposes on one thread and asserts every built
validator is collectable (fails on the iterator, on a holder-reaching sequence,
and on an array-only iterator).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(hosting): project the validator probes straight to an array, no mutable accumulator
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The fixes for #6584 and #6588 shipped, but neither defect wrote a log line while it was live. The production log readings on both issues (2026-10-21) were therefore inconclusive: a quiet log after the fix cannot tell "fixed" from "never logs". The owner's decision was to add the line each defect WOULD write, roll it, then verify on production.
Refs #6584
Refs #6588
What
Ownership diagnostics. Every line is a Warning under category
MeshWeaver.ServiceProvider.ModuleScopeOwnershipwith the stable prefix[ModuleScopeOwnership], names its issue, and is written once per registration (so a live defect cannot flood Loki). The correct route writes nothing. No existing log level is changed.ModuleServiceProvider.CreateForCallers→ModuleScopeDiagnostics) warns when a module's SCOPED registration is activated in the module container's own root scope. That is the defect's shape: one instance per generation, shared by every circuit and built against none.ModuleServiceProvider.ReportIfServedAcrossCallers, called fromResolveAt(…, caller)andModuleServices.Resolve(…, caller), warns when a scoped instance is handed to a caller that does not own the module scope it was built in.PopulateMeshWeaver(every MeshWeaver container and hub scope) watches factory-activated registrations owned by the resolving scope. When one returns an instance a module owns, the warning is written as that scope begins to end, just before it disposes the instance.ModuleInstanceOwnersholds one entry per instance, weakly (ConditionalWeakTable, so it pins no generation). There is one per mesh, registered byMeshBuilderas an instance, never static. It records what each module container built, plusModuleServiceProxyinstances over disposable interfaces. The registry and its logger are captured when a container is built and never resolved mid-resolve. A first draft resolved them through the resolve context, and that recursed throughRootResolutionScopeinto a circular dependency, which the existing ownership tests caught.Tests
ModuleScopeOwnershipDiagnosticsTestre-introduces each defect deliberately and shows its line fires. These are the negative controls:The same suite shows the correct routes write nothing, on raw Autofac containers and through a full generic host with a module installed in its own load context.
Local:
MeshWeaver.ServiceProvider,MeshWeaver.Mesh.ContractandMeshWeaver.Compiler.Pipeline.Testbuild with-c Release -warnaserror(0 warnings, 0 errors). The new suite and the existing ownership suites (AModuleForwarderOwnsNothing*,ANotOwnedForwarderKeepsItsPositionTest,AnotherModulesScopedServiceIsTheCallersTest,ACallersModuleScopeEndsWithTheGenerationTest,CallerAndModuleScopedServicesBuildInOneLockTest) pass: 18 of 18. The wholeMeshWeaver.Compiler.Pipeline.Testproject passes: 1566 of 1566.Verification after the roll
The plan is posted on both issues. In short: governed
LogsInstanceActions on the control MCP server, query|= "[ModuleScopeOwnership]", run against each portal. A zero counts only alongside two things in the same window and with the same selector:[ModuleLoad]lines of the same pods) proving the query reads that portal and was not cut.No recycle is needed. The diagnostics live in the root and module containers, which a pod roll rebuilds.
Doc:
Doc/Architecture/LiveModuleUpdate→ "Ownership diagnostics: the line each defect would write".Pairs-with: none — additive only; no public type or member removed.
Implementers: none — no interface member added.
Mirror-sync: none — no i18n catalog key added or changed.
🤖 Generated with Claude Code