Skip to content

feat(modules): ownership diagnostics — the log line #6584 and #6588 would write - #7027

Merged
rbuergi merged 4 commits into
mainfrom
fix/module-scope-ownership-diagnostics
Oct 21, 2026
Merged

rbuergi merged 4 commits into
mainfrom
fix/module-scope-ownership-diagnostics

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

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.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.

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:

  1. evidence that the image carries this change;
  2. 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.

🤖 Generated with Claude Code

…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>

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.

🟡 Changes recommended

Warning suppression and overwritten ownership records can hide the defects these diagnostics are intended to expose.

3 open issues
What changed in this PR

Adds module ownership diagnostics to make regressions behind #6584 and #6588 visible in production logs.

Changes:

  • Tracks module-owned instances and warns about incorrect sharing or disposal.
  • Connects diagnostics to containers, service resolution, and disposable proxies.
  • Adds negative-control tests and production verification guidance.
File Description
test/​MeshWeaver.Compiler.Pipeline.Test/​ModuleScopeOwnershipDiagnosticsTest.cs Tests faulty and correct ownership routes.
src/​MeshWeaver.ServiceProvider/​ServiceProviderExtensions.cs Shares ownership tracking across scopes.
src/​MeshWeaver.ServiceProvider/​NotOwnedServiceDescriptor.cs Attaches disposal diagnostics during registration.
src/​MeshWeaver.ServiceProvider/​ModuleServiceProvider.cs Tracks activation and cross-caller resolution.
src/​MeshWeaver.ServiceProvider/​ModuleScopeOwnership.cs Implements ownership tracking and warnings.
src/​MeshWeaver.ServiceProvider/​MeshWeaver.ServiceProvider.csproj Adds logging abstractions.
src/​MeshWeaver.Mesh.Contract/​ModuleServices.cs Checks ownership when serving instances.
src/​MeshWeaver.Mesh.Contract/​ModuleServiceForwarders.cs Records disposable proxy ownership.
src/​MeshWeaver.Mesh.Contract/​MeshBuilder.cs Registers mesh-scoped ownership tracking.
src/​MeshWeaver.Documentation/​Data/​Architecture/​LiveModuleUpdate.md Documents diagnostics and verification.

Review effort: Balanced

rbuergi and others added 2 commits October 21, 2026 12:45
…#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>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 10:46

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.

🟡 Changes recommended

Diagnostic coverage gaps and registration deduplication can hide ownership failures from production verification.

3 open issues
3 resolved since last review

Review effort: Balanced

…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>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 11:00

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

The verification procedure can falsely declare success when an earlier warning falls outside the query window and later occurrences are suppressed.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Low severity Suppressed warnings can make zero-warning results falsely pass

src/​MeshWeaver.Documentation/​Data/​Architecture/​LiveModuleUpdate.md:538

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.

3 resolved since last review

Review effort: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

    22 files      22 suites   1h 3m 49s ⏱️
13 604 tests 13 604 ✅ 0 💤 0 ❌
13 634 runs  13 634 ✅ 0 💤 0 ❌

Results for commit 9b9cafa.

@rbuergi
rbuergi merged commit c7ad412 into main Oct 21, 2026
rbuergi added a commit that referenced this pull request Oct 22, 2026
… 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>
@rbuergi
rbuergi deleted the fix/module-scope-ownership-diagnostics branch October 24, 2026 14:14
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