diff --git a/Directory.Build.props b/Directory.Build.props
index 95416a2..68032b4 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -21,7 +21,15 @@
admin entrypoint (DynamicTenancyAdminExtensions) that dispatches add/disable/enable/remove
to every registered dynamic tenant source — graceful no-op when none — so CritterWatch never
sniffs the concrete tenancy type. -->
- 2.6.1
+
+ 2.8.0
13
1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618
Jeremy D. Miller;Jaedyn Tonee
diff --git a/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs b/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs
index 33f972a..a0a583c 100644
--- a/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs
+++ b/src/CoreTests/MultiTenancy/dynamic_tenancy_admin_surface.cs
@@ -20,8 +20,9 @@ public async Task auto_assign_overload_throws_by_default()
public async Task auto_assign_overload_is_used_when_a_source_supports_it()
{
var source = new AutoAssignTenantSource();
- await ((IDynamicTenantSource)source).AddTenantAsync("acme");
+ var resolved = await ((IDynamicTenantSource)source).AddTenantAsync("acme");
source.AutoAssigned.ShouldBe(["acme"]);
+ resolved.ShouldBe("shard-acme"); // resolved database id / partition suffix
}
[Fact]
@@ -41,9 +42,10 @@ public async Task auto_assign_add_dispatches_to_registered_sources()
var source = new AutoAssignTenantSource();
var services = provider(source);
- await services.AddTenantAsync("acme");
+ var resolved = await services.AddTenantAsync("acme");
source.AutoAssigned.ShouldBe(["acme"]);
+ resolved.ShouldBe("shard-acme");
}
[Fact]
@@ -87,7 +89,7 @@ public async Task is_a_graceful_no_op_when_no_dynamic_source_is_registered()
// None of these should throw with no source registered
await services.AddTenantAsync("acme", "Host=db1");
- await services.AddTenantAsync("acme");
+ (await services.AddTenantAsync("acme")).ShouldBeNull(); // no source -> no resolved assignment
await services.DisableTenantAsync("acme");
await services.EnableTenantAsync("acme");
await services.RemoveTenantAsync("acme");
@@ -167,10 +169,11 @@ internal class AutoAssignTenantSource : ValueOnlyTenantSource, IDynamicTenantSou
{
public List AutoAssigned { get; } = new();
- public Task AddTenantAsync(string tenantId, CancellationToken token = default)
+ public Task AddTenantAsync(string tenantId, CancellationToken token = default)
{
AutoAssigned.Add(tenantId);
- return Task.CompletedTask;
+ // Simulate the assignment strategy resolving the tenant onto a shard / partition.
+ return Task.FromResult($"shard-{tenantId}");
}
}
diff --git a/src/JasperFx/Descriptors/HttpChainDescriptor.cs b/src/JasperFx/Descriptors/HttpChainDescriptor.cs
index 3408c85..6b0f15b 100644
--- a/src/JasperFx/Descriptors/HttpChainDescriptor.cs
+++ b/src/JasperFx/Descriptors/HttpChainDescriptor.cs
@@ -109,11 +109,10 @@ public class HttpChainDescriptor : OptionsDescription
/// OpenAPI tags applied to this chain.
public new List Tags { get; set; } = new();
- /// Middleware steps in apply order.
- public List Middleware { get; set; } = new();
-
- /// Postprocessor steps in apply order.
- public List Postprocessors { get; set; } = new();
+ // jasperfx#411: the Middleware / Postprocessors / ServiceDependencies pipeline-introspection fields
+ // were removed. The same operator-facing information is available on demand via the chain's generated
+ // source code (RequestHandlerSourceCode in Wolverine.CritterWatch) — the generated C# IS the compiled
+ // pipeline — so the descriptor copies were redundant payload.
///
/// Cascading message types this chain may publish — the
@@ -122,9 +121,6 @@ public class HttpChainDescriptor : OptionsDescription
///
public List CascadingMessageTypes { get; set; } = new();
- /// Service dependencies the chain resolves at runtime.
- public List ServiceDependencies { get; set; } = new();
-
/// Full OpenAPI shape of the operation, when discoverable.
public OpenApiOperationDescriptor? OpenApi { get; set; }
diff --git a/src/JasperFx/Descriptors/HttpGraphUsage.cs b/src/JasperFx/Descriptors/HttpGraphUsage.cs
index 01a7466..9c7fe0f 100644
--- a/src/JasperFx/Descriptors/HttpGraphUsage.cs
+++ b/src/JasperFx/Descriptors/HttpGraphUsage.cs
@@ -80,11 +80,8 @@ public class HttpGraphUsage : OptionsDescription
///
public List PolicyNames { get; set; } = new();
- ///
- /// Type names of middleware that may apply across the graph (the
- /// WolverineHttpOptions.Middleware registry).
- ///
- public List MiddlewareTypes { get; set; } = new();
+ // jasperfx#411: graph-level MiddlewareTypes (the WolverineHttpOptions.Middleware registry) was removed
+ // for API consistency with the per-chain pipeline-introspection removal.
///
/// Names of tenant-detection strategies registered on the graph.
diff --git a/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs b/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs
index 42313a8..24a2dba 100644
--- a/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs
+++ b/src/JasperFx/Descriptors/MiddlewareStepDescriptor.cs
@@ -1,29 +1,8 @@
namespace JasperFx.Descriptors;
-///
-/// One ordered step in a chain's middleware / postprocessor pipeline.
-///
-public class MiddlewareStepDescriptor
-{
- /// Type that owns the step (handler / middleware / policy).
- public string TypeFullName { get; set; } = "";
-
- /// Method name within the owning type.
- public string MethodName { get; set; } = "";
-
- ///
- /// Step kind — "Middleware", "Postprocessor",
- /// "Validation", "Audit", etc. Used for visual
- /// grouping in the Pipeline tab.
- ///
- public string Kind { get; set; } = "";
-
- ///
- /// Compact display string for the row (e.g.
- /// "OrderHandler.Before(Order)").
- ///
- public string Description { get; set; } = "";
-}
+// jasperfx#411: MiddlewareStepDescriptor (one step in a chain's middleware/postprocessor pipeline) was
+// removed along with HttpChainDescriptor.Middleware/Postprocessors. Pipeline information is available on
+// demand via the chain's generated source code rather than mirrored into the descriptor payload.
///
/// Narrow per-namespace prefix applied via Wolverine's
diff --git a/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs b/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs
index a2ad8e2..a3449a3 100644
--- a/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs
+++ b/src/JasperFx/MultiTenancy/DynamicTenancyAdminExtensions.cs
@@ -32,11 +32,22 @@ public static Task AddTenantAsync(this IServiceProvider services, string tenantI
/// Add a tenant whose connection/partition is auto-assigned by the source (sharded/partitioned style;
/// no caller-supplied value). Dispatched to every registered dynamic tenant source — a source that
/// requires a connection value will surface from its default
- /// .
+ /// . Returns the
+ /// resolved assignment (database id / partition suffix) from the first source that provisioned the
+ /// tenant, or when no dynamic source is registered.
///
- public static Task AddTenantAsync(this IServiceProvider services, string tenantId,
+ public static async Task AddTenantAsync(this IServiceProvider services, string tenantId,
CancellationToken token = default)
- => forEachSource(services, source => source.AddTenantAsync(tenantId, token));
+ {
+ string? resolved = null;
+ foreach (var source in services.DynamicTenantSources())
+ {
+ var assignment = await source.AddTenantAsync(tenantId, token).ConfigureAwait(false);
+ resolved ??= assignment;
+ }
+
+ return resolved;
+ }
///
/// Disable (soft delete) a tenant across every registered dynamic tenant source.
@@ -91,7 +102,7 @@ public static Task AddTenantAsync(this IHost host, string tenantId, string conne
=> host.Services.AddTenantAsync(tenantId, connectionValue);
///
- public static Task AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default)
+ public static Task AddTenantAsync(this IHost host, string tenantId, CancellationToken token = default)
=> host.Services.AddTenantAsync(tenantId, token);
///
diff --git a/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs b/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs
index feaf735..acaff5d 100644
--- a/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs
+++ b/src/JasperFx/MultiTenancy/IDynamicTenantSource.cs
@@ -14,15 +14,16 @@ public interface IDynamicTenantSource : ITenantedSource
///
/// Provision a new tenant whose connection/partition is auto-assigned by the source's configured
/// strategy — e.g. sharded-database pooling or per-tenant event partitions — rather than a
- /// caller-supplied connection value. The default implementation throws
- /// ; sources that auto-assign (e.g. Marten's sharded tenancy)
- /// override it. This is the uniform "add a tenant" entrypoint for provisioning models that own the
- /// physical assignment, so a tool such as CritterWatch never has to sniff the concrete tenancy type.
- /// See jasperfx#409.
+ /// caller-supplied connection value. Returns the resolved assignment: the database id (sharded pool)
+ /// or partition suffix (managed partitions) the tenant landed on, so the caller (e.g. CritterWatch)
+ /// learns where it was placed without sniffing the concrete tenancy type. The default implementation
+ /// throws ; auto-assign sources (e.g. Marten's sharded tenancy)
+ /// override it, while caller-supplies-value sources keep
+ /// . See jasperfx#413 (split from #409).
///
- Task AddTenantAsync(string tenantId, CancellationToken token = default)
+ Task AddTenantAsync(string tenantId, CancellationToken token = default)
=> throw new NotSupportedException(
- $"This tenant source ({GetType().FullName}) requires a caller-supplied connection value; call AddTenantAsync(tenantId, connectionValue) instead, or use a source that supports auto-assignment.");
+ $"This tenant source ({GetType().FullName}) does not support auto-assignment; call AddTenantAsync(tenantId, connectionValue) with a caller-supplied connection value instead.");
///
/// Disable a tenant (soft delete). The tenant data is preserved but