Repository navigation
fix(mesh): the router names a spokesman — no one publishes from the main hub, and gone-subscriber stream-end drops go quiet - #2928
Conversation
…okesman, and a terminal notice to a departed subscriber is Debug
'No one must ever publish from main hub' + the log flood ('we get tons of this … get rid of
all of this'), maintainer, 2026-09-01. Two roots, measured on the compile+render gate and the
live portal:
1. Workspace's recycle announcement posted StreamEndedEvent through Configuration.ParentHub —
for a per-node/activity hub that IS the mesh router, so every recycled activity produced a
ROUTER_TRAFFIC violation ('StreamEndedEvent has the mesh hub as sender'). The router now
registers a RouterCarrier spokesman (MeshBuilder → the nodeops execution hub, the same seam
every node-CRUD path already hops onto); Workspace resolves it when the parent is
router-typed, falls back to the router's own non-router parent, and with neither present
keeps the documented change-feed-latch recovery. RecycleStrandsLiveSubscriberTest (the
production strand repro) passes in 587ms — the announcement still lands, without the
router's identity. (A naive climb-past-the-router hung it for a full minute: in the
Monolith host the router is the only parent, which is exactly why the spokesman exists.)
2. A StreamEndedEvent dropped because the target stream is ALREADY GONE is a typed no-op —
its entire meaning is 'nothing more comes', which a departed subscriber knows by
definition — yet it logged one WARN per released reader (Information+ ships to Loki).
The drop is Debug for that one type; every other StreamMessage drop (a lost Full renders a
region blank) stays a warning. Level change justified by type semantics, not convenience.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR reduces ROUTER_TRAFFIC-related log floods by ensuring recycle announcements don’t originate from the mesh router hub and by downgrading benign StreamEndedEvent drops to Debug.
Changes:
- Introduces a
RouterCarrier“spokesman” abstraction and registers it inMeshBuilderto route router-adjacent infrastructure posts through the nodeops execution hub. - Updates
Workspacerecycle announcements to avoid router-stamped senders by resolving a non-router carrier (viaRouterCarrieror a non-router parent). - Changes
DataExtensionslogging soStreamEndedEventdrops for already-gone subscribers log at Debug, while other dropped stream messages remain Warning.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/MeshWeaver.Messaging.Hub/RouterCarrier.cs | Adds a router “spokesman” record used to select a non-router hub for infrastructure posting. |
| src/MeshWeaver.Mesh.Contract/MeshBuilder.cs | Registers a default RouterCarrier mapping the router to NodeOperationExecutionHub(). |
| src/MeshWeaver.Data/Workspace.cs | Uses RouterCarrier (or a safe parent fallback) to avoid router-stamped recycle announcements. |
| src/MeshWeaver.Data/DataExtensions.cs | Lowers log level for benign StreamEndedEvent drops while keeping other drop diagnostics at Warning. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| IMessageHub? spokesman = null; | ||
| try { spokesman = carrier.Configuration.Get<RouterCarrier>()?.Resolve(carrier); } | ||
| catch { /* teardown probing must never throw */ } | ||
| if (spokesman is null) | ||
| { | ||
| IMessageHub? up; | ||
| try { up = carrier.Configuration.ParentHub; } | ||
| catch { up = null; } | ||
| spokesman = up is null || ReferenceEquals(up, carrier) | ||
| || string.Equals(up.Address.Type, AddressExtensions.MeshType, StringComparison.Ordinal) | ||
| ? null : up; | ||
| } |
Test Results (shard 0)793 tests 789 ✅ 3m 19s ⏱️ Results for commit 5cde0e3. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)1 136 tests 1 136 ✅ 3m 52s ⏱️ Results for commit 5cde0e3. ♻️ This comment has been updated with latest results. |
Test Results (shard 4)1 165 tests 1 165 ✅ 4m 51s ⏱️ Results for commit 5cde0e3. ♻️ This comment has been updated with latest results. |
Test Results (shard 5) 8 files 8 suites 4m 17s ⏱️ Results for commit 5cde0e3. ♻️ This comment has been updated with latest results. |
…retention edit — empty path, every prebuilt publisher red The retention-days insertion (#2925) landed INSIDE the one multi-line 'path: |' block in the lane, leaving upload-artifact with 'Input required and not supplied: path' — measured on the first ec1ef1b lap (Plugins run 33451338438, Markdown.Collaboration; the same lap also proved prepare-once live: 'platform image: cache HIT — docker load, no ACR' on both images). retention-days moves below the block where it belongs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Test Results (shard 3)1 152 tests 1 152 ✅ 8m 30s ⏱️ Results for commit 5cde0e3. ♻️ This comment has been updated with latest results. |
… scripts, referenced from AGENTS.md 'unify buildprocess' + 'centralize scripts as best as possible' + 'document this big time and reference in all the agents.md in all projects' (maintainer, 2026-09-01). - Doc/Architecture/ModuleBuildArchitecture is now THE build process: the full pipeline (select → prepare-once → one-workspace build → pack/tests fan out → verify), content-addressed blob outputs (unchanged ⇒ no compile — incremental CI below the diff selector), the compiler doctrine, implementation-framework gates, quiet-CI policy, the night's measured numbers, the per-repo adoption contract, and the roadmap (one-workspace lane, in-image verbs, #2926 runners). - compile-check.py is CENTRALIZED: the canonical copy lives at .github/scripts/ and the node-repo-compile-check lane fetches it at the pinned platform ref (the compose-sealed-modules.sh pattern — asserted RED when unavailable, no drifted-local fallback; three satellite copies had already diverged). Callers keep only their allow-file; MW_REPO_ROOT/MW_ALLOW_FILE carry the caller's tree into the platform's script, with the legacy beside-the-repo layout intact for unmigrated pins. - Core AGENTS.md carries the doctrine reference; every satellite AGENTS.md gets its own in the rollout branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and the leftover Debug pin is gone
Measured on shard 1 of this PR's own lap (job 99686210763): 356 'UpdateImpl: adds/updates/
deletes' Debug lines and the same 'Opening gate' repeated per update for the same path
dominated the console ('pls check github logs yourself and remove spam', maintainer,
2026-09-01). Three roots, three fixes:
- the gate-open ANNOUNCEMENT fires once per source (OpenGate itself stays idempotent per
update); the same line per heartbeat carried no information after the first;
- per-beat counts are TRACE (version re-stamps make updates=1 on nearly every emission);
Debug speaks only when the SHAPE changed (adds or deletes);
- test/MeshWeaver.Hosting.Monolith.Test/appsettings.json carried
'MeshWeaver.Graph.MeshNodeTypeSource: Debug' — committed residue of a past investigation,
now removed. (Query.Test's Debug pins STAY: deliberately scoped, documented with trx-size
receipts.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e closure — no module waits for an artifact it does not reference The first run that ever ACTIVATED prebuilt-modules (Plugins 33451338438) measured the #2922 wait indiscriminate: every container job — consumers and non-consumers alike — sat the bounded wait for every wanted module, and with the producers' uploads broken by the path: split that was a uniform ~680s of pure idling per job, poisoning the whole benchmark. The wait now runs only for modules in THIS entry's transitive ProjectReference closure (computed by a 12-line stdlib walk over csproj ProjectReferences — verified: Northwind's closure carries MeshWeaver.Import; OgCard's carries neither AI nor Import and skips every wait). The rendered bash and the embedded heredoc were proven by extraction + execution, not by eyeballing YAML dedent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Test Results (shard 2)3 059 tests 2 867 ✅ 6m 53s ⏱️ Results for commit 5cde0e3. |
Test Results 44 files 44 suites 31m 44s ⏱️ Results for commit 5cde0e3. |
Kills two measured log floods at the root ('no one must ever publish from main hub'):
MeshBuilderregisters aRouterCarrierspokesman (the nodeops execution hub — the seam all node CRUD already uses);Workspaceresolves it when its parent is router-typed.RecycleStrandsLiveSubscriberTest(the production strand repro) passes in 587ms — a naive climb-past-the-router instead hung it a full minute, which is why the spokesman exists.StreamEndedEventto an already-departed subscriber drops at Debug — a typed no-op ('nothing more comes' to someone who already left); every other StreamMessage drop stays WARN because those are real signal.🤖 Generated with Claude Code