diff --git a/src/Persistence/MartenTests/MultiTenancy/blue_green_version_bump_assignment.cs b/src/Persistence/MartenTests/MultiTenancy/blue_green_version_bump_assignment.cs index 5ce04f44a..7ad55cec3 100644 --- a/src/Persistence/MartenTests/MultiTenancy/blue_green_version_bump_assignment.cs +++ b/src/Persistence/MartenTests/MultiTenancy/blue_green_version_bump_assignment.cs @@ -217,12 +217,11 @@ private static List AgentsFor(IEnumerable agents, string projectionNam agents.Where(uri => uri.Segments.Any(segment => segment.Trim('/').Equals(projectionName, StringComparison.OrdinalIgnoreCase))).ToList(); - // The (type, name, databaseId) prefix of an agent URI, mirroring the internal - // EventSubscriptionAgentFamily.DatabaseKeyOf that group affinity keys on. - private static string DatabaseKeyOf(Uri uri) => - uri.Segments.Length >= 3 - ? $"{uri.Host}/{uri.Segments[1].Trim('/')}/{uri.Segments[2].Trim('/')}" - : uri.AbsoluteUri; + // Calls the real EventSubscriptionAgentFamily.DatabaseKeyOf rather than a local copy of it (GH-3819). + // This used to reimplement the URI grammar verbatim, which is exactly the drift risk that made the + // method public: a copy asserting co-location can quietly stop matching the key the distribution + // really groups on, and the test would keep passing while proving nothing. + private static string DatabaseKeyOf(Uri uri) => EventSubscriptionAgentFamily.DatabaseKeyOf(uri); } public record TripStarted(Guid Id, string Description); diff --git a/src/Wolverine/Runtime/Agents/EventSubscriptionAgentFamily.cs b/src/Wolverine/Runtime/Agents/EventSubscriptionAgentFamily.cs index e9444d293..555358638 100644 --- a/src/Wolverine/Runtime/Agents/EventSubscriptionAgentFamily.cs +++ b/src/Wolverine/Runtime/Agents/EventSubscriptionAgentFamily.cs @@ -321,10 +321,17 @@ private async ValueTask> RetireSupersededAgentsAsync(AssignmentGrid return DatabaseId.TryParse(uri.Segments[2].Trim('/'), out var id) ? id : null; } - // Agent URIs are event-subscriptions://{type}/{name}/{databaseId}/{shard...} (see UriFor); the - // (type, name, databaseId) prefix identifies the shard database an agent belongs to, so grouping on it - // co-locates every tenant/projection agent for one database on the same node. - internal static string DatabaseKeyOf(Uri uri) + /// + /// The (type, name, databaseId) prefix of an event-subscription agent URI — the key group affinity + /// distributes on, so every tenant/projection agent belonging to one shard database is co-located on + /// the same node. Agent URIs are event-subscriptions://{type}/{name}/{databaseId}/{shard...} + /// (see ). + /// + /// Public so that callers reasoning about agent placement per database — an admin view, a + /// readiness probe, a test asserting co-location — can use the same key the distribution actually + /// uses instead of re-implementing the URI grammar and silently drifting from it. See GH-3819. + /// + public static string DatabaseKeyOf(Uri uri) => uri.Segments.Length >= 3 ? $"{uri.Host}/{uri.Segments[1].Trim('/')}/{uri.Segments[2].Trim('/')}" : uri.AbsoluteUri; @@ -348,7 +355,7 @@ internal static string StoreKeyOf(Uri uri) /// else is a tenant. Returns null for a URI that doesn't parse as this scheme's grammar — such an /// agent is never treated as superseded. /// - internal static string? TenantNeutralKeyOf(Uri uri) + public static string? TenantNeutralKeyOf(Uri uri) { // Segments: "/", {storeName}, {databaseId}, {projectionName}, {shardKey}, [v{n} | tenant], [tenant] var segments = uri.Segments.Skip(1).Select(x => x.Trim('/')).Where(x => x.Length > 0).ToArray();