Skip to content
bitzorcas
中EN

Recipe

Matter Intake & Conflicts: Hands-on Multi-Tenant 4-Tier Dynamic Validation

Master enterprise multi-tenant validation grounded in LegalTech: from universal base invariants and composite conflict strategies to tenant hot-configs and no-code field delivery rules.

Last updated

In the early evolution of multi-tenant enterprise SaaS systems, engineering teams frequently fall into the trap of tenant branching pollution:

  • Code-level hardcoded pollution: To satisfy bespoke risk management requirements across different enterprise clients, developers clutter single validator classes with brittle conditionals such as if (tenantId == "1000001") { ... } or When(t => ...), rapidly degrading core invariants into an unmaintainable state.
  • Release cycle bottlenecks: When an enterprise client mandates that cross-border arbitrations involving claims $\ge 50$ million must upload a formal Conflict of Interest Waiver, the engineering team is forced to rebuild, regression-test, and deploy a complete backend container release.
  • Zero regulatory tolerance: In legal technology and financial systems, representing an adversary against an active retainer client triggers immediate statutory disciplinary action, disbarment, and severe civil liability.

BitzOrcas.Modern introduces the “4-Tier Business Validation Golden Pattern.” It strictly partitions validation responsibilities into four cohesive tiers:

  1. Tier 1 (Universal Base Rules): Mandatory compile-time static invariants bound to the command;
  2. Tier 2(A) (Composite & Cascading Code Strategies): Domain-specific cross-entity invariants enforced via code;
  3. Tier 2(B) (Tenant Hot-Config Strategies): Thresholds and feature flags that dynamically update via config providers without redeployment;
  4. Tier 3 (Field Delivery No-Code Rule Engine): Runtime JSON regex and presence rules configured directly through admin consoles.

This tutorial guides you through building a production-grade multi-tenant validation pipeline grounded in Litigation Matter Intake and Adversary Conflict Blocking.

Multi-Tenant 4-Tier Validation Architecture

Tier 2: Tenant-Specific Rule ExtensibilityBase passedAll passedAll passedAll passedViolationViolationViolationViolation

1. Inbound Command (CreateMatterIntakeCommand)

2. Resolve Trusted Tenancy Context (ICurrentTenant)

3. Tier 1: Universal Base Rules (IRequestRule)
Required fields, positive claim amounts, distinct parties

4. Dispatch Tenant Strategy (ITenantValidationStrategy.GetTenantRules)

5. Tier 2(A): Cross-Border Composite Cascading Strategy (Conflict Waiver)

6. Tier 2(B): Tenant Dynamic Hot-Config Lookback Strategy

7. Tier 3: Field Delivery No-Code Rule Engine

8. All rules passed: forward to business Handler for persistence

9. Violation short-circuit: emit RFC 9457 Problem Details (Zero DB overhead)


Step 1: Define the Matter Intake Command Contract

The intake command encapsulates claim values, client names, adversaries, and optional conflict waiver references:

src/Modules/Business/Legal/Application/Commands/CreateMatterIntake/CreateMatterIntakeCommand.cs
using BitzOrcas.Application.Authorization;
using BitzOrcas.Domain.Abstractions;
using BitzOrcas.Domain.Results;
using BitzOrcas.Endpoint.Attributes;
using Mediator;
namespace BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake;
public enum MatterCategory
{
Litigation = 1, // Civil/Commercial Litigation
CrossBorder = 2, // International Arbitration
NonLitigation = 3 // Advisory and M&A
}
[GenerateEndpoint(HttpRoute.Post, "/api/legal/matters", Tag = "LegalMatter")]
public sealed record CreateMatterIntakeCommand(
string MatterTitle,
string ClientName,
string OpposingParty,
decimal ClaimAmount,
MatterCategory Category,
string? ConflictWaiverDocumentId = null) : ICommand<Result<string>>, IAuthorizedRequest
{
public ResourceDescriptor Resource { get; } = new("Legal", "Matter");
public AuthorizationAction Action { get; } = AuthorizationAction.Create;
}

Step 2: Implement Tier 1 Universal Base Rules

Invariants mandatory across all tenants are declared via IRequestRule<TRequest>:

src/Modules/Business/Legal/Application/Commands/CreateMatterIntake/Rules/LegalValidationErrors.cs
using BitzOrcas.Domain.Results;
namespace BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake.Rules;
/// <summary>
/// Static validation error catalog for legal matter intake.
/// </summary>
public static class LegalValidationErrors
{
/// <summary>
/// Matter title is invalid
/// </summary>
public static readonly Error InvalidTitle =
Error.Validation("Legal.Validation.InvalidTitle", "Matter title cannot be empty or exceed 200 characters.");
/// <summary>
/// Both retaining client and opposing party are mandatory
/// </summary>
public static readonly Error PartiesRequired =
Error.Validation("Legal.Validation.PartiesRequired", "Both retaining client and opposing party are mandatory.");
/// <summary>
/// Adversary conflict of interest detected
/// </summary>
public static readonly Error AdversaryConflict =
Error.Conflict("Legal.Validation.AdversaryConflict", "Retaining client and opposing party cannot be identical.");
/// <summary>
/// Claim amount must be strictly greater than zero
/// </summary>
public static readonly Error InvalidAmount =
Error.Validation("Legal.Validation.InvalidAmount", "Claim amount must be strictly greater than zero.");
/// <summary>
/// Conflict waiver document is required for high-value cross-border matters
/// </summary>
public static readonly Error WaiverRequired =
Error.Validation("Legal.Validation.WaiverRequired", "Cross-border disputes with claim values exceeding 50M mandate an executed Conflict Waiver document.");
}
src/Modules/Business/Legal/Application/Commands/CreateMatterIntake/Rules/CreateMatterIntakeBaseRule.cs
using BitzOrcas.Application.Validation;
using BitzOrcas.Domain.Results;
namespace BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake.Rules;
/// <summary>
/// Tier 1: Invariant validation rules enforced across all tenants.
/// </summary>
public sealed class CreateMatterIntakeBaseRule : IRequestRule<CreateMatterIntakeCommand>
{
public ValueTask<Result> ValidateAsync(CreateMatterIntakeCommand request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.MatterTitle) || request.MatterTitle.Length > 200)
{
return ValueTask.FromResult(Result.Failure(LegalValidationErrors.InvalidTitle));
}
if (string.IsNullOrWhiteSpace(request.ClientName) || string.IsNullOrWhiteSpace(request.OpposingParty))
{
return ValueTask.FromResult(Result.Failure(LegalValidationErrors.PartiesRequired));
}
// Base Invariant: Client and opposing party cannot be identical legal entities
if (string.Equals(request.ClientName.Trim(), request.OpposingParty.Trim(), StringComparison.OrdinalIgnoreCase))
{
return ValueTask.FromResult(Result.Failure(LegalValidationErrors.AdversaryConflict));
}
if (request.ClaimAmount <= 0)
{
return ValueTask.FromResult(Result.Failure(LegalValidationErrors.InvalidAmount));
}
return ValueTask.FromResult(Result.Success());
}
}

Step 3: Implement Tier 2 Tenant-Specific Strategy (ITenantValidationStrategy)

In BitzOrcas.Modern, tenant-specific customizations never pollute base rules. By implementing ITenantValidationStrategy<TRequest>, the engine queries the active tenant context (ICurrentTenant) to append tenant-specific rules:

src/Modules/Business/Legal/Application/Commands/CreateMatterIntake/Strategies/TenantMatterValidationStrategy.cs
using BitzOrcas.Application.Abstractions.Tenancy;
using BitzOrcas.Application.Validation;
using BitzOrcas.Domain.Results;
using BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake.Rules;
namespace BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake.Strategies;
/// <summary>
/// Tier 2: Tenant-specific rule strategy dispatcher.
/// </summary>
public sealed class TenantMatterValidationStrategy : ITenantValidationStrategy<CreateMatterIntakeCommand>
{
public IReadOnlyList<IRequestRule<CreateMatterIntakeCommand>> GetTenantRules(ICurrentTenant currentTenant)
{
var tenant = currentTenant.Tenant;
if (!tenant.IsAvailable) return [];
var rules = new List<IRequestRule<CreateMatterIntakeCommand>>();
// Tenant 1000001 (Premier International Firm): High-value matters require mandatory conflict waivers
if (tenant.EffectiveTenantId == "1000001")
{
rules.Add(new HighValueCrossBorderWaiverRule());
}
return rules;
}
}
/// <summary>
/// Tier 2(A): Composite cascading rule enforced for enterprise tenants.
/// </summary>
internal sealed class HighValueCrossBorderWaiverRule : IRequestRule<CreateMatterIntakeCommand>
{
public ValueTask<Result> ValidateAsync(CreateMatterIntakeCommand request, CancellationToken cancellationToken)
{
// Rule: Cross-border arbitration with claim >= 50M mandates an executed Conflict Waiver document
if (request.Category == MatterCategory.CrossBorder && request.ClaimAmount >= 50_000_000.00m)
{
if (string.IsNullOrWhiteSpace(request.ConflictWaiverDocumentId))
{
return ValueTask.FromResult(Result.Failure(LegalValidationErrors.WaiverRequired));
}
}
return ValueTask.FromResult(Result.Success());
}
}

Step 4: End-to-End Multi-Tenant Isolation Testing

Assert that tenant-specific rules isolate enforcement to targeted tenants:

tests/BitzOrcas.Integration.Tests/Legal/MultiTenantValidationIntegrationTests.cs
using System.Net;
using System.Net.Http.Json;
using BitzOrcas.Domain.Results;
using BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake;
using BitzOrcas.Modules.Legal.Application.Commands.CreateMatterIntake.Rules;
using Shouldly;
using Xunit;
public sealed class MultiTenantValidationIntegrationTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly CustomWebApplicationFactory _factory;
public MultiTenantValidationIntegrationTests(CustomWebApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task StandardTenant_SubmittingHighValueMatter_WithoutWaiver_ShouldSucceed()
{
// 1. Dispatch high-value claim from standard regional tenant (Tenant 1000002)
var client = _factory.CreateAuthenticatedClient(tenantId: "1000002", role: "SuperAdmin");
var command = new CreateMatterIntakeCommand(
MatterTitle: "Commercial Construction Dispute",
ClientName: "Apex Construction Corp.",
OpposingParty: "Municipal Development Group",
ClaimAmount: 60_000_000.00m,
Category: MatterCategory.CrossBorder,
ConflictWaiverDocumentId: null); // Regional tenant does not enforce Tier 2(A) waiver policy
// 2. Dispatch request
var response = await client.PostAsJsonAsync("/api/legal/matters", command);
// 3. Assert success: standard tenant is exempt from Tier 2(A) rules
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task RedCircleTenant_SubmittingHighValueMatter_WithoutWaiver_ShouldBeBlockedByTier2Rule()
{
// 1. Dispatch identical high-value claim from enterprise law firm tenant (Tenant 1000001)
var client = _factory.CreateAuthenticatedClient(tenantId: "1000001", role: "SuperAdmin");
var command = new CreateMatterIntakeCommand(
MatterTitle: "Cross-Border Offshore Fund Dispute",
ClientName: "Global Offshore Capital Fund",
OpposingParty: "Multinational Asset Group",
ClaimAmount: 60_000_000.00m,
Category: MatterCategory.CrossBorder,
ConflictWaiverDocumentId: null); // Missing required waiver
// 2. Dispatch request
var response = await client.PostAsJsonAsync("/api/legal/matters", command);
// 3. Assert failure: Tier 2 rule short-circuits with HTTP 400 Bad Request
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
var result = await response.Content.ReadFromJsonAsync<Result<string>>();
result.ShouldNotBeNull();
result.IsFailure.ShouldBeTrue();
result.Error.Code.ShouldBe(LegalValidationErrors.WaiverRequired.Code);
}
}

Architectural Review & Benefits

  1. Zero Branching Pollution: Universal invariants (Tier 1) and tenant strategies (Tier 2) are physically separated, eliminating fragile if (tenantId == ...) anti-patterns.
  2. Deterministic Execution: The pipeline evaluates universal rules first, invoking tenant strategy dispatchers only when base rules pass to maximize throughput.
  3. Zero-Downtime Adaptability: Coupling dynamic strategies with tenant config providers allows immediate threshold adjustments across active tenants without backend redeployments.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%